import { getUserEncryptionKey } from "../keyManager.js";
import System from "../System.js";
import SeriesLocationRepo, { SeriesLocationResult, SeriesLocationElementResult, SeriesLocationSubElementResult } from "../repositories/series-location.repo.js";
import RemovedItem from "./RemovedItem.js";

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

export interface SeriesLocationElementProps {
    id: string;
    name: string;
    description: string;
    subElements: SeriesLocationSubElementProps[];
}

export interface SeriesLocationListProps {
    id: string;
    name: string;
    elements: SeriesLocationElementProps[];
}

export default class SeriesLocation {
    /**
     * Retrieves all locations for a series with their elements and sub-elements.
     * @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 locations
     */
    public static getLocationList(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesLocationListProps[] {
        const userKey: string = getUserEncryptionKey(userId);
        const locationsResult: SeriesLocationResult[] = SeriesLocationRepo.fetchLocations(userId, seriesId, lang);

        return locationsResult.map((loc): SeriesLocationListProps => {
            const elementsResult: SeriesLocationElementResult[] = SeriesLocationRepo.fetchElements(userId, loc.loc_id, lang);

            const elements: SeriesLocationElementProps[] = elementsResult.map((elem): SeriesLocationElementProps => {
                const subElementsResult: SeriesLocationSubElementResult[] = SeriesLocationRepo.fetchSubElements(userId, elem.element_id, lang);

                const subElements: SeriesLocationSubElementProps[] = subElementsResult.map((sub): SeriesLocationSubElementProps => ({
                    id: sub.sub_element_id,
                    name: sub.sub_elem_name ? System.decryptDataWithUserKey(sub.sub_elem_name, userKey) : '',
                    description: sub.sub_elem_description ? System.decryptDataWithUserKey(sub.sub_elem_description, userKey) : ''
                }));

                return {
                    id: elem.element_id,
                    name: elem.element_name ? System.decryptDataWithUserKey(elem.element_name, userKey) : '',
                    description: elem.element_description ? System.decryptDataWithUserKey(elem.element_description, userKey) : '',
                    subElements
                };
            });

            return {
                id: loc.loc_id,
                name: loc.loc_name ? System.decryptDataWithUserKey(loc.loc_name, userKey) : '',
                elements
            };
        });
    }

    /**
     * Adds a new location section 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 location
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns The new location ID
     */
    public static addLocationSection(userId: string, seriesId: string, name: string, lang: 'fr' | 'en' = 'fr'): string {
        const userKey: string = getUserEncryptionKey(userId);
        const locationId: string = System.createUniqueId();
        const encryptedName: string = System.encryptDataWithUserKey(name, userKey);
        const originalName: string = System.hashElement(name);

        SeriesLocationRepo.insertLocation(locationId, seriesId, userId, encryptedName, originalName, lang);
        return locationId;
    }

    /**
     * Adds a new element to a location.
     * @param userId - The unique identifier of the user
     * @param locationId - The unique identifier of the location
     * @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, locationId: string, 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;

        SeriesLocationRepo.insertElement(elementId, locationId, userId, encryptedName, originalName, encryptedDescription, lang);
        return elementId;
    }

    /**
     * Adds a new sub-element to an element.
     * @param userId - The unique identifier of the user
     * @param elementId - The unique identifier of the element
     * @param name - The name of the sub-element
     * @param lang - The language for error messages ('fr' or 'en')
     * @param description - The description of the sub-element (optional)
     * @returns The new sub-element ID
     */
    public static addSubElement(userId: string, elementId: string, name: string, lang: 'fr' | 'en' = 'fr', description?: string): string {
        const userKey: string = getUserEncryptionKey(userId);
        const subElementId: 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;

        SeriesLocationRepo.insertSubElement(subElementId, elementId, userId, encryptedName, originalName, encryptedDescription, lang);
        return subElementId;
    }

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

    /**
     * Deletes an element.
     * @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 = SeriesLocationRepo.deleteElement(userId, elementId, lang);
        if (deleted) {
            RemovedItem.deleteTracker(userId, null, 'series_location_elements', elementId, deletedAt, lang);
        }
        return deleted;
    }

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