import { getUserEncryptionKey } from "../keyManager.js";
import System from "../System.js";
import SeriesSyncRepo, { SyncElementType } from "../repositories/series-sync.repo.js";
import Sync from "./Sync.js";
import {
    CompleteSeries,
    SyncedSeries,
    SeriesTable,
    SeriesBooksTable,
    SeriesCharactersTable,
    SeriesCharacterAttributesTable,
    SeriesWorldsTable,
    SeriesWorldElementsTable,
    SeriesLocationsTable,
    SeriesLocationElementsTable,
    SeriesLocationSubElementsTable,
    SeriesSpellsTable,
    SeriesSpellTagsTable
} from "./Book.js";
import SeriesRepo from "../repositories/series.repo.js";
import BookRepo from "../repositories/book.repository.js";
import SeriesCharacterRepo from "../repositories/series-character.repo.js";
import SeriesWorldRepo from "../repositories/series-world.repo.js";
import SeriesLocationRepo from "../repositories/series-location.repo.js";
import SeriesSpellRepo from "../repositories/series-spell.repo.js";

export interface SeriesSyncUploadPayload {
    type: SyncElementType;
    bookElementId: string;
    field: string;
    value: string;
}

export interface SeriesSyncResult {
    success: boolean;
    updatedCount: number;
}

export type { CompleteSeries, SyncedSeries };

/**
 * Handles series synchronization operations.
 * Manages field propagation from book elements to series elements,
 * and provides methods for complete series upload/download synchronization.
 */
export default class SeriesSync {
    /**
     * Uploads a field value from a book element to its linked series element,
     * then propagates the change to all other book elements linked to the same series element.
     * @param userId - The unique identifier of the user
     * @param payload - Contains type, bookElementId, field, and value
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns Result containing success status and count of updated book elements
     */
    static uploadFieldToSeries(userId: string, payload: SeriesSyncUploadPayload, lang: 'fr' | 'en'): SeriesSyncResult {
        const { type, bookElementId, field, value } = payload;

        // 1. Get the series element ID linked to the book element
        const seriesElementId: string | null = this.getSeriesLink(userId, type, bookElementId, lang);
        if (!seriesElementId) {
            return { success: false, updatedCount: 0 };
        }

        // 2. Encrypt the value
        const userEncryptionKey: string = getUserEncryptionKey(userId);
        const encryptedValue: string = System.encryptDataWithUserKey(value, userEncryptionKey);

        // 3. Map the frontend field name to the database column name
        const dbColumn: string = this.mapFieldToDbColumn(type, field);

        // 4. Update the series element
        const seriesUpdated: boolean = this.updateSeriesElement(userId, type, seriesElementId, dbColumn, encryptedValue, lang);
        if (!seriesUpdated) {
            return { success: false, updatedCount: 0 };
        }

        // 5. Map the series field to the book field (may be different for some types)
        const bookField: string = this.mapSeriesFieldToBookField(type, dbColumn);

        // 6. Update all linked book elements
        const updatedCount: number = this.updateLinkedBookElements(userId, type, seriesElementId, bookField, encryptedValue, lang);

        return { success: true, updatedCount };
    }

    /**
     * Gets the series element ID linked to a book element.
     */
    private static getSeriesLink(userId: string, type: SyncElementType, bookElementId: string, lang: 'fr' | 'en'): string | null {
        switch (type) {
            case 'character':
                return SeriesSyncRepo.getCharacterSeriesLink(userId, bookElementId, lang);
            case 'world':
                return SeriesSyncRepo.getWorldSeriesLink(userId, bookElementId, lang);
            case 'location':
                return SeriesSyncRepo.getLocationSeriesLink(userId, bookElementId, lang);
            case 'spell':
                return SeriesSyncRepo.getSpellSeriesLink(userId, bookElementId, lang);
            default:
                return null;
        }
    }

    /**
     * Maps frontend field names to database column names.
     */
    private static mapFieldToDbColumn(type: SyncElementType, field: string): string {
        // Most fields have the same name, but some need mapping
        const fieldMappings: Record<string, Record<string, string>> = {
            character: {
                firstName: 'first_name',
                lastName: 'last_name',
                speechPattern: 'speech_pattern'
            },
            world: {},
            location: {
                name: 'name',
                locName: 'loc_name'
            },
            spell: {
                powerLevel: 'power_level'
            }
        };

        const typeMapping = fieldMappings[type] || {};
        return typeMapping[field] || field;
    }

    /**
     * Updates a field in the series element.
     */
    private static updateSeriesElement(
        userId: string,
        type: SyncElementType,
        seriesElementId: string,
        field: string,
        encryptedValue: string,
        lang: 'fr' | 'en'
    ): boolean {
        switch (type) {
            case 'character':
                return SeriesSyncRepo.updateSeriesCharacterField(userId, seriesElementId, field, encryptedValue, lang);
            case 'world':
                return SeriesSyncRepo.updateSeriesWorldField(userId, seriesElementId, field, encryptedValue, lang);
            case 'location':
                return SeriesSyncRepo.updateSeriesLocationField(userId, seriesElementId, field, encryptedValue, lang);
            case 'spell':
                return SeriesSyncRepo.updateSeriesSpellField(userId, seriesElementId, field, encryptedValue, lang);
            default:
                return false;
        }
    }

