import { getUserEncryptionKey } from "../keyManager.js";
import System from "../System.js";
import WorldRepository, { WorldElementValue, WorldQuery } from "../repositories/world.repository.js";
import BookRepo, {BookToolsTable} from "../repositories/book.repository.js";
import RemovedItem from "./RemovedItem.js";

export interface SyncedWorld {
    id: string;
    name: string;
    lastUpdate: number;
    elements: SyncedWorldElement[];
}

export interface SyncedWorldElement {
    id: string;
    name: string;
    lastUpdate: number;
}

export interface WorldElement {
    id: string;
    name: string;
    description: string;
    type?: number;
}

export interface WorldProps {
    id: string;
    name: string;
    history: string;
    politics: string;
    economy: string;
    religion: string;
    languages: string;
    laws: WorldElement[];
    biomes: WorldElement[];
    issues: WorldElement[];
    customs: WorldElement[];
    kingdoms: WorldElement[];
    climate: WorldElement[];
    resources: WorldElement[];
    wildlife: WorldElement[];
    arts: WorldElement[];
    ethnicGroups: WorldElement[];
    socialClasses: WorldElement[];
    importantCharacters: WorldElement[];
    seriesWorldId?: string | null;
}

export interface WorldListResponse {
    worlds: WorldProps[];
    enabled: boolean;
}

/**
 * Mapping of element type keys to their corresponding numeric type identifiers.
 */
const ELEMENT_TYPE_MAP: Record<string, number> = {
    laws: 1,
    biomes: 2,
    issues: 3,
    customs: 4,
    kingdoms: 5,
    climate: 6,
    resources: 7,
    wildlife: 8,
    arts: 9,
    ethnicGroups: 10,
    socialClasses: 11,
    importantCharacters: 12
};

/**
 * Mapping of numeric type identifiers to their corresponding WorldProps keys.
 */
const ELEMENT_TYPE_KEYS: Record<number, keyof WorldProps> = {
    1: 'laws',
    2: 'biomes',
    3: 'issues',
    4: 'customs',
    5: 'kingdoms',
    6: 'climate',
    7: 'resources',
    8: 'wildlife',
    9: 'arts',
    10: 'ethnicGroups',
    11: 'socialClasses',
    12: 'importantCharacters'
};

export default class World {
    /**
     * Creates a new world for a book.
     * @param userId - The unique identifier of the user creating the world
     * @param bookId - The unique identifier of the book to associate the world with
     * @param worldName - The name of the new world
     * @param lang - The language for error messages ('fr' or 'en'), defaults to 'fr'
     * @param existingWorldId - Optional existing world ID for syncing purposes
     * @returns The unique identifier of the newly created world
     * @throws Error if a world with the same name already exists for this book
     */
    public static addNewWorld(userId: string, bookId: string, worldName: string, lang: 'fr' | 'en' = 'fr', existingWorldId?: string, seriesWorldId: string | null = null): string {
        const userEncryptionKey: string = getUserEncryptionKey(userId);
        const hashedWorldName: string = System.hashElement(worldName);
        if (!existingWorldId && WorldRepository.checkWorldExist(userId, bookId, hashedWorldName, lang)) {
            throw new Error(lang === "fr" ? `Tu as déjà un monde ${worldName}.` : `You already have a world named ${worldName}.`);
        }
        const encryptedWorldName: string = System.encryptDataWithUserKey(worldName, userEncryptionKey);
        const worldId: string = existingWorldId || System.createUniqueId();
        return WorldRepository.insertNewWorld(worldId, userId, bookId, encryptedWorldName, hashedWorldName, lang, seriesWorldId);
    }

