import UserRepo, {
    BasicUserCredentials,
    GuideTourResult,
    PasswordResponse,
    TOTPQuery,
    UserAccountQuery,
    UserAiUsageResult,
    UserAPIKeyResult,
    UserInfosQueryResponse,
    UserQueryResponse,
    UserSubscription
} from "../repositories/user.repo";
import {FetchQueryResponse} from "../../config/SharedInterface";
import System, {EncryptedKey, EncryptedUserKey, UserKey} from "./System";
import {FastifyInstance} from "fastify";
import EmailTemplate from "./EmailTemplate";
import path from "path";
import fs from "fs";
import {Secret, TOTP} from "otpauth";
import Book, {BookProps} from "./Book";
import {JWT} from "@fastify/jwt";
import {AIBrand} from "./AI";
import {ConflictError, NotFoundError} from "../error";
import {getLanguage} from "../context";
import Subscription, { SubscriptionInfo } from "./Subscription";

export type UserLanguage = 'fr' | 'en';

export interface LoginData{
    valid:boolean;
    token?:string;
    id?:string;
    message?:string;
}

export interface UserAPIKey {
    brand: AIBrand,
    key: string;
}

interface UserAccount{
    firstName:string;
    lastName:string;
    username:string
    authorName:string;
    email:string;
}

export interface TOTPData{
    totpCode:string;
}

export interface GuideTour {
    [key: string]: boolean;
}

export interface UserSubscriptionProps {
    userId: string;
    subType: string;
    subTier: number;
    startDate: string;
    endDate: string;
    status: number;
}

interface UserSubscriptionInfo {
    subType: string;
    subTier: number;
    status: boolean;
}

export default class User{

    private readonly id:string;
    private firstName: string;
    private lastName: string;
    private username: string;
    private email: string;
    private platform: string;
    private accountVerified: boolean;
    private authorName: string;
    private writingLang: number;
    private writingLevel: number;
    private ritePoints: number;
    private groupId: number;
    private balancedCredits: number;
    private termsAccepted: boolean;

    constructor(id:string){
        this.id = id;
        this.firstName = '';
        this.lastName = '';
        this.username = '';
        this.email = '';
        this.platform = '';
        this.accountVerified = false;
        this.authorName = '';
        this.writingLang = 0;
        this.writingLevel = 1;
        this.ritePoints = 0;
        this.groupId = 0;
        this.balancedCredits = 0;
        this.termsAccepted = false;
    }
    
    public async getUserInfos(): Promise<void> {
        const data: UserInfosQueryResponse = await UserRepo.fetchUserInfos(this.id);
        const userKey: string = await this.getUserKey(data.user_meta);
        this.firstName = System.decryptDataWithUserKey(data.first_name, userKey);
        this.lastName = System.decryptDataWithUserKey(data.last_name, userKey);
        this.username = System.decryptDataWithUserKey(data.username, userKey);
        this.email = System.decryptDataWithUserKey(data.email, userKey);
        this.platform = data.plateform;
        this.accountVerified = data.account_verified === 1;
        this.authorName = data.author_name ? System.decryptDataWithUserKey(data.author_name, userKey) : '';
        this.writingLang = data.writing_lang ? data.writing_lang : 1;
        this.writingLevel = data.writing_level ? data.writing_level : 1;
        this.groupId = data.user_group ? data.user_group : 0;
        this.ritePoints = data.rite_points ? data.rite_points : 0;
        this.balancedCredits = data.credits_balance ? data.credits_balance : 0;
        this.termsAccepted = data.term_accepted === 1;
    }
    
    public static async returnUserInfos(userId: string, plateforme: string) {
        const user: User = new User(userId);
        await user.getUserInfos();
        const books: BookProps[] = await Book.getBooks(userId);
        const guideTourResult: GuideTourResult[] = await UserRepo.fetchGuideTour(userId, plateforme);
        const guideTour: GuideTour[] = [];
        const requestSubscription: UserSubscriptionProps[] = await User.getSubscription(userId);
        const userSubscriptions:SubscriptionInfo = await Subscription.getSubscriptionInfo(userId);
        const requestKeys: UserAPIKey[] = await User.getAPIKey(userId);
        let moneySpentThisMonth: number = 0;
        if (requestKeys.length > 0) {
            moneySpentThisMonth = await User.moneySpentThisMonth(userId)
        }
        const subscriptions: UserSubscriptionInfo[] = [];
        for (const userSubscriptionProp of requestSubscription) {
            subscriptions.push({
                subType: userSubscriptionProp.subType,
                subTier: userSubscriptionProp.subTier,
                status: userSubscriptionProp.status === 1
            });
        }

        if (guideTourResult) {
            guideTourResult.forEach((tour: GuideTourResult): void => {
                guideTour.push({
                    [tour.step_tour]: true
                });
            });
        }
        return {
            id: user.getId(),
            name: user.getFirstName(),
            lastName: user.getLastName(),
            username: user.getUsername(),
            email: user.getEmail(),
            accountVerified: user.isAccountVerified(),
            authorName: user.getAuthorName(),
            writingLang: user.getWritingLang(),
            writingLevel: user.getWritingLevel(),
            ritePoints: user.getRitePoints(),
            groupId: user.getGroupId(),
            termsAccepted: user.isTermsAccepted(),
            guideTour: guideTour,
            subscription: subscriptions,
            aiUsage: moneySpentThisMonth,
            creditsBalance: userSubscriptions.totalCredits,
            apiKeys: {
                gemini: !!requestKeys.find((key: UserAPIKey): boolean => {
                    return key.brand === 'gemini'
                }),
                openai: !!requestKeys.find((key: UserAPIKey): boolean => {
                    return key.brand === 'openai'
                }),
                anthropic: !!requestKeys.find((key: UserAPIKey): boolean => {
                    return key.brand === 'anthropic'
                }),
            },
            books: books.map((book: BookProps) => {
                return {
                    bookId: book.id,
                    title: book.title,
                    subTitle: book.subTitle,
                };
            })
        }
    }
    