    /**
     * Updates all book elements linked to a series element.
     */
    private static updateLinkedBookElements(
        userId: string,
        type: SyncElementType,
        seriesElementId: string,
        field: string,
        encryptedValue: string,
        lang: 'fr' | 'en'
    ): number {
        switch (type) {
            case 'character':
                return SeriesSyncRepo.updateLinkedBookCharactersField(userId, seriesElementId, field, encryptedValue, lang);
            case 'world':
                return SeriesSyncRepo.updateLinkedBookWorldsField(userId, seriesElementId, field, encryptedValue, lang);
            case 'location':
                return SeriesSyncRepo.updateLinkedBookLocationsField(userId, seriesElementId, field, encryptedValue, lang);
            case 'spell':
                return SeriesSyncRepo.updateLinkedBookSpellsField(userId, seriesElementId, field, encryptedValue, lang);
            default:
                return 0;
        }
    }

    /**
     * Maps series field names to book field names (they may differ).
     */
    private static mapSeriesFieldToBookField(type: SyncElementType, seriesField: string): string {
        const fieldMappings: Record<string, Record<string, string>> = {
            location: {
                name: 'loc_name'
            }
        };

        const typeMapping = fieldMappings[type] || {};
        return typeMapping[seriesField] || seriesField;
    }

    // ===== SYNC METHODS =====

    /**
     * Gets all synced series for a user.
     * Delegates to Sync.getSyncedSeries which already implements this functionality.
     */
    static getSyncedSeries(userId: string, lang: 'fr' | 'en'): SyncedSeries[] {
        return Sync.getSyncedSeries(userId, lang);
    }

