import { getUserEncryptionKey } from "../keyManager.js";
import System from "../System.js";
import SeriesSpellRepo, { SeriesSpellResult, SeriesSpellTagResult } from "../repositories/series-spell.repo.js";
import RemovedItem from "./RemovedItem.js";

export interface SeriesSpellTagProps {
    id: string;
    name: string;
    color: string | null;
}

export interface SeriesSpellListProps {
    id: string;
    name: string;
    description: string;
    tags: string[];
}

export interface SeriesSpellListResponse {
    spells: SeriesSpellListProps[];
    tags: SeriesSpellTagProps[];
}

export interface SeriesSpellDetailProps {
    id: string;
    name: string;
    description: string;
    appearance: string;
    tags: string[];
    powerLevel: string | null;
    components: string | null;
    limitations: string | null;
    notes: string | null;
}

export default class SeriesSpell {
    /**
     * Retrieves all spells and tags for a series.
     * @param userId - The unique identifier of the user
     * @param seriesId - The unique identifier of the series
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns The list of spells and tags
     */
    public static getSpellList(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesSpellListResponse {
        const userKey: string = getUserEncryptionKey(userId);
        const spellsResult: SeriesSpellResult[] = SeriesSpellRepo.fetchSpells(userId, seriesId, lang);
        const tagsResult: SeriesSpellTagResult[] = SeriesSpellRepo.fetchTags(userId, seriesId, lang);

        const spells: SeriesSpellListProps[] = spellsResult.map((spell): SeriesSpellListProps => ({
            id: spell.spell_id,
            name: spell.name ? System.decryptDataWithUserKey(spell.name, userKey) : '',
            description: spell.description ? System.decryptDataWithUserKey(spell.description, userKey) : '',
            tags: spell.tags ? JSON.parse(System.decryptDataWithUserKey(spell.tags, userKey)) : []
        }));

        const tags: SeriesSpellTagProps[] = tagsResult.map((tag): SeriesSpellTagProps => ({
            id: tag.tag_id,
            name: tag.name ? System.decryptDataWithUserKey(tag.name, userKey) : '',
            color: tag.color
        }));

        return { spells, tags };
    }

    /**
     * Retrieves the details of a specific spell.
     * @param userId - The unique identifier of the user
     * @param spellId - The unique identifier of the spell
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns The spell details
     */
    public static getSpellDetail(userId: string, spellId: string, lang: 'fr' | 'en' = 'fr'): SeriesSpellDetailProps {
        const userKey: string = getUserEncryptionKey(userId);
        const spell: SeriesSpellResult | null = SeriesSpellRepo.fetchSpellById(userId, spellId, lang);

        if (!spell) {
            throw new Error(lang === 'fr' ? 'Sort non trouvé.' : 'Spell not found.');
        }

        return {
            id: spell.spell_id,
            name: spell.name ? System.decryptDataWithUserKey(spell.name, userKey) : '',
            description: spell.description ? System.decryptDataWithUserKey(spell.description, userKey) : '',
            appearance: spell.appearance ? System.decryptDataWithUserKey(spell.appearance, userKey) : '',
            tags: spell.tags ? JSON.parse(System.decryptDataWithUserKey(spell.tags, userKey)) : [],
            powerLevel: spell.power_level ? System.decryptDataWithUserKey(spell.power_level, userKey) : null,
            components: spell.components ? System.decryptDataWithUserKey(spell.components, userKey) : null,
            limitations: spell.limitations ? System.decryptDataWithUserKey(spell.limitations, userKey) : null,
            notes: spell.notes ? System.decryptDataWithUserKey(spell.notes, userKey) : null
        };
    }

    /**
     * Adds a new spell to a series.
     * @param userId - The unique identifier of the user
     * @param seriesId - The unique identifier of the series
     * @param name - The spell name
     * @param lang - The language for error messages ('fr' or 'en')
     * @param description - The spell description
     * @param appearance - The spell appearance
     * @param tags - The spell tags
     * @param powerLevel - The spell power level
     * @param components - The spell components
     * @param limitations - The spell limitations
     * @param notes - The spell notes
     * @returns The new spell ID
     */
    public static addSpell(userId: string, seriesId: string, name: string, lang: 'fr' | 'en' = 'fr', description?: string | null, appearance?: string | null, tags?: string[], powerLevel?: string | null, components?: string | null, limitations?: string | null, notes?: string | null): string {
        const userKey: string = getUserEncryptionKey(userId);
        const spellId: string = System.createUniqueId();
        const encryptedName: string = System.encryptDataWithUserKey(name, userKey);
        const nameHash: string = System.hashElement(name);
        const encryptedDescription: string = description ? System.encryptDataWithUserKey(description, userKey) : '';
        const encryptedAppearance: string = appearance ? System.encryptDataWithUserKey(appearance, userKey) : '';
        const encryptedTags: string = System.encryptDataWithUserKey(JSON.stringify(tags || []), userKey);
        const encryptedPowerLevel: string | null = powerLevel ? System.encryptDataWithUserKey(powerLevel, userKey) : null;
        const encryptedComponents: string | null = components ? System.encryptDataWithUserKey(components, userKey) : null;
        const encryptedLimitations: string | null = limitations ? System.encryptDataWithUserKey(limitations, userKey) : null;
        const encryptedNotes: string | null = notes ? System.encryptDataWithUserKey(notes, userKey) : null;

        SeriesSpellRepo.insertSpell(spellId, seriesId, userId, encryptedName, nameHash, encryptedDescription, encryptedAppearance, encryptedTags, encryptedPowerLevel, encryptedComponents, encryptedLimitations, encryptedNotes, lang);
        return spellId;
    }

    /**
     * Updates an existing spell.
     * @param userId - The unique identifier of the user
     * @param spellId - The unique identifier of the spell
     * @param name - The spell name
     * @param lang - The language for error messages ('fr' or 'en')
     * @param description - The spell description
     * @param appearance - The spell appearance
     * @param tags - The spell tags
     * @param powerLevel - The spell power level
     * @param components - The spell components
     * @param limitations - The spell limitations
     * @param notes - The spell notes
     * @returns True if successful
     */
    public static updateSpell(userId: string, spellId: string, name: string, lang: 'fr' | 'en' = 'fr', description?: string | null, appearance?: string | null, tags?: string[], powerLevel?: string | null, components?: string | null, limitations?: string | null, notes?: string | null): boolean {
        const userKey: string = getUserEncryptionKey(userId);
        const encryptedName: string = System.encryptDataWithUserKey(name, userKey);
        const nameHash: string = System.hashElement(name);
        const encryptedDescription: string = description ? System.encryptDataWithUserKey(description, userKey) : '';
        const encryptedAppearance: string = appearance ? System.encryptDataWithUserKey(appearance, userKey) : '';
        const encryptedTags: string = System.encryptDataWithUserKey(JSON.stringify(tags || []), userKey);
        const encryptedPowerLevel: string | null = powerLevel ? System.encryptDataWithUserKey(powerLevel, userKey) : null;
        const encryptedComponents: string | null = components ? System.encryptDataWithUserKey(components, userKey) : null;
        const encryptedLimitations: string | null = limitations ? System.encryptDataWithUserKey(limitations, userKey) : null;
        const encryptedNotes: string | null = notes ? System.encryptDataWithUserKey(notes, userKey) : null;

        return SeriesSpellRepo.updateSpell(userId, spellId, encryptedName, nameHash, encryptedDescription, encryptedAppearance, encryptedTags, encryptedPowerLevel, encryptedComponents, encryptedLimitations, encryptedNotes, lang);
    }

    /**
     * Deletes a spell.
     * @param userId - The unique identifier of the user
     * @param spellId - The unique identifier of the spell
     * @param deletedAt - The timestamp of deletion
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns True if successful
     */
    public static deleteSpell(userId: string, spellId: string, deletedAt: number = System.timeStampInSeconds(), lang: 'fr' | 'en' = 'fr'): boolean {
        const deleted: boolean = SeriesSpellRepo.deleteSpell(userId, spellId, lang);
        if (deleted) {
            RemovedItem.deleteTracker(userId, null, 'series_spells', spellId, deletedAt, lang);
        }
        return deleted;
    }

    /**
     * Adds a new tag to a series.
     * @param userId - The unique identifier of the user
     * @param seriesId - The unique identifier of the series
     * @param name - The name of the tag
     * @param lang - The language for error messages ('fr' or 'en')
     * @param color - The color of the tag (optional)
     * @returns The new tag ID
     */
    public static addTag(userId: string, seriesId: string, name: string, lang: 'fr' | 'en' = 'fr', color?: string | null): string {
        const userKey: string = getUserEncryptionKey(userId);
        const tagId: string = System.createUniqueId();
        const encryptedName: string = System.encryptDataWithUserKey(name, userKey);
        const hashedName: string = System.hashElement(name);

        SeriesSpellRepo.insertTag(tagId, seriesId, userId, encryptedName, hashedName, color || null, lang);
        return tagId;
    }

    /**
     * Updates an existing tag.
     * @param userId - The unique identifier of the user
     * @param tagId - The unique identifier of the tag
     * @param name - The new name of the tag
     * @param lang - The language for error messages ('fr' or 'en')
     * @param color - The new color of the tag (optional)
     * @returns True if successful
     */
    public static updateTag(userId: string, tagId: string, name: string, lang: 'fr' | 'en' = 'fr', color?: string | null): boolean {
        const userKey: string = getUserEncryptionKey(userId);
        const encryptedName: string = System.encryptDataWithUserKey(name, userKey);
        const hashedName: string = System.hashElement(name);

        return SeriesSpellRepo.updateTag(userId, tagId, encryptedName, hashedName, color || null, lang);
    }

    /**
     * Deletes a tag.
     * @param userId - The unique identifier of the user
     * @param tagId - The unique identifier of the tag
     * @param deletedAt - The timestamp of deletion
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns True if successful
     */
    public static deleteTag(userId: string, tagId: string, deletedAt: number = System.timeStampInSeconds(), lang: 'fr' | 'en' = 'fr'): boolean {
        const deleted: boolean = SeriesSpellRepo.deleteTag(userId, tagId, lang);
        if (deleted) {
            RemovedItem.deleteTracker(userId, null, 'series_spell_tags', tagId, deletedAt, lang);
        }
        return deleted;
    }
}