    public static async isTwoFactorEnabled(userId: string): Promise<boolean> {
        return UserRepo.isTwoFactorEnabled(userId);
    }
    
    public static async changePassword(userId: string, password: string, newPassword: string, email: string): Promise<boolean> {
        if (await User.validUser(email, password)) {
            const newPasswordHash: string = await System.hashPassword(newPassword);
            return await UserRepo.updatePassword(userId, System.hashElement(email), newPasswordHash);
        } else {
            throw new Error('Your password is incorrect');
        }
    }
    
    public static async deleteAccount(userId: string, email: string): Promise<boolean> {
        return UserRepo.deleteAccount(userId, System.hashElement(email));
    }
    
    public static async resetPassword(newPassword: string, email: string, verifyCode: string): Promise<boolean> {
        const encryptPassword: string = await System.hashPassword(newPassword);
        const user: PasswordResponse = await UserRepo.verifyUserAuth(System.hashElement(email), verifyCode)
        const userId: string = user.user_id;
        return await UserRepo.updatePassword(userId, System.hashElement(email), encryptPassword);
    }
    
    public static async verifyCode(verifyCode: string, email: string): Promise<string> {
        return await UserRepo.fetchVerifyCode(System.hashElement(email), verifyCode);
    }
    
    static async subscribeToBeta(email: string): Promise<boolean> {
        let checkEmail: string[] = email.split('@');
        checkEmail = checkEmail[1].split('.');
        if (checkEmail[0] === 'gmail' || checkEmail[0] === 'hotmail' || checkEmail[0] === 'outlook') {
            if (await UserRepo.checkBetaSubscriber(email)) {
                throw new Error(`L'adresse courriel est déjà enregistré.`);
            }
            const insertEmail: boolean = await UserRepo.insertBetaSubscriber(email);
            if (insertEmail) {
                System.sendEmailTo(email, 'Inscription à la bêta.', '', EmailTemplate('newsletter', {}));
                return insertEmail;
            } else {
                throw new Error('Une erreur est survenue lors de l\'inscription à la bêta.');
            }
        } else {
            throw new Error('Votre adresse email n\'est pas valide.');
        }
    }
    
    public static async addUser(firstName: string, lastName: string, username: string, email: string, notEncryptPassword: string, userId: string | null = null, provider: string = 'credential', socialId: string | null = null, tempAdd: boolean = false): Promise<string> {
        const originEmail:string = System.hashElement(email);
        const originUsername:string = System.hashElement(username);
        await this.checkUser(originUsername, originEmail);
        const lang: "fr" | "en" = getLanguage()
        if (tempAdd) {
            const password:string = notEncryptPassword ? await System.hashPassword(notEncryptPassword) : '';
            const newUserId: string = System.createUniqueId();
            await UserRepo.insertTempUser(newUserId, firstName, lastName, username, originUsername, email, originEmail, password);
            await User.sendVerifyCode(username, email);
            return newUserId;
        } else {
            if (!userId) {
                throw new Error(lang === 'fr' ? 'L\'identifiant utilisateur est requis.' : 'User ID is required.');
            }
            const encryptData: EncryptedUserKey = await System.generateAndEncryptUserKey(userId);
            const userKey: string = encryptData.userKey;
            const encryptFirstName: string = System.encryptDataWithUserKey(firstName, userKey);
            const encryptLastName: string = System.encryptDataWithUserKey(lastName, userKey);
            const encryptUsername: string = System.encryptDataWithUserKey(username, userKey);
            const encryptEmail: string = System.encryptDataWithUserKey(email, userKey);
            const originalEmail: string = System.hashElement(email);
            const originalUsername: string = System.hashElement(username);
            const meta: string = System.encryptDateKey(userId);
            const insertedUserId: string = await UserRepo.insertUser(userId, encryptFirstName, encryptLastName, encryptUsername, originalUsername, encryptEmail, originalEmail, notEncryptPassword, meta, socialId, provider);
            await System.storeUserKey(userId, encryptData.encryptedKey);
            await UserRepo.deleteTempUser(userId);
            return insertedUserId;
        }
    }
    