    /**
     * Retrieves all worlds and their elements for a specific book.
     * @param userId - The unique identifier of the user
     * @param bookId - The unique identifier of the book
     * @param lang - The language for error messages ('fr' or 'en'), defaults to 'fr'
     * @returns WorldListResponse containing an array of WorldProps and enabled flag
     */
    public static getWorlds(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): WorldListResponse {
        const bookTools: BookToolsTable | null = BookRepo.fetchBookTools(userId, bookId, lang);
        const enabled: boolean = bookTools ? bookTools.worlds_enabled === 1 : false;

        const worldQueryResults: WorldQuery[] = WorldRepository.fetchWorlds(userId, bookId, lang);
        const userEncryptionKey: string = getUserEncryptionKey(userId);
        const worlds: WorldProps[] = [];

        for (const queryRow of worldQueryResults) {
            const existingWorld: WorldProps | undefined = worlds.find((world: WorldProps) => world.id === queryRow.world_id);

            if (!existingWorld) {
                const newWorld: WorldProps = {
                    id: queryRow.world_id,
                    name: System.decryptDataWithUserKey(queryRow.world_name, userEncryptionKey),
                    history: queryRow.history ? System.decryptDataWithUserKey(queryRow.history, userEncryptionKey) : '',
                    politics: queryRow.politics ? System.decryptDataWithUserKey(queryRow.politics, userEncryptionKey) : '',
                    economy: queryRow.economy ? System.decryptDataWithUserKey(queryRow.economy, userEncryptionKey) : '',
                    religion: queryRow.religion ? System.decryptDataWithUserKey(queryRow.religion, userEncryptionKey) : '',
                    languages: queryRow.languages ? System.decryptDataWithUserKey(queryRow.languages, userEncryptionKey) : '',
                    laws: [],
                    biomes: [],
                    issues: [],
                    customs: [],
                    kingdoms: [],
                    climate: [],
                    resources: [],
                    wildlife: [],
                    arts: [],
                    ethnicGroups: [],
                    socialClasses: [],
                    importantCharacters: [],
                    seriesWorldId: queryRow.series_world_id || null,
                };

                worlds.push(newWorld);

                if (queryRow.element_type) {
                    const worldElement: WorldElement = {
                        id: queryRow.element_id as string,
                        name: queryRow.element_name ? System.decryptDataWithUserKey(queryRow.element_name, userEncryptionKey) : '',
                        description: queryRow.element_description ? System.decryptDataWithUserKey(queryRow.element_description, userEncryptionKey) : ''
                    };

                    const elementKey: keyof WorldProps | undefined = ELEMENT_TYPE_KEYS[queryRow.element_type];
                    if (elementKey) {
                        (worlds[worlds.length - 1][elementKey] as WorldElement[]).push(worldElement);
                    }
                }
            } else {
                const worldElement: WorldElement = {
                    id: queryRow.element_id as string,
                    name: queryRow.element_name ? System.decryptDataWithUserKey(queryRow.element_name, userEncryptionKey) : '',
                    description: queryRow.element_description ? System.decryptDataWithUserKey(queryRow.element_description, userEncryptionKey) : ''
                };

                const elementKey: keyof WorldProps | undefined = ELEMENT_TYPE_KEYS[queryRow.element_type as number];
                if (elementKey) {
                    (existingWorld[elementKey] as WorldElement[]).push(worldElement);
                }
            }
        }
        return { worlds, enabled };
    }

