import User from "./User";
import System, {UserKey} from "./System";
import LocationRepo, {
    LocationByTagResult,
    LocationElementQueryResult,
    LocationQueryResult
} from "../repositories/location.repo";

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

export interface Element {
    id: string;
    name: string;
    description: string;
    subElements: SubElement[];
}

export interface LocationProps {
    id: string;
    name: string;
    elements: Element[];
}

export default class Location {
    /**
     * Récupère toutes les locations pour un utilisateur et un livre donnés.
     * @param {string} userId - L'ID de l'utilisateur.
     * @param {string} bookId - L'ID du livre.
     * @returns {Promise<LocationProps[]>} - Une promesse qui résout un tableau de propriétés de location.
     * @throws {Error} - Lance une erreur si une exception se produit lors de la récupération des locations.
     */
    static async getAllLocations(userId: string, bookId: string): Promise<LocationProps[]> {
        const locations: LocationQueryResult[] = await LocationRepo.getLocation(userId, bookId);
        if (!locations || locations.length === 0) return [];
        const user = new User(userId);
        const userKeys: UserKey[] = await System.getAllUserKeysAndVersions(userId);
        
        const locationArray: LocationProps[] = [];
        
        for (const record of locations) {
            let location = locationArray.find(loc => loc.id === record.loc_id);
            
            if (!location) {
                const key: string = await user.getUserKey(record.loc_meta, true, userKeys);
                const decryptedName: string = System.decryptDataWithUserKey(record.loc_name, key)/* ton code de décryptage ici avec record.loc_name */;
                location = {
                    id: record.loc_id,
                    name: decryptedName,
                    elements: []
                };
                locationArray.push(location);
            }
            
            if (record.element_id) {
                let element = location.elements.find(elem => elem.id === record.element_id);
                if (!element) {
                    const key: string = await user.getUserKey(record.element_meta, true, userKeys);
                    const decryptedName: string = System.decryptDataWithUserKey(record.element_name, key)/* ton code de décryptage ici avec record.element_name */;
                    const decryptedDesc: string = record.element_description ? System.decryptDataWithUserKey(record.element_description, key) : ''/* ton code de décryptage ici avec record.element_description */;
                    
                    element = {
                        id: record.element_id,
                        name: decryptedName,
                        description: decryptedDesc,
                        subElements: []
                    };
                    location.elements.push(element);
                }
                
                if (record.sub_element_id) {
                    const subElementExists = element.subElements.some(sub => sub.id === record.sub_element_id);
                    
                    if (!subElementExists) {
                        const key: string = await user.getUserKey(record.sub_elem_meta, true, userKeys);
                        const decryptedName: string = System.decryptDataWithUserKey(record.sub_elem_name, key)/* ton code de décryptage ici avec record.sub_elem_name */;
                        const decryptedDesc: string = record.sub_elem_description ? System.decryptDataWithUserKey(record.sub_elem_description, key) : ''/* ton code de décryptage ici avec record.sub_elem_description */;
                        
                        
                        element.subElements.push({
                            id: record.sub_element_id,
                            name: decryptedName,
                            description: decryptedDesc
                        });
                    }
                }
            }
        }
        return locationArray;
    }
    
    static async addLocationSection(userId: string, locationName: string, bookId: string): Promise<string> {
        const user = new User(userId);
        const meta: string = System.encryptDateKey(userId);
        const userKey: string = await user.getUserKey(meta, true);
        const originalName: string = System.hashElement(locationName);
        const encryptedName: string = System.encryptDataWithUserKey(locationName, userKey);
        const locationId: string = System.createUniqueId();
        return await LocationRepo.insertLocation(userId, locationId, bookId, encryptedName, originalName, meta);
    }

    static async addLocationElement(userId: string, locationId: string, elementName: string) {
        const user = new User(userId);
        const meta: string = System.encryptDateKey(userId);
        const userKey: string = await user.getUserKey(meta, true);
        const originalName: string = System.hashElement(elementName);
        const encryptedName: string = System.encryptDataWithUserKey(elementName, userKey);
        const elementId: string = System.createUniqueId();
        return LocationRepo.insertLocationElement(userId, elementId, locationId, encryptedName, originalName, meta)
    }

    static async addLocationSubElement(userId: string, elementId: string, subElementName: string) {
        const user = new User(userId);
        const meta: string = System.encryptDateKey(userId);
        const userKey: string = await user.getUserKey(meta, true);
        const originalName: string = System.hashElement(subElementName);
        const encryptedName: string = System.encryptDataWithUserKey(subElementName, userKey);
        const subElementId: string = System.createUniqueId();
        return LocationRepo.insertLocationSubElement(userId, subElementId, elementId, encryptedName, originalName, meta)
    }