    public static async updateUserInfos(userKey: string, userId: string, firstName: string, lastName: string, username: string, email: string, authorName?: string): Promise<boolean> {
        const encryptFirstName:string = System.encryptDataWithUserKey(firstName,userKey);
        const encryptLastName:string = System.encryptDataWithUserKey(lastName,userKey);
        const encryptUsername:string = System.encryptDataWithUserKey(username,userKey);
        const encryptEmail:string = System.encryptDataWithUserKey(email,userKey);
        const originalEmail:string = System.hashElement(email);
        const originalUsername:string = System.hashElement(username);
        const userMeta:string = System.encryptDateKey(userId);
        let encryptAuthorName:string = '';
        let originalAuthorName: string = '';
        if (authorName){
            encryptAuthorName = System.encryptDataWithUserKey(authorName,userKey);
            originalAuthorName = System.hashElement(authorName);
        }
        return UserRepo.updateUserInfos(userId, encryptFirstName, encryptLastName, encryptUsername, originalUsername, encryptEmail, originalEmail, userMeta, originalAuthorName, encryptAuthorName);
    }
    public async getUserKey(metaKey:string,isNeedToBeDecrypt:boolean=true,keys?:UserKey[]):Promise<string>{
        let meta:string = '';
        if (isNeedToBeDecrypt){
            meta = System.decryptDateKey(this.id,metaKey);
        } else {
            meta = metaKey;
        }
        if (keys){
            return System.getClosestPreviousKey(keys,meta);
        } else {
            const userKey:FetchQueryResponse<EncryptedKey> = await System.getUserKey(this.id,meta);
            if (userKey.data){
                return userKey.data?.encrypted_key;
            } else {
                return '';
            }
        }
    }

    public static async checkUserName(username:string):Promise<boolean>{
        return UserRepo.fetchUserName(username);
    }

    public static async checkEmail(email:string):Promise<boolean>{
        return UserRepo.fetchEmail(email);
    }
    
