import { getUserEncryptionKey } from "../keyManager.js";
import System from "../System.js";
import SeriesWorldRepo, { SeriesWorldResult } from "../repositories/series-world.repo.js";
import RemovedItem from "./RemovedItem.js";

export interface SeriesWorldElementProps {
    id: string;
    name: string;
    description: string;
}

export interface SeriesWorldListProps {
    id: string;
    name: string;
    history: string;
    politics: string;
    economy: string;
    religion: string;
    languages: string;
    laws: SeriesWorldElementProps[];
    biomes: SeriesWorldElementProps[];
    issues: SeriesWorldElementProps[];
    customs: SeriesWorldElementProps[];
    kingdoms: SeriesWorldElementProps[];
    climate: SeriesWorldElementProps[];
    resources: SeriesWorldElementProps[];
    wildlife: SeriesWorldElementProps[];
    arts: SeriesWorldElementProps[];
    ethnicGroups: SeriesWorldElementProps[];
    socialClasses: SeriesWorldElementProps[];
    importantCharacters: SeriesWorldElementProps[];
}

export interface SeriesWorldUpdateProps {
    name: string;
    history?: string;
    politics?: string;
    economy?: string;
    religion?: string;
    languages?: string;
}

const ELEMENT_TYPE_MAP: Record<number, keyof SeriesWorldListProps> = {
    0: 'laws',
    1: 'biomes',
    2: 'issues',
    3: 'customs',
    4: 'kingdoms',
    5: 'climate',
    6: 'resources',
    7: 'wildlife',
    8: 'arts',
    9: 'ethnicGroups',
    10: 'socialClasses',
    11: 'importantCharacters'
};

export default class SeriesWorld {
    /**
     * Retrieves all worlds and their elements 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 worlds
     */
    public static getWorldList(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesWorldListProps[] {
        const userKey: string = getUserEncryptionKey(userId);
        const worldsResult: SeriesWorldResult[] = SeriesWorldRepo.fetchWorlds(userId, seriesId, lang);

        const worldsMap: Map<string, SeriesWorldListProps> = new Map();

        for (const row of worldsResult) {
            if (!worldsMap.has(row.world_id)) {
                worldsMap.set(row.world_id, {
                    id: row.world_id,
                    name: row.world_name ? System.decryptDataWithUserKey(row.world_name, userKey) : '',
                    history: row.history ? System.decryptDataWithUserKey(row.history, userKey) : '',
                    politics: row.politics ? System.decryptDataWithUserKey(row.politics, userKey) : '',
                    economy: row.economy ? System.decryptDataWithUserKey(row.economy, userKey) : '',
                    religion: row.religion ? System.decryptDataWithUserKey(row.religion, userKey) : '',
                    languages: row.languages ? System.decryptDataWithUserKey(row.languages, userKey) : '',
                    laws: [],
                    biomes: [],
                    issues: [],
                    customs: [],
                    kingdoms: [],
                    climate: [],
                    resources: [],
                    wildlife: [],
                    arts: [],
                    ethnicGroups: [],
                    socialClasses: [],
                    importantCharacters: []
                });
            }

            if (row.element_id) {
                const world = worldsMap.get(row.world_id)!;
                const element: SeriesWorldElementProps = {
                    id: row.element_id,
                    name: row.element_name ? System.decryptDataWithUserKey(row.element_name, userKey) : '',
                    description: row.element_description ? System.decryptDataWithUserKey(row.element_description, userKey) : ''
                };

                const key = ELEMENT_TYPE_MAP[row.element_type];
                if (key && Array.isArray(world[key])) {
                    (world[key] as SeriesWorldElementProps[]).push(element);
                }
            }
        }

        return Array.from(worldsMap.values());
    }

    /**
     * Adds a new world 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 world
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns The new world ID
     */
    public static addWorld(userId: string, seriesId: string, name: string, lang: 'fr' | 'en' = 'fr'): string {
        const hashedName: string = System.hashElement(name);

        const exists: boolean = SeriesWorldRepo.checkWorldExist(userId, seriesId, hashedName, lang);
        if (exists) {
            throw new Error(lang === 'fr' ? 'Un monde avec ce nom existe déjà.' : 'A world with this name already exists.');
        }

        const userKey: string = getUserEncryptionKey(userId);
        const worldId: string = System.createUniqueId();
        const encryptedName: string = System.encryptDataWithUserKey(name, userKey);

        SeriesWorldRepo.insertNewWorld(worldId, userId, seriesId, encryptedName, hashedName, lang);
        return worldId;
    }

    /**
     * Updates a world's information.
     * @param userId - The unique identifier of the user
     * @param worldId - The unique identifier of the world
     * @param world - The updated world data
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns True if successful
     */
    public static updateWorld(userId: string, worldId: string, world: SeriesWorldUpdateProps, lang: 'fr' | 'en' = 'fr'): boolean {
        const userKey: string = getUserEncryptionKey(userId);
        const encryptedName: string = System.encryptDataWithUserKey(world.name, userKey);
        const hashedName: string = System.hashElement(world.name);
        const encryptedHistory: string | null = world.history ? System.encryptDataWithUserKey(world.history, userKey) : null;
        const encryptedPolitics: string | null = world.politics ? System.encryptDataWithUserKey(world.politics, userKey) : null;
        const encryptedEconomy: string | null = world.economy ? System.encryptDataWithUserKey(world.economy, userKey) : null;
        const encryptedReligion: string | null = world.religion ? System.encryptDataWithUserKey(world.religion, userKey) : null;
        const encryptedLanguages: string | null = world.languages ? System.encryptDataWithUserKey(world.languages, userKey) : null;

        return SeriesWorldRepo.updateWorld(userId, worldId, encryptedName, hashedName, encryptedHistory, encryptedPolitics, encryptedEconomy, encryptedReligion, encryptedLanguages, lang);
    }

    /**
     * Adds a new element to a world.
     * @param userId - The unique identifier of the user
     * @param worldId - The unique identifier of the world
     * @param elementType - The type of element (0-11)
     * @param name - The name of the element
     * @param lang - The language for error messages ('fr' or 'en')
     * @param description - The description of the element (optional)
     * @returns The new element ID
     */
    public static addElement(userId: string, worldId: string, elementType: number, name: string, lang: 'fr' | 'en' = 'fr', description?: string): string {
        const userKey: string = getUserEncryptionKey(userId);
        const elementId: string = System.createUniqueId();
        const encryptedName: string = System.encryptDataWithUserKey(name, userKey);
        const originalName: string = System.hashElement(name);
        const encryptedDescription: string | null = description ? System.encryptDataWithUserKey(description, userKey) : null;

        SeriesWorldRepo.insertElement(elementId, worldId, userId, elementType, encryptedName, originalName, encryptedDescription, lang);
        return elementId;
    }

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