import UserRepo, {UserAccountQuery, UserInfosQueryResponse} from "../repositories/user.repository.js";
import System from "../System.js";
import Book, {BookProps} from "./Book.js";
import {getUserEncryptionKey} from "../keyManager.js";

/**
 * Represents a user account with basic profile information.
 */
interface UserAccount {
    firstName: string;
    lastName: string;
    username: string;
    authorName: string;
    email: string;
}

/**
 * Represents the guide tour completion status for various features.
 */
export interface GuideTour {
    [key: string]: boolean;
}

/**
 * Summary information for a book associated with a user.
 */
interface BookSummary {
    bookId: string;
    title: string;
    subTitle?: string;
}

/**
 * Complete user information response including profile data and associated books.
 */
export interface UserInfoResponse {
    id: string;
    name: string;
    lastName: string;
    username: string;
    email: string;
    accountVerified: boolean;
    authorName: string;
    groupId: number;
    termsAccepted: boolean;
    guideTour: GuideTour[];
}

/**
 * Represents a user entity with encrypted personal information storage.
 * Handles user data retrieval, creation, and updates with AES-256-CBC encryption.
 */
export default class User {

    private readonly id: string;
    private firstName: string;
    private lastName: string;
    private username: string;
    private email: string;
    private accountVerified: boolean;
    private authorName: string;
    private groupId: number;
    private termsAccepted: boolean;

    /**
     * Creates a new User instance with the specified identifier.
     * @param id - The unique identifier for the user
     */
    constructor(id: string) {
        this.id = id;
        this.firstName = '';
        this.lastName = '';
        this.username = '';
        this.email = '';
        this.accountVerified = false;
        this.authorName = '';
        this.groupId = 0;
        this.termsAccepted = false;
    }

    /**
     * Fetches and decrypts the user's information from the database.
     * Populates all instance properties with the decrypted values.
     * @returns A promise that resolves when user information has been loaded
     */
    public async getUserInfos(): Promise<void> {
        const userInfosData: UserInfosQueryResponse = UserRepo.fetchUserInfos(this.id);
        const userEncryptionKey: string = getUserEncryptionKey(this.id);
        this.firstName = System.decryptDataWithUserKey(userInfosData.first_name, userEncryptionKey);
        this.lastName = System.decryptDataWithUserKey(userInfosData.last_name, userEncryptionKey);
        this.username = System.decryptDataWithUserKey(userInfosData.username, userEncryptionKey);
        this.email = System.decryptDataWithUserKey(userInfosData.email, userEncryptionKey);
        this.accountVerified = userInfosData.account_verified === 1;
        this.authorName = userInfosData.author_name ? System.decryptDataWithUserKey(userInfosData.author_name, userEncryptionKey) : '';
        this.groupId = userInfosData.user_group ? userInfosData.user_group : 0;
        this.termsAccepted = userInfosData.term_accepted === 1;
    }

    /**
     * Retrieves complete user information including associated books.
     * @param userId - The unique identifier of the user to fetch
     * @returns A promise resolving to the complete user information response
     */
    public static async returnUserInfos(userId: string): Promise<UserInfoResponse> {
        const user: User = new User(userId);
        await user.getUserInfos();
        const guideTourStatus: GuideTour[] = [];
        return {
            id: user.getId(),
            name: user.getFirstName(),
            lastName: user.getLastName(),
            username: user.getUsername(),
            email: user.getEmail(),
            accountVerified: user.isAccountVerified(),
            authorName: user.getAuthorName(),
            groupId: user.getGroupId(),
            termsAccepted: user.isTermsAccepted(),
            guideTour: guideTourStatus,
        };
    }

    /**
     * Creates a new user in the database with encrypted personal information.
     * @param userId - The unique identifier for the new user
     * @param firstName - The user's first name (will be encrypted)
     * @param lastName - The user's last name (will be encrypted)
     * @param username - The user's username (will be encrypted and hashed)
     * @param email - The user's email address (will be encrypted and hashed)
     * @param notEncryptPassword - The user's password in plain text (unused in current implementation)
     * @param lang - The preferred language for the user ('fr' or 'en'), defaults to 'fr'
     * @returns A promise resolving to the created user's identifier
     */
    public static async addUser(
        userId: string,
        firstName: string,
        lastName: string,
        username: string,
        email: string,
        notEncryptPassword: string,
        lang: 'fr' | 'en' = 'fr'
    ): Promise<string> {
        const userEncryptionKey: string = getUserEncryptionKey(userId);
        const encryptedFirstName: string = System.encryptDataWithUserKey(firstName, userEncryptionKey);
        const encryptedLastName: string = System.encryptDataWithUserKey(lastName, userEncryptionKey);
        const encryptedUsername: string = System.encryptDataWithUserKey(username, userEncryptionKey);
        const encryptedEmail: string = System.encryptDataWithUserKey(email, userEncryptionKey);
        const hashedEmail: string = System.hashElement(email);
        const hashedUsername: string = System.hashElement(username);
        return UserRepo.insertUser(
            userId,
            encryptedFirstName,
            encryptedLastName,
            encryptedUsername,
            hashedUsername,
            encryptedEmail,
            hashedEmail,
            lang
        );
    }