    static getLevelDetail(level: number, lang: "fr" | "en"): string {
        if (lang === "fr") {
            switch (level) {
                case 1: // Débutant français
                    return `**Niveau : Débutant**
**Objectif** : Texte écrit par un élève de secondaire qui apprend encore à écrire en français.
**Lexique** :
- **Vocabulaire limité** : Répétition des mêmes mots simples, manque de synonymes.
- **Exemples typiques** : "il y a", "c'est", "très", "beaucoup", "faire", "aller", "dire".
- **Caractéristiques** : Anglicismes possibles, répétitions non voulues.
**Structure** :
- **Phrases courtes par nécessité** : 8-15 mots (le français tolère plus de mots).
- **Constructions simples** : Sujet-Verbe-Complément, quelques "qui/que" basiques.
- **Coordinations basiques** : "et", "mais", "alors", "après".
- **Erreurs typiques** : "Il y a des arbres qui sont grands" au lieu de "Les arbres sont grands".
**Style littéraire** :
- **Descriptions factuelle** : "La forêt était sombre et silencieuse."
- **Pas de figures de style** : Tentatives maladroites si présentes.
- **Français scolaire** : Formulations apprises par cœur.
**Exemples comparatifs** :
✅ "Marc a couru dans la forêt. Il avait très peur des bruits qu'il entendait."
❌ "Marc s'élança dans les profondeurs sylvestres, son cœur battant la chamade..."
`;
                
                case 2: // Intermédiaire français
                    return `**Niveau : Intermédiaire**
**Objectif** : Texte écrit par un étudiant de cégep/université qui expérimente le style français.
**Lexique** :
- **Vocabulaire enrichi** : Recherche de synonymes, mots plus soutenus.
- **Exemples** : "s'élancer", "contempler", "mystérieux", "murmurer", "lueur".
- **Tentatives stylistiques** : Parfois réussies, parfois précieuses.
**Structure** :
- **Phrases moyennes à longues** : 12-25 mots, avec subordinations.
- **Constructions élaborées** : Relatives multiples, quelques inversions.
- **Style français émergent** : Tentatives de périodes, de nuances.
- **Connecteurs plus variés** : "cependant", "néanmoins", "tandis que".
**Style littéraire** :
- **Premières métaphores** : Simples mais françaises ("tel un fantôme").
- **Descriptions étoffées** : Adjectifs et compléments circonstanciels.
- **Registre soutenu recherché** : Parfois forcé.
**Exemples comparatifs** :
✅ "Marc s'élança dans la forêt obscure, tandis que chaque bruissement mystérieux faisait naître en lui une crainte sourde."
❌ "Marc se précipita avec véhémence dans les méandres ténébreux de la sylve ancestrale..."
`;
                
                case 3: // Avancé français
                    return `**Niveau : Avancé**
**Objectif** : Texte écrit par un écrivain professionnel maîtrisant la rhétorique française.
**Lexique** :
- **Richesse lexicale maîtrisée** : Précision du mot juste, nuances subtiles.
- **Registres variés** : Du familier au soutenu selon l'effet recherché.
- **Synonymie élégante** : Évitement des répétitions par art, non par contrainte.
**Structure** :
- **Périodes françaises** : Phrases longues et rythmées (20-40 mots possibles).
- **Architecture complexe** : Subordinations enchâssées, incises, inversions maîtrisées.
- **Rythme classique** : Alternance entre brièveté saisissante et amplitude oratoire.
- **Ponctuation expressive** : Points-virgules, deux-points, parenthèses.
**Style littéraire** :
- **Figures maîtrisées** : Métaphores filées, antithèses, chiasmes.
- **Tradition française** : Élégance, clarté, harmonie des sonorités.
- **Sous-entendus et allusions** : Subtilité dans l'évocation.
**Exemples comparatifs** :
✅ "Marc courut." (Simplicité voulue)
✅ "Dans l'entrelacement des ombres que la lune, filtrant à travers la ramure, dessinait sur le sol moussu, Marc discernait les échos de sa propre terreur ; terreur ancestrale, née de ce dialogue éternel entre l'homme et la nuit."
`;
                default:
                    return ``;
            }
        } else { // English Canadian
            switch (level) {
                case 1: // Beginner English
                    return `**Level: Beginner**
**Target**: Text written by a high school student still learning English composition.
**Vocabulary**:
- **Limited word choice**: Repetition of basic words, simple alternatives.
- **Typical examples**: "said", "went", "got", "thing", "stuff", "really", "very".
- **Characteristics**: Overuse of "and then", simple connectors.
**Structure**:
- **Short, choppy sentences**: 5-10 words (English naturally shorter).
- **Basic constructions**: Subject-Verb-Object, simple coordination.
- **Limited variety**: Mostly declarative sentences.
- **Common errors**: Run-on sentences with "and", fragments.
**Literary style**:
- **Plain description**: "The forest was dark. It was scary."
- **No literary devices**: Attempts at metaphors usually failed.
- **Direct emotion**: "He was afraid" (English directness).
**Comparative examples**:
✅ "Mark ran into the forest. He was scared. The trees looked big and dark."
❌ "Mark ventured forth into the sylvan depths, his heart pounding with trepidation..."
`;
                
                case 2: // Intermediate English
                    return `**Level: Intermediate**
**Target**: Text written by a college student developing English writing skills.
**Vocabulary**:
- **Expanded vocabulary**: Some sophisticated words, better word choice.
- **Examples**: "ventured", "glimpsed", "mysterious", "whispered", "shadowy".
- **Characteristics**: Occasional overreach, some pretentious word choices.
**Structure**:
- **Varied sentence length**: 8-18 words, some complex sentences.
- **Better flow**: Proper use of conjunctions, some subordination.
- **Paragraph development**: Topic sentences, basic transitions.
- **English rhythm**: Shorter than French but with variation.
**Literary style**:
- **Simple imagery**: Basic metaphors and similes that work.
- **Show don't tell**: Attempts at indirect description.
- **English conciseness**: Getting to the point while adding style.
**Comparative examples**:
✅ "Mark plunged into the dark forest. Every sound made him jump, like whispers from invisible watchers."
❌ "Mark precipitously advanced into the tenebrous woodland whilst phantasmagorical emanations..."
`;
                
                case 3: // Advanced English
                    return `**Level: Advanced**
**Target**: Text written by a professional English-speaking author.
**Vocabulary**:
- **Precise word choice**: Economy of language, powerful verbs.
- **Understated elegance**: Sophisticated but not showy.
- **Anglo-Saxon vs. Latin**: Strategic use of both registers.
**Structure**:
- **Masterful variety**: From punchy fragments to flowing periods.
- **English rhythm**: Natural speech patterns, emphasis through structure.
- **Controlled complexity**: Never convoluted, always clear.
- **Strategic brevity**: Power in conciseness.
**Literary style**:
- **Subtle imagery**: Metaphors that illuminate, don't decorate.
- **Understated power**: English preference for implication over elaboration.
- **Voice and tone**: Distinctive style through restraint and precision.
- **Anglo tradition**: Clarity, wit, emotional resonance through simplicity.
**Comparative examples**:
✅ "Mark ran. The forest swallowed him." (English power in brevity)
✅ "The shadows moved between the trees like living things, and Mark felt something ancient watching from the darkness—something that had been waiting." (Controlled complexity)
`;
                default:
                    return ``;
            }
        }
    }
    
