import User from "./User";
import System, {UserKey} from "./System";
import CharacterRepo, {AttributeResult, CharacterResult, CompleteCharacterResult} from "../repositories/character.repo";

export type CharacterCategory = 'Main' | 'Secondary' | 'Recurring';

export interface CharacterPropsPost {
    id: number | null;
    name: string;
    lastName: string;
    category: CharacterCategory;
    title: string;
    image: string;
    physical: { name: string }[];
    psychological: { name: string }[];
    relations: { name: string }[];
    skills: { name: string }[];
    weaknesses: { name: string }[];
    strengths: { name: string }[];
    goals: { name: string }[];
    motivations: { name: string }[];
    role: string;
    biography?: string;
    history?: string;
}


export interface CharacterProps {
    id: string;
    name: string;
    lastName: string;
    title: string;
    category: string;
    image: string;
    role: string;
    biography: string;
    history: string;
}

export interface CompleteCharacterProps {
    id?: string;
    name: string;
    lastName: string;
    title: string;
    category: string;
    image?: string;
    role: string;
    biography: string;
    history: string;
    [key: string]: Attribute[] | string | undefined;
}

export interface Attribute {
    id: string;
    name: string;
}


export interface CharacterAttribute {
    type: string;
    values: Attribute[];
}

export default class Character{
    public static async getCharacterList(userId: string, bookId: string): Promise<CharacterProps[]> {
        const user = new User(userId);
        const keys:UserKey[] = await System.getAllUserKeysAndVersions(userId);
        const characters: CharacterResult[] = await CharacterRepo.fetchCharacters(userId, bookId);
        if (!characters) return [];
        if (characters.length === 0) return [];
        const characterList:CharacterProps[] = [];
        for (const character of characters) {
            const userKey:string = await user.getUserKey(character.char_meta,true,keys);
            characterList.push({
                id:character.character_id,
                name:character.first_name ? System.decryptDataWithUserKey(character.first_name,userKey) : '',
                lastName: character.last_name ? System.decryptDataWithUserKey(character.last_name, userKey) : '',
                title: character.title ? System.decryptDataWithUserKey(character.title, userKey) : '',
                category: character.category ? System.decryptDataWithUserKey(character.category, userKey) : '',
                image: character.image ? System.decryptDataWithUserKey(character.image, userKey) : '',
                role: character.role ? System.decryptDataWithUserKey(character.role, userKey) : '',
                biography: character.biography ? System.decryptDataWithUserKey(character.biography, userKey) : '',
                history: character.history ? System.decryptDataWithUserKey(character.history, userKey) : '',
            })
        }
        return characterList;
    }
    
    public static async addNewCharacter(userId: string, character: CharacterPropsPost, bookId: string): Promise<string> {
        const user = new User(userId);
        const meta:string = System.encryptDateKey(userId);
        const userKey:string = await user.getUserKey(meta,true);
        const characterId: string = System.createUniqueId();
        const encryptedName: string = System.encryptDataWithUserKey(character.name, userKey);
        const encryptedLastName: string = System.encryptDataWithUserKey(character.lastName, userKey);
        const encryptedTitle: string = System.encryptDataWithUserKey(character.title, userKey);
        const encryptedCategory: string = System.encryptDataWithUserKey(character.category, userKey);
        const encryptedImage: string = System.encryptDataWithUserKey(character.image, userKey);
        const encryptedRole: string = System.encryptDataWithUserKey(character.role, userKey);
        const encryptedBiography: string = System.encryptDataWithUserKey(character.biography ? character.biography : '', userKey);
        const encryptedHistory: string = System.encryptDataWithUserKey(character.history ? character.history : '', userKey);
        await CharacterRepo.addNewCharacter(userId, characterId, encryptedName, encryptedLastName, encryptedTitle, encryptedCategory, encryptedImage, encryptedRole, encryptedBiography, encryptedHistory, bookId, meta);
        const attributes: string[] = Object.keys(character);
        for (const key of attributes) {
            if (Array.isArray(character[key as keyof CharacterPropsPost])) {
                const array = character[key as keyof CharacterPropsPost] as { name: string }[];
                if (array.length > 0) {
                    for (const item of array) {
                        const type: string = key;
                        const name: string = item.name;
                        await this.addNewAttribute(characterId, userId, type, name);
                    }
                }
            }
        }
        return characterId;
    }
    