    /**
     * Gets a complete series with all data decrypted for upload to server.
     * @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 Complete series object with all decrypted data
     */
    static getCompleteSeriesForUpload(userId: string, seriesId: string, lang: 'fr' | 'en'): CompleteSeries {
        const userEncryptionKey: string = getUserEncryptionKey(userId);

        // Fetch all series data - use table fetch methods that return arrays
        const seriesData = SeriesRepo.fetchSeriesTableForSync(userId, seriesId, lang);
        const seriesBooksData = SeriesRepo.fetchSeriesBooksTable(seriesId, lang);
        const charactersData = SeriesCharacterRepo.fetchSeriesCharactersTable(userId, seriesId, lang);
        const characterAttributesData = SeriesCharacterRepo.fetchSeriesCharacterAttributesBySeriesId(userId, seriesId, lang);
        const worldsData = SeriesWorldRepo.fetchSeriesWorldsTable(userId, seriesId, lang);
        const worldElementsData = SeriesWorldRepo.fetchSeriesWorldElementsBySeriesId(userId, seriesId, lang);
        const locationsData = SeriesLocationRepo.fetchSeriesLocationsTable(userId, seriesId, lang);
        const locationElementsData = SeriesLocationRepo.fetchSeriesLocationElementsBySeriesId(userId, seriesId, lang);
        const locationSubElementsData = SeriesLocationRepo.fetchSeriesLocationSubElementsBySeriesId(userId, seriesId, lang);
        const spellsData = SeriesSpellRepo.fetchSeriesSpellsTable(userId, seriesId, lang);
        const spellTagsData = SeriesSpellRepo.fetchSeriesSpellTagsTable(userId, seriesId, lang);

        // Decrypt series
        const series: SeriesTable[] = seriesData.map((s: SeriesTable): SeriesTable => ({
            ...s,
            name: System.decryptDataWithUserKey(s.name, userEncryptionKey),
            description: s.description ? System.decryptDataWithUserKey(s.description, userEncryptionKey) : null,
            cover_image: s.cover_image ? System.decryptDataWithUserKey(s.cover_image, userEncryptionKey) : null
        }));

        // Decrypt characters
        const seriesCharacters: SeriesCharactersTable[] = charactersData.map((c): SeriesCharactersTable => {
            const decryptedAge: string | null = c.age ? System.decryptDataWithUserKey(c.age, userEncryptionKey) : null;
            return {
                character_id: c.character_id as string,
                series_id: c.series_id as string,
                user_id: c.user_id as string,
                first_name: System.decryptDataWithUserKey(c.first_name as string, userEncryptionKey),
                last_name: c.last_name ? System.decryptDataWithUserKey(c.last_name as string, userEncryptionKey) : null,
                nickname: c.nickname ? System.decryptDataWithUserKey(c.nickname as string, userEncryptionKey) : null,
                age: decryptedAge ? parseInt(decryptedAge, 10) : null,
                gender: c.gender ? System.decryptDataWithUserKey(c.gender as string, userEncryptionKey) : null,
                species: c.species ? System.decryptDataWithUserKey(c.species as string, userEncryptionKey) : null,
                nationality: c.nationality ? System.decryptDataWithUserKey(c.nationality as string, userEncryptionKey) : null,
                status: c.status ? System.decryptDataWithUserKey(c.status as string, userEncryptionKey) : null,
                title: c.title ? System.decryptDataWithUserKey(c.title as string, userEncryptionKey) : null,
                category: System.decryptDataWithUserKey(c.category as string, userEncryptionKey),
                image: c.image ? System.decryptDataWithUserKey(c.image as string, userEncryptionKey) : null,
                role: c.role ? System.decryptDataWithUserKey(c.role as string, userEncryptionKey) : null,
                biography: c.biography ? System.decryptDataWithUserKey(c.biography as string, userEncryptionKey) : null,
                history: c.history ? System.decryptDataWithUserKey(c.history as string, userEncryptionKey) : null,
                speech_pattern: c.speech_pattern ? System.decryptDataWithUserKey(c.speech_pattern as string, userEncryptionKey) : null,
                catchphrase: c.catchphrase ? System.decryptDataWithUserKey(c.catchphrase as string, userEncryptionKey) : null,
                residence: c.residence ? System.decryptDataWithUserKey(c.residence as string, userEncryptionKey) : null,
                notes: c.notes ? System.decryptDataWithUserKey(c.notes as string, userEncryptionKey) : null,
                color: c.color ? System.decryptDataWithUserKey(c.color as string, userEncryptionKey) : null,
                last_update: c.last_update as number
            };
        });

        // Decrypt character attributes
        const seriesCharacterAttributes: SeriesCharacterAttributesTable[] = characterAttributesData.map((a: SeriesCharacterAttributesTable): SeriesCharacterAttributesTable => ({
            ...a,
            attribute_name: System.decryptDataWithUserKey(a.attribute_name, userEncryptionKey),
            attribute_value: System.decryptDataWithUserKey(a.attribute_value, userEncryptionKey)
        }));

        // Decrypt worlds
        const seriesWorlds: SeriesWorldsTable[] = worldsData.map((w: SeriesWorldsTable): SeriesWorldsTable => ({
            ...w,
            name: System.decryptDataWithUserKey(w.name, userEncryptionKey),
            history: w.history ? System.decryptDataWithUserKey(w.history, userEncryptionKey) : null,
            politics: w.politics ? System.decryptDataWithUserKey(w.politics, userEncryptionKey) : null,
            economy: w.economy ? System.decryptDataWithUserKey(w.economy, userEncryptionKey) : null,
            religion: w.religion ? System.decryptDataWithUserKey(w.religion, userEncryptionKey) : null,
            languages: w.languages ? System.decryptDataWithUserKey(w.languages, userEncryptionKey) : null
        }));

        // Decrypt world elements
        const seriesWorldElements: SeriesWorldElementsTable[] = worldElementsData.map((e: SeriesWorldElementsTable): SeriesWorldElementsTable => ({
            ...e,
            name: System.decryptDataWithUserKey(e.name, userEncryptionKey),
            description: e.description ? System.decryptDataWithUserKey(e.description, userEncryptionKey) : null
        }));

        // Decrypt locations
        const seriesLocations: SeriesLocationsTable[] = locationsData.map((l: SeriesLocationsTable): SeriesLocationsTable => ({
            ...l,
            loc_name: System.decryptDataWithUserKey(l.loc_name, userEncryptionKey)
        }));

        // Decrypt location elements
        const seriesLocationElements: SeriesLocationElementsTable[] = locationElementsData.map((e: SeriesLocationElementsTable): SeriesLocationElementsTable => ({
            ...e,
            element_name: System.decryptDataWithUserKey(e.element_name, userEncryptionKey),
            element_description: e.element_description ? System.decryptDataWithUserKey(e.element_description, userEncryptionKey) : null
        }));

        // Decrypt location sub-elements
        const seriesLocationSubElements: SeriesLocationSubElementsTable[] = locationSubElementsData.map((se: SeriesLocationSubElementsTable): SeriesLocationSubElementsTable => ({
            ...se,
            sub_elem_name: System.decryptDataWithUserKey(se.sub_elem_name, userEncryptionKey),
            sub_elem_description: se.sub_elem_description ? System.decryptDataWithUserKey(se.sub_elem_description, userEncryptionKey) : null
        }));

        // Decrypt spells
        const seriesSpells: SeriesSpellsTable[] = spellsData.map((s): SeriesSpellsTable => ({
            spell_id: s.spell_id as string,
            series_id: s.series_id as string,
            user_id: s.user_id as string,
            name: System.decryptDataWithUserKey(s.name as string, userEncryptionKey),
            name_hash: s.name_hash as string,
            description: s.description ? System.decryptDataWithUserKey(s.description as string, userEncryptionKey) : '',
            appearance: s.appearance ? System.decryptDataWithUserKey(s.appearance as string, userEncryptionKey) : '',
            tags: s.tags ? System.decryptDataWithUserKey(s.tags as string, userEncryptionKey) : '',
            power_level: s.power_level ? System.decryptDataWithUserKey(s.power_level as string, userEncryptionKey) : null,
            components: s.components ? System.decryptDataWithUserKey(s.components as string, userEncryptionKey) : null,
            limitations: s.limitations ? System.decryptDataWithUserKey(s.limitations as string, userEncryptionKey) : null,
            notes: s.notes ? System.decryptDataWithUserKey(s.notes as string, userEncryptionKey) : null,
            last_update: s.last_update as number
        }));

        // Decrypt spell tags
        const seriesSpellTags: SeriesSpellTagsTable[] = spellTagsData.map((t: SeriesSpellTagsTable): SeriesSpellTagsTable => ({
            ...t,
            name: System.decryptDataWithUserKey(t.name, userEncryptionKey)
        }));

        return {
            series,
            seriesBooks: seriesBooksData,
            seriesCharacters,
            seriesCharacterAttributes,
            seriesWorlds,
            seriesWorldElements,
            seriesLocations,
            seriesLocationElements,
            seriesLocationSubElements,
            seriesSpells,
            seriesSpellTags
        };
    }