    static getAudienceDetail(level: number, lang: "fr" | "en"): string {
        if (lang === "fr") {
            switch (level) {
                case 1: // Débutant
                    return `**Niveau : Débutant**
    **Objectif** : Texte accessible à un collégien (18-22 ans).
    **Lexique** :
    - **Priorité absolue** : Mots du quotidien, fréquents dans la langue parlée.
    - **Exemples autorisés** : "marcher", "voir", "dire", "beau", "peur", "lumière".
    - **Mots à éviter** : "luminescence", "éthéré", "volutes", "chuchotements", "centenaires".
    **Structure** :
    - **Longueur moyenne maximale par phrase** : 10 mots.
    - **Interdit** : Phrases composées, subordonnées, ou parenthèses.
    - **Style des descriptions** : Directes et visuelles (ex: "La forêt était sombre" → pas de "La forêt respirait la nuit").
    **Style littéraire** :
    - **Interdits** : Métaphores, similes, ou figures de style.
    - **Priorité** : Clarté et simplicité absolue.
    **Exemples comparatifs** :
    ❌ "La brume enveloppait les arbres avec une grâce éthérée" → ✅ "La brume couvrait les arbres."
    ❌ "Elle courait, les jambes tremblantes, vers la lumière" → ✅ "Elle courait vers une lumière."
    `;
                
                case 2: // Intermédiaire
                    return `
    **Niveau : Intermédiaire**
    **Objectif** : Texte pour un étudiant universitaire (22-30 ans).
    **Lexique** :
    - **Priorité** : Vocabulaire varié mais compréhensible (ex: "murmurer", "luminescent", "mystère", "sentinelles").
    - **Mots à éviter** : Termes littéraires complexes (ex: "volutes", "centenaires", "éthéré").
    **Structure** :
    - **Longueur moyenne maximale par phrase** : 18 mots.
    - **Autorisé** : Phrases composées simples (ex: "Elle courait, mais la forêt était dense").
    - **Descriptions** : Métaphores basiques (ex: "La forêt respirait la nuit").
    **Style littéraire** :
    - **Autorisé** : Similes simples (ex: "comme un fantôme").
    - **Interdit** : Figures de style complexes (antithèses, anaphores).
    **Exemples comparatifs** :
    ❌ "Les arbres, semblables à des sentinelles de pierre, veillaient" → ✅ "Les arbres ressemblaient à des gardiens."
    ❌ "La lueur dansante évoquait un espoir éphémère" → ✅ "La lumière clignotait comme un espoir."
    `;
                
                case 3: // Avancé
                    return `
    **Niveau : Avancé**
    **Objectif** : Texte littéraire d'un professionnel littéraire, ayant un diplôme universitaire.
    **Lexique** :
    - **Priorité** : Vocabulaire riche et littéraire (ex: "volutes", "centenaires", "éthéré", "luminescence").
    - **Mots à éviter** : Termes simples jugés "banals" (ex: "marcher" → préférer "arpenter", "errer").
    **Structure** :
    - **Longueur maximale par phrase** : aucune limite.
    - **Autorisé** : Phrases complexes et enchaînées (ex: "Parmi les ombres dansantes, elle discerna une lueur vacillante, symbole de l'espoir qui s'évanouissait").
    - **Descriptions** : Métaphores poétiques et figures de style avancées.
    **Style littéraire** :
    - **Priorité** : Atmosphère immersive et langage élégant.
    - **Autorisé** : Antithèses, anaphores, et métaphores multi-niveaux.
    **Exemples comparatifs** :
    ✅ "Les volutes de brume, tels des spectres en quête d'écho, enveloppaient les troncs centenaires dans un ballet spectral."
    ✅ "Luminescence vacillante, espoir éphémère : les deux dansaient une dernière valse avant l'obscurité."
    `;
                default:
                    return ``;
            }
        } else { // English Canadian
            switch (level) {
                case 1: // Beginner (Grade 11/Secondary 5 level)
                    return `
    **Level: Beginner**
    **Target**: Text accessible to a Grade 11 student (Secondary 5 equivalent).
    **Vocabulary**:
    - **Absolute priority**: Common everyday words, frequent in spoken language.
    - **Allowed examples**: "walk", "see", "say", "nice", "scared", "light".
    - **Words to avoid**: "luminescence", "ethereal", "tendrils", "whispers", "ancient".
    **Structure**:
    - **Maximum average sentence length**: 10 words.
    - **Forbidden**: Compound sentences, subordinate clauses, or parentheses.
    - **Description style**: Direct and visual (e.g., "The forest was dark" → not "The forest breathed with darkness").
    **Literary style**:
    - **Forbidden**: Metaphors, similes, or figures of speech.
    - **Priority**: Absolute clarity and simplicity.
    **Comparative examples**:
    ❌ "The mist enveloped the trees with ethereal grace" → ✅ "The mist covered the trees."
    ❌ "She ran, her legs trembling, toward the light" → ✅ "She ran toward a light."
    `;
                
                case 2: // Intermediate (CEGEP/College level)
                    return `
    **Level: Intermediate**
    **Target**: Text for a CEGEP/College student.
    **Vocabulary**:
    - **Priority**: Varied but understandable vocabulary (e.g., "murmur", "luminous", "mystery", "sentinels").
    - **Words to avoid**: Complex literary terms (e.g., "tendrils", "ancient", "ethereal").
    **Structure**:
    - **Maximum average sentence length**: 18 words.
    - **Allowed**: Simple compound sentences (e.g., "She ran, but the forest was thick").
    - **Descriptions**: Basic metaphors (e.g., "The forest breathed in the night").
    **Literary style**:
    - **Allowed**: Simple similes (e.g., "like a ghost").
    - **Forbidden**: Complex figures of speech (antithesis, anaphora).
    **Comparative examples**:
    ❌ "The trees, like stone sentinels, stood watch" → ✅ "The trees looked like guardians."
    ❌ "The dancing light evoked fleeting hope" → ✅ "The light flickered like hope."
    `;
                
                case 3: // Advanced (University/Professional writer level)
                    return `
    **Level: Advanced**
    **Target**: Literary text from a professional writer or bestselling author with university education.
    **Vocabulary**:
    - **Priority**: Rich and literary vocabulary (e.g., "tendrils", "ancient", "ethereal", "luminescence").
    - **Words to avoid**: Simple terms deemed "mundane" (e.g., "walk" → prefer "traverse", "wander").
    **Structure**:
    - **Maximum sentence length**: No limit.
    - **Allowed**: Complex and chained sentences (e.g., "Among the dancing shadows, she discerned a flickering light, a symbol of hope that was fading away").
    - **Descriptions**: Poetic metaphors and advanced figures of speech.
    **Literary style**:
    - **Priority**: Immersive atmosphere and elegant language.
    - **Allowed**: Antithesis, anaphora, and multi-layered metaphors.
    **Comparative examples**:
    ✅ "Tendrils of mist, like spectres seeking echo, enveloped the ancient trunks in a ghostly ballet."
    ✅ "Flickering luminescence, fleeting hope: both danced a final waltz before darkness fell."
    `;
                default:
                    return ``;
            }
        }
    }
    