    static async updateLocationSection(userId: string, locations: LocationProps[]) {
        const user = new User(userId);
        const meta: string = System.encryptDateKey(userId);
        const userKey: string = await user.getUserKey(meta, true);

        for (const location of locations) {
            const originalName: string = System.hashElement(location.name);
            const encryptedName: string = System.encryptDataWithUserKey(location.name, userKey);
            await LocationRepo.updateLocationSection(userId, location.id, encryptedName, originalName, meta)
            for (const element of location.elements) {
                const originalName: string = System.hashElement(element.name);
                const encryptedName: string = System.encryptDataWithUserKey(element.name, userKey);
                const encryptDescription: string = element.description ? System.encryptDataWithUserKey(element.description, userKey) : '';
                await LocationRepo.updateLocationElement(userId, element.id, encryptedName, originalName, encryptDescription, meta)
                for (const subElement of element.subElements) {
                    const originalName: string = System.hashElement(subElement.name);
                    const encryptedName: string = System.encryptDataWithUserKey(subElement.name, userKey);
                    const encryptDescription: string = subElement.description ? System.encryptDataWithUserKey(subElement.description, userKey) : '';
                    await LocationRepo.updateLocationSubElement(userId, subElement.id, encryptedName, originalName, encryptDescription, meta)
                }
            }
        }
        return {
            valid: true,
            message: 'Les sections ont été mis à jour.'
        }
    }

    static async deleteLocationSection(userId: string, locationId: string) {
        return LocationRepo.deleteLocationSection(userId, locationId);
    }

    static async deleteLocationElement(userId: string, elementId: string) {
        return LocationRepo.deleteLocationElement(userId, elementId);
    }

    static async deleteLocationSubElement(userId: string, subElementId: string) {
        return LocationRepo.deleteLocationSubElement(userId, subElementId);
    }
    
    static async getLocationTags(userId: string, bookId: string): Promise<SubElement[]> {
        const data: LocationElementQueryResult[] = await LocationRepo.fetchLocationTags(userId, bookId);
        if (!data || data.length === 0) return [];
        const user = new User(userId);
        const userKeys: UserKey[] = await System.getAllUserKeysAndVersions(userId);
        
        const elementCounts = new Map<string, number>();
        data.forEach((record: LocationElementQueryResult): void => {
            elementCounts.set(record.element_id, (elementCounts.get(record.element_id) || 0) + 1);
        });
    
        const subElements: SubElement[] = [];
        const processedIds = new Set<string>();
    
        for (const record of data) {
            const elementCount: number = elementCounts.get(record.element_id) || 0;
            
            if (elementCount > 1 && record.sub_element_id) {
                if (processedIds.has(record.sub_element_id)) continue;
                
                const key: string = await user.getUserKey(record.sub_elem_meta, true, userKeys);
                subElements.push({
                    id: record.sub_element_id,
                    name: System.decryptDataWithUserKey(record.sub_elem_name, key),
                    description: record.sub_elem_description ? System.decryptDataWithUserKey(record.sub_elem_description, key) : ''
                });
                processedIds.add(record.sub_element_id);
            } else if (elementCount === 1 && !record.sub_element_id) {
                if (processedIds.has(record.element_id)) continue;
                
                const key: string = await user.getUserKey(record.element_meta, true, userKeys);
                subElements.push({
                    id: record.element_id,
                    name: System.decryptDataWithUserKey(record.element_name, key),
                    description: record.element_description ? System.decryptDataWithUserKey(record.element_description, key) : ''
                });
                processedIds.add(record.element_id);
            }
        }
        return subElements;
    }
    
    static async getLocationsByTags(userId: string, locations: string[]): Promise<Element[]> {
        const locationsTags: LocationByTagResult[] = await LocationRepo.fetchLocationsByTags(userId, locations);
        if (!locationsTags || locationsTags.length === 0) return [];
        const user = new User(userId);
        const userKeys: UserKey[] = await System.getAllUserKeysAndVersions(userId);
        const locationTags: Element[] = [];
        for (const record of locationsTags) {
            let element: Element | undefined = locationTags.find((elem: Element): boolean => elem.name === record.element_name);
            if (!element) {
                const key: string = await user.getUserKey(record.element_meta, true, userKeys);
                const decryptedName: string = System.decryptDataWithUserKey(record.element_name, key);
                const decryptedDesc: string = record.element_description ? System.decryptDataWithUserKey(record.element_description, key) : '';
                element = {
                    id: record.element_id,
                    name: decryptedName,
                    description: decryptedDesc,
                    subElements: []
                };
                locationTags.push(element);
            }
            if (record.sub_elem_name) {
                const subElementExists: boolean = element.subElements.some(sub => sub.name === record.sub_elem_name);
                if (!subElementExists) {
                    const key: string = await user.getUserKey(record.sub_elem_meta, true, userKeys);
                    const decryptedName: string = System.decryptDataWithUserKey(record.sub_elem_name, key);
                    const decryptedDesc: string = record.sub_elem_description ? System.decryptDataWithUserKey(record.sub_elem_description, key) : '';
                    element.subElements.push({
                        id: '',
                        name: decryptedName,
                        description: decryptedDesc
                    });
                }
            }
        }
        return locationTags;
    }
    
    static async locationsDescription(locations: Element[]): Promise<string> {
        return locations.map((location: Element): string => {
            const fields: string[] = [];
            if (location.name) fields.push(`Nom : ${location.name}`);
            if (location.description) fields.push(`Description : ${location.description}`);
            return fields.join('\n');
        }).join('\n\n');
    }
}