    /**
     * Saves a complete series downloaded from the server to the local database.
     * Encrypts all data before storing.
     * @param userId - The unique identifier of the user
     * @param completeSeries - The complete series data from the server
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns True if save was successful, false otherwise
     */
    static saveCompleteSeries(userId: string, completeSeries: CompleteSeries, lang: 'fr' | 'en'): boolean {
        const userEncryptionKey: string = getUserEncryptionKey(userId);

        // Save series
        for (const series of completeSeries.series) {
            const encryptedName: string = System.encryptDataWithUserKey(series.name, userEncryptionKey);
            const encryptedDescription: string | null = series.description ? System.encryptDataWithUserKey(series.description, userEncryptionKey) : null;
            const encryptedCoverImage: string | null = series.cover_image ? System.encryptDataWithUserKey(series.cover_image, userEncryptionKey) : null;

            const success: boolean = SeriesRepo.insertSyncSeries(
                series.series_id,
                userId,
                encryptedName,
                series.hashed_name,
                encryptedDescription,
                encryptedCoverImage,
                series.last_update,
                lang
            );
            if (!success) return false;
        }

        // Save series books (only if the book exists locally)
        for (const seriesBook of completeSeries.seriesBooks) {
            const bookExists: boolean = BookRepo.isBookExist(userId, seriesBook.book_id, lang);
            if (!bookExists) continue;

            const success: boolean = SeriesRepo.insertSyncSeriesBook(
                seriesBook.series_id,
                seriesBook.book_id,
                seriesBook.book_order,
                seriesBook.last_update,
                lang
            );
            if (!success) return false;
        }

        // Save characters
        for (const character of completeSeries.seriesCharacters) {
            const encFirstName: string = System.encryptDataWithUserKey(character.first_name, userEncryptionKey);
            const encLastName: string | null = character.last_name ? System.encryptDataWithUserKey(character.last_name, userEncryptionKey) : null;
            const encNickname: string | null = character.nickname ? System.encryptDataWithUserKey(character.nickname, userEncryptionKey) : null;
            const encAge: string | null = character.age !== null ? System.encryptDataWithUserKey(String(character.age), userEncryptionKey) : null;
            const encGender: string | null = character.gender ? System.encryptDataWithUserKey(character.gender, userEncryptionKey) : null;
            const encSpecies: string | null = character.species ? System.encryptDataWithUserKey(character.species, userEncryptionKey) : null;
            const encNationality: string | null = character.nationality ? System.encryptDataWithUserKey(character.nationality, userEncryptionKey) : null;
            const encStatus: string | null = character.status ? System.encryptDataWithUserKey(character.status, userEncryptionKey) : null;
            const encTitle: string | null = character.title ? System.encryptDataWithUserKey(character.title, userEncryptionKey) : null;
            const encCategory: string = System.encryptDataWithUserKey(character.category, userEncryptionKey);
            const encImage: string | null = character.image ? System.encryptDataWithUserKey(character.image, userEncryptionKey) : null;
            const encRole: string | null = character.role ? System.encryptDataWithUserKey(character.role, userEncryptionKey) : null;
            const encBiography: string | null = character.biography ? System.encryptDataWithUserKey(character.biography, userEncryptionKey) : null;
            const encHistory: string | null = character.history ? System.encryptDataWithUserKey(character.history, userEncryptionKey) : null;
            const encSpeechPattern: string | null = character.speech_pattern ? System.encryptDataWithUserKey(character.speech_pattern, userEncryptionKey) : null;
            const encCatchphrase: string | null = character.catchphrase ? System.encryptDataWithUserKey(character.catchphrase, userEncryptionKey) : null;
            const encResidence: string | null = character.residence ? System.encryptDataWithUserKey(character.residence, userEncryptionKey) : null;
            const encNotes: string | null = character.notes ? System.encryptDataWithUserKey(character.notes, userEncryptionKey) : null;
            const encColor: string | null = character.color ? System.encryptDataWithUserKey(character.color, userEncryptionKey) : null;

            const success: boolean = SeriesCharacterRepo.insertSyncSeriesCharacter(
                character.character_id,
                character.series_id,
                userId,
                encFirstName,
                encLastName,
                encNickname,
                encAge,
                encGender,
                encSpecies,
                encNationality,
                encStatus,
                encCategory,
                encTitle,
                encImage,
                encRole,
                encBiography,
                encHistory,
                encSpeechPattern,
                encCatchphrase,
                encResidence,
                encNotes,
                encColor,
                character.last_update,
                lang
            );
            if (!success) return false;
        }

        // Save character attributes
        for (const attr of completeSeries.seriesCharacterAttributes) {
            const encryptedName: string = System.encryptDataWithUserKey(attr.attribute_name, userEncryptionKey);
            const encryptedValue: string = System.encryptDataWithUserKey(attr.attribute_value, userEncryptionKey);

            const success: boolean = SeriesCharacterRepo.insertSyncSeriesCharacterAttribute(
                attr.attr_id,
                attr.character_id,
                userId,
                encryptedName,
                encryptedValue,
                attr.last_update,
                lang
            );
            if (!success) return false;
        }

        // Save worlds
        for (const world of completeSeries.seriesWorlds) {
            const encryptedName: string = System.encryptDataWithUserKey(world.name, userEncryptionKey);
            const encryptedHistory: string | null = world.history ? System.encryptDataWithUserKey(world.history, userEncryptionKey) : null;
            const encryptedPolitics: string | null = world.politics ? System.encryptDataWithUserKey(world.politics, userEncryptionKey) : null;
            const encryptedEconomy: string | null = world.economy ? System.encryptDataWithUserKey(world.economy, userEncryptionKey) : null;
            const encryptedReligion: string | null = world.religion ? System.encryptDataWithUserKey(world.religion, userEncryptionKey) : null;
            const encryptedLanguages: string | null = world.languages ? System.encryptDataWithUserKey(world.languages, userEncryptionKey) : null;

            const success: boolean = SeriesWorldRepo.insertSyncSeriesWorld(
                world.world_id,
                world.series_id,
                userId,
                encryptedName,
                world.hashed_name,
                encryptedHistory,
                encryptedPolitics,
                encryptedEconomy,
                encryptedReligion,
                encryptedLanguages,
                world.last_update,
                lang
            );
            if (!success) return false;
        }

        // Save world elements
        for (const element of completeSeries.seriesWorldElements) {
            const encryptedName: string = System.encryptDataWithUserKey(element.name, userEncryptionKey);
            const encryptedDescription: string | null = element.description ? System.encryptDataWithUserKey(element.description, userEncryptionKey) : null;

            const success: boolean = SeriesWorldRepo.insertSyncSeriesWorldElement(
                element.element_id,
                element.world_id,
                userId,
                element.element_type,
                encryptedName,
                element.original_name,
                encryptedDescription,
                element.last_update,
                lang
            );
            if (!success) return false;
        }

        // Save locations
        for (const location of completeSeries.seriesLocations) {
            const encryptedName: string = System.encryptDataWithUserKey(location.loc_name, userEncryptionKey);

            const success: boolean = SeriesLocationRepo.insertSyncSeriesLocation(
                location.loc_id,
                location.series_id,
                userId,
                encryptedName,
                location.loc_original_name,
                location.last_update,
                lang
            );
            if (!success) return false;
        }

        // Save location elements
        for (const element of completeSeries.seriesLocationElements) {
            const encryptedName: string = System.encryptDataWithUserKey(element.element_name, userEncryptionKey);
            const encryptedDescription: string | null = element.element_description ? System.encryptDataWithUserKey(element.element_description, userEncryptionKey) : null;

            const success: boolean = SeriesLocationRepo.insertSyncSeriesLocationElement(
                element.element_id,
                element.location_id,
                userId,
                encryptedName,
                element.original_name,
                encryptedDescription,
                element.last_update,
                lang
            );
            if (!success) return false;
        }

        // Save location sub-elements
        for (const subElement of completeSeries.seriesLocationSubElements) {
            const encryptedName: string = System.encryptDataWithUserKey(subElement.sub_elem_name, userEncryptionKey);
            const encryptedDescription: string | null = subElement.sub_elem_description ? System.encryptDataWithUserKey(subElement.sub_elem_description, userEncryptionKey) : null;

            const success: boolean = SeriesLocationRepo.insertSyncSeriesLocationSubElement(
                subElement.sub_element_id,
                subElement.element_id,
                userId,
                encryptedName,
                subElement.original_name,
                encryptedDescription,
                subElement.last_update,
                lang
            );
            if (!success) return false;
        }

        // Save spells
        for (const spell of completeSeries.seriesSpells) {
            const encryptedName: string = System.encryptDataWithUserKey(spell.name, userEncryptionKey);
            const encryptedDescription: string = System.encryptDataWithUserKey(spell.description, userEncryptionKey);
            const encryptedAppearance: string = System.encryptDataWithUserKey(spell.appearance, userEncryptionKey);
            const encryptedTags: string = System.encryptDataWithUserKey(spell.tags, userEncryptionKey);
            const encryptedPowerLevel: string | null = spell.power_level ? System.encryptDataWithUserKey(spell.power_level, userEncryptionKey) : null;
            const encryptedComponents: string | null = spell.components ? System.encryptDataWithUserKey(spell.components, userEncryptionKey) : null;
            const encryptedLimitations: string | null = spell.limitations ? System.encryptDataWithUserKey(spell.limitations, userEncryptionKey) : null;
            const encryptedNotes: string | null = spell.notes ? System.encryptDataWithUserKey(spell.notes, userEncryptionKey) : null;

            const success: boolean = SeriesSpellRepo.insertSyncSeriesSpell(
                spell.spell_id,
                spell.series_id,
                userId,
                encryptedName,
                spell.name_hash,
                encryptedDescription,
                encryptedAppearance,
                encryptedTags,
                encryptedPowerLevel,
                encryptedComponents,
                encryptedLimitations,
                encryptedNotes,
                spell.last_update,
                lang
            );
            if (!success) return false;
        }

        // Save spell tags
        for (const tag of completeSeries.seriesSpellTags) {
            const encryptedName: string = System.encryptDataWithUserKey(tag.name, userEncryptionKey);

            const success: boolean = SeriesSpellRepo.insertSyncSeriesSpellTag(
                tag.tag_id,
                tag.series_id,
                userId,
                encryptedName,
                tag.hashed_name,
                tag.color,
                tag.last_update,
                lang
            );
            if (!success) return false;
        }

        return true;
    }