    public static async checkUser(username: string, email: string): Promise<void> {
        const validUsername:boolean = await User.checkUserName(username);
        const validEmail:boolean = await User.checkEmail(email);
        const lang: "fr" | "en" = getLanguage()
        if (validUsername){
            throw new ConflictError(lang === 'fr' ? "Le nom d'utilisateur est déjà utilisé." : 'Username is already taken.');
        }
        if (validEmail){
            throw new ConflictError(lang === 'fr' ? "L'adresse courriel est déjà utilisée." : 'Email address is already in use.');
        }
    }
    
    public static async login(email: string, password: string, jwtInstance: FastifyInstance): Promise<string> {
        const lang: "fr" | "en" = getLanguage()
        if (await User.validUser(email, password)) {
            const user: BasicUserCredentials = await UserRepo.fetchUserByCredentials(System.hashElement(email));
            
            return jwtInstance.jwt.sign({userId: user.user_id})
        } else {
            throw new NotFoundError(lang === 'fr' ? "Nom d'utilisateur ou mot de passe invalide." : "Invalid username or password.");
        }
    }
    
    public static async setUserAccountInformation(userId: string, authorName: string, username: string): Promise<boolean> {
        const user:User = new User(userId);
        await user.getUserInfos();
        const meta:string = System.encryptDateKey(userId);
        const userKey:string = await user.getUserKey(meta);
        return User.updateUserInfos(userKey, userId, user.firstName, user.lastName, username, user.getEmail(), authorName);
    }

    public static async validUser(email:string,password:string):Promise<boolean>{
        const validUser: PasswordResponse | null = await UserRepo.verifyUserAuth(System.hashElement(email));
        if (!validUser) {
            return false;
        }
        const stockedPassword: string = validUser.password;
        return System.verifyPassword(stockedPassword, password);
    }
    