    static async updateCharacter(userId: string, character: CharacterPropsPost): Promise<boolean> {
        const user = new User(userId);
        const meta: string = System.encryptDateKey(userId);
        const userKey: string = await user.getUserKey(meta, true);
        const encryptedName: string = System.encryptDataWithUserKey(character.name, userKey);
        const encryptedLastName: string = System.encryptDataWithUserKey(character.lastName, userKey);
        const encryptedTitle: string = System.encryptDataWithUserKey(character.title, userKey);
        const encryptedCategory: string = System.encryptDataWithUserKey(character.category, userKey);
        const encryptedImage: string = System.encryptDataWithUserKey(character.image, userKey);
        const encryptedRole: string = System.encryptDataWithUserKey(character.role, userKey);
        const encryptedBiography: string = System.encryptDataWithUserKey(character.biography ? character.biography : '', userKey);
        const encryptedHistory: string = System.encryptDataWithUserKey(character.history ? character.history : '', userKey);
        return await CharacterRepo.updateCharacter(userId, character.id, encryptedName, encryptedLastName, encryptedTitle, encryptedCategory, encryptedImage, encryptedRole, encryptedBiography, encryptedHistory, meta);
    }
    
    static async addNewAttribute(characterId: string, userId: string, type: string, name: string): Promise<string> {
        const user = new User(userId);
        const meta: string = System.encryptDateKey(userId);
        const userKey: string = await user.getUserKey(meta, true);
        const attributeId: string = System.createUniqueId();
        const encryptedType: string = System.encryptDataWithUserKey(type, userKey);
        const encryptedName: string = System.encryptDataWithUserKey(name, userKey);
        return await CharacterRepo.insertAttribute(attributeId, characterId, userId, encryptedType, encryptedName, meta);
    }

    static async deleteAttribute(userId: string, attributeId: string) {
        return await CharacterRepo.deleteAttribute(userId, attributeId);
    }
    
    static async getAttributes(characterId: string, userId: string): Promise<CharacterAttribute[]> {
        const user: User = new User(userId);
        const keys: UserKey[] = await System.getAllUserKeysAndVersions(userId);
        const attributes: AttributeResult[] = await CharacterRepo.fetchAttributes(characterId, userId);
        if (!attributes?.length) return [];
        
        const groupedMap: Map<string, Attribute[]> = new Map<string, Attribute[]>();

        for (const attribute of attributes) {
            const userKey: string = await user.getUserKey(attribute.attr_meta, true, keys);
            const type: string = System.decryptDataWithUserKey(attribute.attribute_name, userKey);
            const value: string = attribute.attribute_value ? System.decryptDataWithUserKey(attribute.attribute_value, userKey) : '';
            
            if (!groupedMap.has(type)) {
                groupedMap.set(type, []);
            }
            
            groupedMap.get(type)!.push({
                id: attribute.attr_id,
                name: value
            });
        }
        
        return Array.from<[string, Attribute[]], CharacterAttribute>(
            groupedMap,
            ([type, values]: [string, Attribute[]]): CharacterAttribute => ({type, values})
        );
    }
    