    /**
     * Updates a world's properties and all its elements.
     * @param userId - The unique identifier of the user
     * @param world - The WorldProps object containing updated world data
     * @param lang - The language for error messages ('fr' or 'en'), defaults to 'fr'
     * @returns True if the update was successful, false otherwise
     */
    public static updateWorld(userId: string, world: WorldProps, lang: 'fr' | 'en' = 'fr'): boolean {
        const userEncryptionKey: string = getUserEncryptionKey(userId);
        const encryptedName: string = world.name ? System.encryptDataWithUserKey(world.name, userEncryptionKey) : '';
        const encryptedHistory: string = world.history ? System.encryptDataWithUserKey(world.history, userEncryptionKey) : '';
        const encryptedPolitics: string = world.politics ? System.encryptDataWithUserKey(world.politics, userEncryptionKey) : '';
        const encryptedEconomy: string = world.economy ? System.encryptDataWithUserKey(world.economy, userEncryptionKey) : '';
        const encryptedReligion: string = world.religion ? System.encryptDataWithUserKey(world.religion, userEncryptionKey) : '';
        const encryptedLanguages: string = world.languages ? System.encryptDataWithUserKey(world.languages, userEncryptionKey) : '';

        let elementsToUpdate: WorldElementValue[] = [];
        const elementCategories: { key: keyof WorldProps; elements: WorldElement[] }[] = [
            { key: 'laws', elements: world.laws },
            { key: 'biomes', elements: world.biomes },
            { key: 'issues', elements: world.issues },
            { key: 'customs', elements: world.customs },
            { key: 'kingdoms', elements: world.kingdoms },
            { key: 'climate', elements: world.climate },
            { key: 'resources', elements: world.resources },
            { key: 'wildlife', elements: world.wildlife },
            { key: 'arts', elements: world.arts },
            { key: 'ethnicGroups', elements: world.ethnicGroups },
            { key: 'socialClasses', elements: world.socialClasses },
            { key: 'importantCharacters', elements: world.importantCharacters }
        ];

        elementCategories.forEach(({ key, elements: categoryElements }) => {
            elementsToUpdate = elementsToUpdate.concat(categoryElements.map((worldElement: WorldElement) => {
                const encryptedElementName: string = System.encryptDataWithUserKey(worldElement.name, userEncryptionKey);
                const hashedElementName: string = System.hashElement(worldElement.name);
                const encryptedDescription: string = worldElement.description ? System.encryptDataWithUserKey(worldElement.description, userEncryptionKey) : '';
                const elementTypeId: number = World.getElementTypes(key);

                return {
                    id: worldElement.id,
                    name: encryptedElementName,
                    hashedName: hashedElementName,
                    description: encryptedDescription,
                    type: elementTypeId
                };
            }));
        });

        WorldRepository.updateWorld(userId, world.id, encryptedName, System.hashElement(world.name), encryptedHistory, encryptedPolitics, encryptedEconomy, encryptedReligion, encryptedLanguages, System.timeStampInSeconds(), lang, world.seriesWorldId || null);
        return WorldRepository.updateWorldElements(userId, elementsToUpdate, lang);
    }

    /**
     * Adds a new element to an existing world.
     * @param userId - The unique identifier of the user
     * @param worldId - The unique identifier of the world to add the element to
     * @param elementName - The name of the new element
     * @param elementType - The type of element (e.g., 'laws', 'biomes', 'customs')
     * @param lang - The language for error messages ('fr' or 'en'), defaults to 'fr'
     * @param existingElementId - Optional existing element ID for syncing purposes
     * @returns The unique identifier of the newly created element
     * @throws Error if an element with the same name already exists in this world
     */
    public static addNewElementToWorld(userId: string, worldId: string, elementName: string, elementType: string, lang: 'fr' | 'en' = 'fr', existingElementId?: string): string {
        const userEncryptionKey: string = getUserEncryptionKey(userId);
        const hashedElementName: string = System.hashElement(elementName);
        if (!existingElementId && WorldRepository.checkElementExist(worldId, hashedElementName, lang)) {
            throw new Error(lang === "fr" ? `Vous avez déjà un élément avec ce nom ${elementName}.` : `You already have an element named ${elementName}.`);
        }
        const elementTypeId: number = World.getElementTypes(elementType);
        const encryptedElementName: string = System.encryptDataWithUserKey(elementName, userEncryptionKey);
        const elementId: string = existingElementId || System.createUniqueId();
        return WorldRepository.insertNewElement(userId, elementId, elementTypeId, worldId, encryptedElementName, hashedElementName, lang);
    }

    /**
     * Converts an element type string key to its corresponding numeric identifier.
     * @param elementType - The element type key (e.g., 'laws', 'biomes', 'customs')
     * @returns The numeric identifier for the element type, or 0 if not found
     */
    public static getElementTypes(elementType: string): number {
        return ELEMENT_TYPE_MAP[elementType] ?? 0;
    }

    /**
     * Removes an element from a world.
     * @param userId - The unique identifier of the user
     * @param bookId - The unique identifier of the book
     * @param elementId - The unique identifier of the element to remove
     * @param deletedAt - The timestamp of deletion
     * @param lang - The language for error messages ('fr' or 'en'), defaults to 'fr'
     * @returns True if the deletion was successful, false otherwise
     */
    public static removeElementFromWorld(userId: string, bookId: string, elementId: string, deletedAt: number = System.timeStampInSeconds(), lang: 'fr' | 'en' = 'fr'): boolean {
        const deleted: boolean = WorldRepository.deleteElement(userId, elementId, lang);
        if (deleted) {
            RemovedItem.deleteTracker(userId, bookId, 'book_world_elements', elementId, deletedAt, lang);
        }
        return deleted;
    }

}