    public static async socialLogin(firstName: string, lastName: string, email: string | null, socialId: string, provider: string, jwtInstance: JWT): Promise<string> {
        const validUserId: string | null = await UserRepo.fetchBySocialId(socialId);
        let userId: string = '';
        let newEmail: string = '';
        if (email === null) {
            newEmail = socialId + '@' + provider + '.com';
        } else {
            newEmail = email;
        }
        if (!validUserId) {
            const emailValidationUserId: string | null = await UserRepo.fetchByEmail(System.hashElement(newEmail));
            if (!emailValidationUserId) {
                const randomNum: number = Math.floor(Math.random() * 10000);
                const fourDigitNum: string = randomNum.toString().padStart(4, '0');
                const username: string = newEmail.split('@')[0] + fourDigitNum;
                userId = System.createUniqueId();
                await this.addUser(firstName, lastName, username, newEmail, "", userId, provider, socialId);
            } else {
                userId = emailValidationUserId;
                await UserRepo.updateSocialId(userId, socialId, provider);
            }
        } else {
            userId = validUserId;
        }
        
        return jwtInstance.sign({userId: userId})
    }
    
    public static async confirmAccount(verifyCode: string, email: string): Promise<boolean> {
        await UserRepo.fetchVerifyCode(System.hashElement(email), verifyCode);
        const account: UserQueryResponse = await UserRepo.fetchTempAccount(email, verifyCode);
        const userId: string = account.user_id;
        const firstName: string = account.first_name;
        const lastName: string = account.last_name;
        const username: string = account.username;
        const emailAddr: string = account.email;
        const password: string = account.password;
        const insertUser: string = await this.addUser(firstName, lastName, username, emailAddr, password, userId);
        const projectDirectory: string = path.join(process.cwd(), 'uploads', userId.toString(), 'cover');
        
        if (!insertUser) {
            throw new Error('Une erreur est survenue lors de la confirmation du compte.');
        }
        await fs.promises.mkdir(projectDirectory, {recursive: true})
            .catch(error => console.error(`Erreur lors de la création du dossier: ${error}`));
        return true;
    }
    
    public static async sendVerifyCode(username: string, email: string): Promise<void> {
        const verifyCode: string = System.createVerifyCode();
        const validUpdate: boolean = await UserRepo.updateVerifyCode(username, username === "" ? System.hashElement(email) : email, verifyCode);
        if (validUpdate) {
            const options = {code: verifyCode}
            System.sendEmailTo(email, 'Votre code de vérification', '', EmailTemplate('verify-code', options));
        } else {
            throw new Error('Une erreur est survenue lors de l\'envoi du code de vérification');
        }
    }
    public static validateTOTPCode(code: string, token: string): boolean {
        try {
            const totp = new TOTP({
                secret: Secret.fromBase32(code)
            });

            const isValid = totp.validate({
                token,
                window: 1
            });
            return isValid !== null;
        } catch (error) {
            return false;
        }
    }

    public static async checkTOTPExist(userId:string,email:string):Promise<boolean>{
        return UserRepo.checkTOTPExist(userId,email);
    }
    
    public static async handleTOTP(userId: string, email: string, verifyCode: string): Promise<boolean> {
        const user = new User(userId);
        const meta:string = System.encryptDateKey(userId);
        const userKey:string = await user.getUserKey(meta);
        const encryptedCode:string = System.encryptDataWithUserKey(verifyCode,userKey);
        const encryptedEmail:string = System.hashElement(email);
        return UserRepo.insertTOTP(encryptedCode,encryptedEmail,userId,meta,true);
    }
    
    public static async activateTOTP(userId: string, email: string, token: string): Promise<boolean> {
        const data: TOTPQuery = await UserRepo.fetchTempTOTP(userId, System.hashElement(email));
        const user = new User(userId);
        const meta: string = data.totp_meta;
        const userKey: string = await user.getUserKey(meta);
        const code: string = System.decryptDataWithUserKey(data.totp_code, userKey);
        if (User.validateTOTPCode(code, token)) {
            return UserRepo.insertTOTP(data.totp_code, data.email, userId, meta, false);
        } else {
            throw new Error('Your token is incorrect');
        }
    }
    
    public static async validateTOTP(userId: string, email: string, token: string): Promise<boolean> {
        const data: TOTPQuery = await UserRepo.fetchTOTP(userId, System.hashElement(email));
        const user = new User(userId);
        const meta: string = data.totp_meta;
        const userKey: string = await user.getUserKey(meta);
        const code: string = System.decryptDataWithUserKey(data.totp_code, userKey);
        if (User.validateTOTPCode(code, token)) {
            return true;
        } else {
            throw new Error('Your token is incorrect');
        }
    }
    
    public static async getUserAccountInformation(userId: string): Promise<UserAccount> {
        const user:User = new User(userId);
        const data: UserAccountQuery = await UserRepo.fetchAccountInformation(userId);
        const userKey: string = await user.getUserKey(data.user_meta);
        const userName: string = data.first_name ? System.decryptDataWithUserKey(data.first_name, userKey) : '';
        const lastName: string = data.last_name ? System.decryptDataWithUserKey(data.last_name, userKey) : '';
        const username: string = data.username ? System.decryptDataWithUserKey(data.username, userKey) : '';
        const authorName: string = data.author_name ? System.decryptDataWithUserKey(data.author_name, userKey) : '';
        const email: string = data.email ? System.decryptDataWithUserKey(data.email, userKey) : '';
        return {
            firstName: userName,
            lastName: lastName,
            username: username,
            authorName: authorName,
            email: email
        };
    }
    