    static async getCompleteCharacterList(userId: string, bookId: string, characters: string[]): Promise<CompleteCharacterProps[]> {
        const characterList: CompleteCharacterResult[] = await CharacterRepo.fetchCompleteCharacters(userId, bookId, characters);
        
        if (!characterList || characterList.length === 0) {
            return [];
        }
        
        const user = new User(userId);
        const keys: UserKey[] = await System.getAllUserKeysAndVersions(userId);
        const completeCharactersMap = new Map<string, CompleteCharacterProps>();
        for (const character of characterList) {
            if (!character.character_id) {
                continue;
            }
            
            if (!completeCharactersMap.has(character.character_id)) {
                const userKey: string = await user.getUserKey(character.char_meta, true, keys);
                if (!userKey) {
                    continue;
                }
                const personnageObj: CompleteCharacterProps = {
                    id: '',
                    name: character.first_name ? System.decryptDataWithUserKey(character.first_name, userKey) : '',
                    lastName: character.last_name ? System.decryptDataWithUserKey(character.last_name, userKey) : '',
                    title: character.title ? System.decryptDataWithUserKey(character.title, userKey) : '',
                    category: character.category ? System.decryptDataWithUserKey(character.category, userKey) : '',
                    role: character.role ? System.decryptDataWithUserKey(character.role, userKey) : '',
                    biography: character.biography ? System.decryptDataWithUserKey(character.biography, userKey) : '',
                    history: character.history ? System.decryptDataWithUserKey(character.history, userKey) : '',
                    physical: [],
                    psychological: [],
                    relations: [],
                    skills: [],
                    weaknesses: [],
                    strengths: [],
                    goals: [],
                    motivations: []
                };
                completeCharactersMap.set(character.character_id, personnageObj);
            }
            
            const personnage: CompleteCharacterProps | undefined = completeCharactersMap.get(character.character_id);
            
            if (!character.attr_meta) {
                continue;
            }
            
            const rowKey: string = await user.getUserKey(character.attr_meta, true, keys);
            
            if (!personnage || !rowKey) {
                continue;
            }
            const decryptedName: string = System.decryptDataWithUserKey(character.attribute_name, rowKey);
            const decryptedValue: string = character.attribute_value ? System.decryptDataWithUserKey(character.attribute_value, rowKey) : '';
            
            if (Array.isArray(personnage[decryptedName])) {
                personnage[decryptedName].push({
                    id: '',
                    name: decryptedValue
                });
            }
        }
        return Array.from(completeCharactersMap.values());
    }
    
    static characterVCard(characters: CompleteCharacterProps[]): string {
        const charactersMap = new Map<string, CompleteCharacterProps>();
        let charactersDescription: string = '';
        
        characters.forEach((character: CompleteCharacterProps): void => {
            const characterKey: string = character.name || character.id || 'unknown';
            
            if (!charactersMap.has(characterKey)) {
                charactersMap.set(characterKey, {
                    name: character.name,
                    lastName: character.lastName,
                    category: character.category,
                    title: character.title,
                    role: character.role,
                    biography: character.biography,
                    history: character.history
                });
            }
            
            const characterData: CompleteCharacterProps = charactersMap.get(characterKey)!;
            
            Object.keys(character).forEach((fieldName: string): void => {
                if (Array.isArray(character[fieldName])) {
                    if (!characterData[fieldName]) characterData[fieldName] = [];
                    (characterData[fieldName] as Attribute[]).push(...(character[fieldName] as Attribute[]));
                }
            });
        });
        
        charactersDescription = Array.from(charactersMap.values()).map((character: CompleteCharacterProps): string => {
            const descriptionFields: string[] = [];
            const fullName: string = [character.name, character.lastName].filter(Boolean).join(' ');
            if (fullName) descriptionFields.push(`Nom : ${fullName}`);
            
            (['category', 'title', 'role', 'biography', 'history'] as const).forEach((propertyKey) => {
                if (character[propertyKey]) {
                    descriptionFields.push(`${propertyKey.charAt(0).toUpperCase() + propertyKey.slice(1)} : ${character[propertyKey]}`);
                }
            });
            
            Object.keys(character).forEach((propertyKey: string): void => {
                const propertyValue: string | Attribute[] | undefined = character[propertyKey];
                if (Array.isArray(propertyValue) && propertyValue.length > 0) {
                    const capitalizedPropertyKey: string = propertyKey.charAt(0).toUpperCase() + propertyKey.slice(1);
                    const formattedValues: string = propertyValue.map((item: Attribute) => item.name).join(', ');
                    descriptionFields.push(`${capitalizedPropertyKey} : ${formattedValues}`);
                }
            });
            
            return descriptionFields.join('\n');
        }).join('\n\n');
        return charactersDescription;
    }
}