    /**
     * Updates an existing user's profile information in the database.
     * @param userKey - The encryption key for the user's data
     * @param userId - The unique identifier of the user to update
     * @param firstName - The updated first name (will be encrypted)
     * @param lastName - The updated last name (will be encrypted)
     * @param username - The updated username (will be encrypted and hashed)
     * @param email - The updated email address (will be encrypted and hashed)
     * @param authorName - The optional author/pen name (will be encrypted and hashed if provided)
     * @param lang - The preferred language for the user ('fr' or 'en'), defaults to 'fr'
     * @returns A promise resolving to true if the update was successful
     */
    public static async updateUserInfos(
        userKey: string,
        userId: string,
        firstName: string,
        lastName: string,
        username: string,
        email: string,
        authorName?: string,
        lang: 'fr' | 'en' = 'fr'
    ): Promise<boolean> {
        const encryptedFirstName: string = System.encryptDataWithUserKey(firstName, userKey);
        const encryptedLastName: string = System.encryptDataWithUserKey(lastName, userKey);
        const encryptedUsername: string = System.encryptDataWithUserKey(username, userKey);
        const encryptedEmail: string = System.encryptDataWithUserKey(email, userKey);
        const hashedEmail: string = System.hashElement(email);
        const hashedUsername: string = System.hashElement(username);
        let encryptedAuthorName: string = '';
        let hashedAuthorName: string = '';
        if (authorName) {
            encryptedAuthorName = System.encryptDataWithUserKey(authorName, userKey);
            hashedAuthorName = System.hashElement(authorName);
        }
        return UserRepo.updateUserInfos(
            userId,
            encryptedFirstName,
            encryptedLastName,
            encryptedUsername,
            hashedUsername,
            encryptedEmail,
            hashedEmail,
            hashedAuthorName,
            encryptedAuthorName,
            lang
        );
    }

    /**
     * Retrieves and decrypts the user's account information from the database.
     * @param userId - The unique identifier of the user
     * @returns A promise resolving to the decrypted user account information
     */
    public static async getUserAccountInformation(userId: string): Promise<UserAccount> {
        const accountData: UserAccountQuery = UserRepo.fetchAccountInformation(userId);
        const userEncryptionKey: string = getUserEncryptionKey(userId);
        const decryptedFirstName: string = accountData.first_name ? System.decryptDataWithUserKey(accountData.first_name, userEncryptionKey) : '';
        const decryptedLastName: string = accountData.last_name ? System.decryptDataWithUserKey(accountData.last_name, userEncryptionKey) : '';
        const decryptedUsername: string = accountData.username ? System.decryptDataWithUserKey(accountData.username, userEncryptionKey) : '';
        const decryptedAuthorName: string = accountData.author_name ? System.decryptDataWithUserKey(accountData.author_name, userEncryptionKey) : '';
        const decryptedEmail: string = accountData.email ? System.decryptDataWithUserKey(accountData.email, userEncryptionKey) : '';
        return {
            firstName: decryptedFirstName,
            lastName: decryptedLastName,
            username: decryptedUsername,
            authorName: decryptedAuthorName,
            email: decryptedEmail
        };
    }

    /**
     * Gets the unique identifier of the user.
     * @returns The user's unique identifier
     */
    public getId(): string {
        return this.id;
    }

    /**
     * Gets the user's first name.
     * @returns The user's first name
     */
    public getFirstName(): string {
        return this.firstName;
    }

    /**
     * Gets the user's last name.
     * @returns The user's last name
     */
    public getLastName(): string {
        return this.lastName;
    }

    /**
     * Gets the user's username.
     * @returns The user's username
     */
    public getUsername(): string {
        return this.username;
    }

    /**
     * Gets the user's email address.
     * @returns The user's email address
     */
    public getEmail(): string {
        return this.email;
    }

    /**
     * Checks if the user's account has been verified.
     * @returns True if the account is verified, false otherwise
     */
    public isAccountVerified(): boolean {
        return this.accountVerified;
    }

    /**
     * Checks if the user has accepted the terms of service.
     * @returns True if the terms have been accepted, false otherwise
     */
    public isTermsAccepted(): boolean {
        return this.termsAccepted;
    }

    /**
     * Gets the user's group identifier.
     * @returns The user's group identifier
     */
    public getGroupId(): number {
        return this.groupId;
    }

    /**
     * Gets the user's author/pen name.
     * @returns The user's author name
     */
    public getAuthorName(): string {
        return this.authorName;
    }
}