    public static async getSubscription(userId: string): Promise<UserSubscriptionProps[]> {
        const result: UserSubscription[] = await UserRepo.fetchSubscription(userId);
        const userSubscription: UserSubscriptionProps[] = [];
        for (const sub of result) {
            userSubscription.push({
                userId: sub.user_id,
                subType: sub.sub_type,
                subTier: sub.sub_tier,
                startDate: sub.start_date,
                endDate: sub.end_date,
                status: sub.status
            })
        }
        return userSubscription;
    }

    public async convertTimeToPoint(time:number):Promise<void>{
        const points:number = Math.ceil(time/30);
        await UserRepo.updatePoints(points,this.id);
    }
    
    static async setUserPreferences(userId: string, writingLang: number, writingLevel: number): Promise<boolean | string> {
        if (writingLang === 0 || writingLevel === 0) {
            throw new Error('Veuillez choisir une langue et un niveau de rédaction valides.');
        }
        if (await UserRepo.isUserPreferencesExist(userId)) {
            return UserRepo.updateUserPreference(userId, writingLang, writingLevel)
        } else {
            const preferenceId: string = System.createUniqueId();
            return await UserRepo.insertUserPreference(preferenceId, userId, writingLang, writingLevel);
        }
    }
    
    static async acceptTerms(userId: string, version: string): Promise<boolean> {
        const log: boolean = await UserRepo.logAcceptTerms(userId, version);
        if (log) {
            return await UserRepo.updateTermsAccepted(userId);
        } else {
            return false;
        }
    }
    
    public getId(): string {
        return this.id;
    }
    
    public getFirstName(): string {
        return this.firstName;
    }
    
    public getLastName(): string {
        return this.lastName;
    }
    
    public getUsername(): string {
        return this.username;
    }
    
    public getEmail(): string {
        return this.email;
    }
    
    public isAccountVerified(): boolean {
        return this.accountVerified;
    }
    
    public getWritingLang(): number {
        return this.writingLang;
    }
    
    public getWritingLevel(): number {
        return this.writingLevel;
    }
    
    public getRitePoints(): number {
        return this.ritePoints;
    }
    
    public isTermsAccepted(): boolean {
        return this.termsAccepted;
    }
    
    public getGroupId(): number {
        return this.groupId;
    }
    
    public getBalancedCredits(): number {
        return this.balancedCredits;
    }
    
    public getAuthorName(): string {
        return this.authorName;
    }
    
    static async updateAIUserKey(userId: string, anthropicKey: string | null, openaiKey: string | null, geminiKey: string | null): Promise<boolean> {
        const user = new User(userId);
        const meta: string = System.encryptDateKey(userId);
        const userKey: string = await user.getUserKey(meta);
        const groupedKey: { brand: 'anthropic' | 'openai' | 'gemini', key: string }[] = [];
        const anthropicKeyEncrypted: string = anthropicKey ? System.encryptDataWithUserKey(anthropicKey, userKey) : '';
        groupedKey.push({brand: 'anthropic', key: anthropicKeyEncrypted});
        const openaiKeyEncrypted: string = openaiKey ? System.encryptDataWithUserKey(openaiKey, userKey) : '';
        groupedKey.push({brand: 'openai', key: openaiKeyEncrypted});
        const geminiKeyEncrypted: string = geminiKey ? System.encryptDataWithUserKey(geminiKey, userKey) : '';
        groupedKey.push({brand: 'gemini', key: geminiKeyEncrypted});
        for (const key of groupedKey) {
            const updateAI: boolean = await UserRepo.updateAIUserKey(userId, key.brand, key.key);
            if (!updateAI) {
                return false;
            }
        }
        return true;
    }
    
    static async getAPIKey(userId: string): Promise<UserAPIKey[]> {
        const user: User = new User(userId);
        const meta: string = System.encryptDateKey(userId);
        const userKey: string = await user.getUserKey(meta);
        const apiKeys: UserAPIKeyResult[] = await UserRepo.fetchAPIKey(userId);
        const decryptKeys: UserAPIKey[] = [];
        for (const apiKey of apiKeys) {
            if (apiKey.key && apiKey.key.length > 0) {
                decryptKeys.push({
                    brand: apiKey.brand,
                    key: System.decryptDataWithUserKey(apiKey.key, userKey)
                })
            }
        }
        return decryptKeys;
    }
    
    private static async moneySpentThisMonth(userId: string): Promise<number> {
        const {last, first} = System.getMonthBounds();
        const usage: UserAiUsageResult = await UserRepo.fetchUserAiUsage(userId, first, last);
        return usage.total_price;
    }
}