    /**
     * Synchronizes a series from server to client, updating existing records or inserting new ones.
     * @param userId - The unique identifier of the user
     * @param completeSeries - The complete series data from the server
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns True if sync was successful, false otherwise
     */
    static syncSeriesFromServerToClient(userId: string, completeSeries: CompleteSeries, lang: 'fr' | 'en'): boolean {
        const userEncryptionKey: string = getUserEncryptionKey(userId);

        // Sync series
        for (const series of completeSeries.series) {
            const encryptedName: string = System.encryptDataWithUserKey(series.name, userEncryptionKey);
            const encryptedDescription: string | null = series.description ? System.encryptDataWithUserKey(series.description, userEncryptionKey) : null;
            const encryptedCoverImage: string | null = series.cover_image ? System.encryptDataWithUserKey(series.cover_image, userEncryptionKey) : null;

            const exists: boolean = SeriesRepo.seriesExists(userId, series.series_id, lang);
            if (exists) {
                const success: boolean = SeriesRepo.updateSyncSeries(
                    userId,
                    series.series_id,
                    encryptedName,
                    series.hashed_name,
                    encryptedDescription,
                    encryptedCoverImage,
                    series.last_update,
                    lang
                );
                if (!success) return false;
            } else {
                const success: boolean = SeriesRepo.insertSyncSeries(
                    series.series_id,
                    userId,
                    encryptedName,
                    series.hashed_name,
                    encryptedDescription,
                    encryptedCoverImage,
                    series.last_update,
                    lang
                );
                if (!success) return false;
            }
        }

        // Sync series books (only if the book exists locally)
        for (const seriesBook of completeSeries.seriesBooks) {
            const bookExists: boolean = BookRepo.isBookExist(userId, seriesBook.book_id, lang);
            if (!bookExists) continue;

            const success: boolean = SeriesRepo.insertSyncSeriesBook(
                seriesBook.series_id,
                seriesBook.book_id,
                seriesBook.book_order,
                seriesBook.last_update,
                lang
            );
            if (!success) return false;
        }

        // Sync characters
        for (const character of completeSeries.seriesCharacters) {
            const encFirstName: string = System.encryptDataWithUserKey(character.first_name, userEncryptionKey);
            const encLastName: string | null = character.last_name ? System.encryptDataWithUserKey(character.last_name, userEncryptionKey) : null;
            const encNickname: string | null = character.nickname ? System.encryptDataWithUserKey(character.nickname, userEncryptionKey) : null;
            const encAge: string | null = character.age !== null ? System.encryptDataWithUserKey(String(character.age), userEncryptionKey) : null;
            const encGender: string | null = character.gender ? System.encryptDataWithUserKey(character.gender, userEncryptionKey) : null;
            const encSpecies: string | null = character.species ? System.encryptDataWithUserKey(character.species, userEncryptionKey) : null;
            const encNationality: string | null = character.nationality ? System.encryptDataWithUserKey(character.nationality, userEncryptionKey) : null;
            const encStatus: string | null = character.status ? System.encryptDataWithUserKey(character.status, userEncryptionKey) : null;
            const encTitle: string | null = character.title ? System.encryptDataWithUserKey(character.title, userEncryptionKey) : null;
            const encCategory: string = System.encryptDataWithUserKey(character.category, userEncryptionKey);
            const encImage: string | null = character.image ? System.encryptDataWithUserKey(character.image, userEncryptionKey) : null;
            const encRole: string | null = character.role ? System.encryptDataWithUserKey(character.role, userEncryptionKey) : null;
            const encBiography: string | null = character.biography ? System.encryptDataWithUserKey(character.biography, userEncryptionKey) : null;
            const encHistory: string | null = character.history ? System.encryptDataWithUserKey(character.history, userEncryptionKey) : null;
            const encSpeechPattern: string | null = character.speech_pattern ? System.encryptDataWithUserKey(character.speech_pattern, userEncryptionKey) : null;
            const encCatchphrase: string | null = character.catchphrase ? System.encryptDataWithUserKey(character.catchphrase, userEncryptionKey) : null;
            const encResidence: string | null = character.residence ? System.encryptDataWithUserKey(character.residence, userEncryptionKey) : null;
            const encNotes: string | null = character.notes ? System.encryptDataWithUserKey(character.notes, userEncryptionKey) : null;
            const encColor: string | null = character.color ? System.encryptDataWithUserKey(character.color, userEncryptionKey) : null;

            const exists: boolean = SeriesCharacterRepo.seriesCharacterExists(userId, character.character_id, lang);
            if (exists) {
                const success: boolean = SeriesCharacterRepo.updateSyncSeriesCharacter(
                    userId,
                    character.character_id,
                    encFirstName,
                    encLastName,
                    encNickname,
                    encAge,
                    encGender,
                    encSpecies,
                    encNationality,
                    encStatus,
                    encCategory,
                    encTitle,
                    encImage,
                    encRole,
                    encBiography,
                    encHistory,
                    encSpeechPattern,
                    encCatchphrase,
                    encResidence,
                    encNotes,
                    encColor,
                    character.last_update,
                    lang
                );
                if (!success) return false;
            } else {
                const success: boolean = SeriesCharacterRepo.insertSyncSeriesCharacter(
                    character.character_id,
                    character.series_id,
                    userId,
                    encFirstName,
                    encLastName,
                    encNickname,
                    encAge,
                    encGender,
                    encSpecies,
                    encNationality,
                    encStatus,
                    encCategory,
                    encTitle,
                    encImage,
                    encRole,
                    encBiography,
                    encHistory,
                    encSpeechPattern,
                    encCatchphrase,
                    encResidence,
                    encNotes,
                    encColor,
                    character.last_update,
                    lang
                );
                if (!success) return false;
            }
        }

        // Sync character attributes
        for (const attr of completeSeries.seriesCharacterAttributes) {
            const encryptedName: string = System.encryptDataWithUserKey(attr.attribute_name, userEncryptionKey);
            const encryptedValue: string = System.encryptDataWithUserKey(attr.attribute_value, userEncryptionKey);

            const exists: boolean = SeriesCharacterRepo.seriesCharacterAttributeExists(userId, attr.attr_id, lang);
            if (exists) {
                const success: boolean = SeriesCharacterRepo.updateSyncSeriesCharacterAttribute(
                    userId,
                    attr.attr_id,
                    encryptedName,
                    encryptedValue,
                    attr.last_update,
                    lang
                );
                if (!success) return false;
            } else {
                const success: boolean = SeriesCharacterRepo.insertSyncSeriesCharacterAttribute(
                    attr.attr_id,
                    attr.character_id,
                    userId,
                    encryptedName,
                    encryptedValue,
                    attr.last_update,
                    lang
                );
                if (!success) return false;
            }
        }

        // Sync worlds
        for (const world of completeSeries.seriesWorlds) {
            const encryptedName: string = System.encryptDataWithUserKey(world.name, userEncryptionKey);
            const encryptedHistory: string | null = world.history ? System.encryptDataWithUserKey(world.history, userEncryptionKey) : null;
            const encryptedPolitics: string | null = world.politics ? System.encryptDataWithUserKey(world.politics, userEncryptionKey) : null;
            const encryptedEconomy: string | null = world.economy ? System.encryptDataWithUserKey(world.economy, userEncryptionKey) : null;
            const encryptedReligion: string | null = world.religion ? System.encryptDataWithUserKey(world.religion, userEncryptionKey) : null;
            const encryptedLanguages: string | null = world.languages ? System.encryptDataWithUserKey(world.languages, userEncryptionKey) : null;

            const exists: boolean = SeriesWorldRepo.seriesWorldExists(userId, world.world_id, lang);
            if (exists) {
                const success: boolean = SeriesWorldRepo.updateSyncSeriesWorld(
                    world.world_id,
                    userId,
                    encryptedName,
                    encryptedHistory,
                    encryptedPolitics,
                    encryptedEconomy,
                    encryptedReligion,
                    encryptedLanguages,
                    world.last_update,
                    lang
                );
                if (!success) return false;
            } else {
                const success: boolean = SeriesWorldRepo.insertSyncSeriesWorld(
                    world.world_id,
                    world.series_id,
                    userId,
                    encryptedName,
                    world.hashed_name,
                    encryptedHistory,
                    encryptedPolitics,
                    encryptedEconomy,
                    encryptedReligion,
                    encryptedLanguages,
                    world.last_update,
                    lang
                );
                if (!success) return false;
            }
        }

        // Sync world elements
        for (const element of completeSeries.seriesWorldElements) {
            const encryptedName: string = System.encryptDataWithUserKey(element.name, userEncryptionKey);
            const encryptedDescription: string | null = element.description ? System.encryptDataWithUserKey(element.description, userEncryptionKey) : null;

            const exists: boolean = SeriesWorldRepo.seriesWorldElementExists(userId, element.element_id, lang);
            if (exists) {
                const success: boolean = SeriesWorldRepo.updateSyncSeriesWorldElement(
                    element.element_id,
                    userId,
                    encryptedName,
                    encryptedDescription,
                    element.last_update,
                    lang
                );
                if (!success) return false;
            } else {
                const success: boolean = SeriesWorldRepo.insertSyncSeriesWorldElement(
                    element.element_id,
                    element.world_id,
                    userId,
                    element.element_type,
                    encryptedName,
                    element.original_name,
                    encryptedDescription,
                    element.last_update,
                    lang
                );
                if (!success) return false;
            }
        }

        // Sync locations
        for (const location of completeSeries.seriesLocations) {
            const encryptedName: string = System.encryptDataWithUserKey(location.loc_name, userEncryptionKey);

            const exists: boolean = SeriesLocationRepo.seriesLocationExists(userId, location.loc_id, lang);
            if (exists) {
                const success: boolean = SeriesLocationRepo.updateSyncSeriesLocation(
                    location.loc_id,
                    userId,
                    encryptedName,
                    location.last_update,
                    lang
                );
                if (!success) return false;
            } else {
                const success: boolean = SeriesLocationRepo.insertSyncSeriesLocation(
                    location.loc_id,
                    location.series_id,
                    userId,
                    encryptedName,
                    location.loc_original_name,
                    location.last_update,
                    lang
                );
                if (!success) return false;
            }
        }

        // Sync location elements
        for (const element of completeSeries.seriesLocationElements) {
            const encryptedName: string = System.encryptDataWithUserKey(element.element_name, userEncryptionKey);
            const encryptedDescription: string | null = element.element_description ? System.encryptDataWithUserKey(element.element_description, userEncryptionKey) : null;

            const exists: boolean = SeriesLocationRepo.seriesLocationElementExists(userId, element.element_id, lang);
            if (exists) {
                const success: boolean = SeriesLocationRepo.updateSyncSeriesLocationElement(
                    element.element_id,
                    userId,
                    encryptedName,
                    encryptedDescription,
                    element.last_update,
                    lang
                );
                if (!success) return false;
            } else {
                const success: boolean = SeriesLocationRepo.insertSyncSeriesLocationElement(
                    element.element_id,
                    element.location_id,
                    userId,
                    encryptedName,
                    element.original_name,
                    encryptedDescription,
                    element.last_update,
                    lang
                );
                if (!success) return false;
            }
        }

        // Sync location sub-elements
        for (const subElement of completeSeries.seriesLocationSubElements) {
            const encryptedName: string = System.encryptDataWithUserKey(subElement.sub_elem_name, userEncryptionKey);
            const encryptedDescription: string | null = subElement.sub_elem_description ? System.encryptDataWithUserKey(subElement.sub_elem_description, userEncryptionKey) : null;

            const exists: boolean = SeriesLocationRepo.seriesLocationSubElementExists(userId, subElement.sub_element_id, lang);
            if (exists) {
                const success: boolean = SeriesLocationRepo.updateSyncSeriesLocationSubElement(
                    subElement.sub_element_id,
                    userId,
                    encryptedName,
                    encryptedDescription,
                    subElement.last_update,
                    lang
                );
                if (!success) return false;
            } else {
                const success: boolean = SeriesLocationRepo.insertSyncSeriesLocationSubElement(
                    subElement.sub_element_id,
                    subElement.element_id,
                    userId,
                    encryptedName,
                    subElement.original_name,
                    encryptedDescription,
                    subElement.last_update,
                    lang
                );
                if (!success) return false;
            }
        }

        // Sync spells
        for (const spell of completeSeries.seriesSpells) {
            const encryptedName: string = System.encryptDataWithUserKey(spell.name, userEncryptionKey);
            const encryptedDescription: string = System.encryptDataWithUserKey(spell.description, userEncryptionKey);
            const encryptedAppearance: string = System.encryptDataWithUserKey(spell.appearance, userEncryptionKey);
            const encryptedTags: string = System.encryptDataWithUserKey(spell.tags, userEncryptionKey);
            const encryptedPowerLevel: string | null = spell.power_level ? System.encryptDataWithUserKey(spell.power_level, userEncryptionKey) : null;
            const encryptedComponents: string | null = spell.components ? System.encryptDataWithUserKey(spell.components, userEncryptionKey) : null;
            const encryptedLimitations: string | null = spell.limitations ? System.encryptDataWithUserKey(spell.limitations, userEncryptionKey) : null;
            const encryptedNotes: string | null = spell.notes ? System.encryptDataWithUserKey(spell.notes, userEncryptionKey) : null;

            const exists: boolean = SeriesSpellRepo.seriesSpellExists(userId, spell.spell_id, lang);
            if (exists) {
                const success: boolean = SeriesSpellRepo.updateSyncSeriesSpell(
                    spell.spell_id,
                    userId,
                    encryptedName,
                    encryptedDescription,
                    encryptedAppearance,
                    encryptedTags,
                    encryptedPowerLevel,
                    encryptedComponents,
                    encryptedLimitations,
                    encryptedNotes,
                    spell.last_update,
                    lang
                );
                if (!success) return false;
            } else {
                const success: boolean = SeriesSpellRepo.insertSyncSeriesSpell(
                    spell.spell_id,
                    spell.series_id,
                    userId,
                    encryptedName,
                    spell.name_hash,
                    encryptedDescription,
                    encryptedAppearance,
                    encryptedTags,
                    encryptedPowerLevel,
                    encryptedComponents,
                    encryptedLimitations,
                    encryptedNotes,
                    spell.last_update,
                    lang
                );
                if (!success) return false;
            }
        }

        // Sync spell tags
        for (const tag of completeSeries.seriesSpellTags) {
            const encryptedName: string = System.encryptDataWithUserKey(tag.name, userEncryptionKey);

            const exists: boolean = SeriesSpellRepo.seriesSpellTagExists(userId, tag.tag_id, lang);
            if (exists) {
                const success: boolean = SeriesSpellRepo.updateSyncSeriesSpellTag(
                    tag.tag_id,
                    userId,
                    encryptedName,
                    tag.color,
                    tag.last_update,
                    lang
                );
                if (!success) return false;
            } else {
                const success: boolean = SeriesSpellRepo.insertSyncSeriesSpellTag(
                    tag.tag_id,
                    tag.series_id,
                    userId,
                    encryptedName,
                    tag.hashed_name,
                    tag.color,
                    tag.last_update,
                    lang
                );
                if (!success) return false;
            }
        }

        return true;
    }
}
