import { Database, RunResult, SQLiteValue } from 'node-sqlite3-wasm';
import System from '../System.js';

export interface SpellResult extends Record<string, SQLiteValue> {
    spell_id: string;
    book_id: string;
    name: string;
    description: string | null;
    appearance: string | null;
    tags: string | null;
    power_level: string | null;
    components: string | null;
    limitations: string | null;
    notes: string | null;
    series_spell_id: string | null;
}

export interface BookSpellsTable extends Record<string, SQLiteValue> {
    spell_id: string;
    book_id: string;
    user_id: string;
    name: string;
    name_hash: string;
    description: string | null;
    appearance: string | null;
    tags: string | null;
    power_level: string | null;
    components: string | null;
    limitations: string | null;
    notes: string | null;
    last_update: number;
}

export interface SyncedSpellResult extends Record<string, SQLiteValue> {
    spell_id: string;
    book_id: string;
    name: string;
    last_update: number;
}

export default class SpellRepo {
    /**
     * Fetches all spells for a specific book owned by the user.
     * @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')
     * @returns An array of spell results
     */
    static fetchSpells(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): SpellResult[] {
        try {
            const db: Database = System.getDb();
            const query: string = 'SELECT spell_id, book_id, name, description, appearance, tags, power_level, components, limitations, notes, series_spell_id FROM book_spells WHERE user_id=? AND book_id=?';
            const params: SQLiteValue[] = [userId, bookId];
            return db.all(query, params) as SpellResult[];
        } catch (error: unknown) {
            if (error instanceof Error) {
                console.error(`[SpellRepo] DB Error: ${error.message}`);
            } else {
                console.error('[SpellRepo] An unknown error occurred.');
            }
            throw new Error(lang === 'fr' ? `Impossible de récupérer les sorts.` : `Unable to retrieve spells.`);
        }
    }

    /**
     * Fetches a single spell by its ID.
     * @param userId - The unique identifier of the user
     * @param spellId - The unique identifier of the spell
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns The spell result or null if not found
     */
    static fetchSpellById(userId: string, spellId: string, lang: 'fr' | 'en' = 'fr'): SpellResult | null {
        try {
            const db: Database = System.getDb();
            const query: string = 'SELECT spell_id, book_id, name, description, appearance, tags, power_level, components, limitations, notes, series_spell_id FROM book_spells WHERE user_id=? AND spell_id=?';
            const params: SQLiteValue[] = [userId, spellId];
            const spells: SpellResult[] = db.all(query, params) as SpellResult[];
            return spells.length > 0 ? spells[0] : null;
        } catch (error: unknown) {
            if (error instanceof Error) {
                console.error(`[SpellRepo] DB Error: ${error.message}`);
            } else {
                console.error('[SpellRepo] An unknown error occurred.');
            }
            throw new Error(lang === 'fr' ? `Impossible de récupérer le sort.` : `Unable to retrieve spell.`);
        }
    }

    /**
     * Inserts a new spell.
     * @param spellId - The unique identifier for the new spell
     * @param bookId - The unique identifier of the book
     * @param userId - The unique identifier of the user
     * @param name - The encrypted name
     * @param nameHash - The hashed name for duplicate detection
     * @param description - The encrypted description
     * @param appearance - The encrypted appearance
     * @param tags - The encrypted JSON tags array
     * @param powerLevel - The encrypted power level (nullable)
     * @param components - The encrypted components (nullable)
     * @param limitations - The encrypted limitations (nullable)
     * @param notes - The encrypted notes (nullable)
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns The spell ID if successful
     */
    static insertSpell(spellId: string, bookId: string, userId: string, name: string, nameHash: string, description: string | null, appearance: string | null, tags: string, powerLevel: string | null, components: string | null, limitations: string | null, notes: string | null, lang: 'fr' | 'en' = 'fr', seriesSpellId: string | null = null): string {
        let result: RunResult;
        try {
            const db: Database = System.getDb();
            const query: string = seriesSpellId
                ? 'INSERT INTO book_spells (spell_id, book_id, user_id, name, name_hash, description, appearance, tags, power_level, components, limitations, notes, series_spell_id, last_update) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)'
                : 'INSERT INTO book_spells (spell_id, book_id, user_id, name, name_hash, description, appearance, tags, power_level, components, limitations, notes, last_update) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)';
            const params: SQLiteValue[] = seriesSpellId
                ? [spellId, bookId, userId, name, nameHash, description, appearance, tags, powerLevel, components, limitations, notes, seriesSpellId, System.timeStampInSeconds()]
                : [spellId, bookId, userId, name, nameHash, description, appearance, tags, powerLevel, components, limitations, notes, System.timeStampInSeconds()];
            result = db.run(query, params);
        } catch (error: unknown) {
            if (error instanceof Error) {
                console.error(`[SpellRepo] DB Error: ${error.message}`);
            } else {
                console.error('[SpellRepo] An unknown error occurred.');
            }
            throw new Error(lang === 'fr' ? `Impossible d'ajouter le sort.` : `Unable to add spell.`);
        }
        if (!result || result.changes === 0) {
            throw new Error(lang === 'fr' ? `Une erreur s'est produite lors de l'ajout du sort.` : `Error adding spell.`);
        }
        return spellId;
    }

    /**
     * Updates an existing spell.
     * @param userId - The unique identifier of the user
     * @param spellId - The unique identifier of the spell
     * @param name - The encrypted name
     * @param nameHash - The hashed name
     * @param description - The encrypted description
     * @param appearance - The encrypted appearance
     * @param tags - The encrypted JSON tags array
     * @param powerLevel - The encrypted power level (nullable)
     * @param components - The encrypted components (nullable)
     * @param limitations - The encrypted limitations (nullable)
     * @param notes - The encrypted notes (nullable)
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns True if the update was successful
     */
    static updateSpell(userId: string, spellId: string, name: string, nameHash: string, description: string | null, appearance: string | null, tags: string, powerLevel: string | null, components: string | null, limitations: string | null, notes: string | null, lang: 'fr' | 'en' = 'fr', seriesSpellId: string | null = null): boolean {
        try {
            const db: Database = System.getDb();
            const query: string = seriesSpellId !== null
                ? 'UPDATE book_spells SET name=?, name_hash=?, description=?, appearance=?, tags=?, power_level=?, components=?, limitations=?, notes=?, series_spell_id=?, last_update=? WHERE spell_id=? AND user_id=?'
                : 'UPDATE book_spells SET name=?, name_hash=?, description=?, appearance=?, tags=?, power_level=?, components=?, limitations=?, notes=?, last_update=? WHERE spell_id=? AND user_id=?';
            const params: SQLiteValue[] = seriesSpellId !== null
                ? [name, nameHash, description, appearance, tags, powerLevel, components, limitations, notes, seriesSpellId, System.timeStampInSeconds(), spellId, userId]
                : [name, nameHash, description, appearance, tags, powerLevel, components, limitations, notes, System.timeStampInSeconds(), spellId, userId];
            const result: RunResult = db.run(query, params);
            return result.changes > 0;
        } catch (error: unknown) {
            if (error instanceof Error) {
                console.error(`[SpellRepo] DB Error: ${error.message}`);
            } else {
                console.error('[SpellRepo] An unknown error occurred.');
            }
            throw new Error(lang === 'fr' ? `Impossible de mettre à jour le sort.` : `Unable to update spell.`);
        }
    }

    /**
     * Deletes a spell.
     * @param userId - The unique identifier of the user
     * @param spellId - The unique identifier of the spell
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns True if the deletion was successful
     */
    static deleteSpell(userId: string, spellId: string, lang: 'fr' | 'en' = 'fr'): boolean {
        try {
            const db: Database = System.getDb();
            const query: string = 'DELETE FROM book_spells WHERE spell_id=? AND user_id=?';
            const params: SQLiteValue[] = [spellId, userId];
            const result: RunResult = db.run(query, params);
            return result.changes > 0;
        } catch (error: unknown) {
            if (error instanceof Error) {
                console.error(`[SpellRepo] DB Error: ${error.message}`);
            } else {
                console.error('[SpellRepo] An unknown error occurred.');
            }
            throw new Error(lang === 'fr' ? `Impossible de supprimer le sort.` : `Unable to delete spell.`);
        }
    }

    /**
     * Updates the tags field of a spell.
     * @param userId - The unique identifier of the user
     * @param spellId - The unique identifier of the spell
     * @param tags - The new encrypted JSON tags array
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns True if the update was successful
     */
    static updateSpellTags(userId: string, spellId: string, tags: string, lang: 'fr' | 'en' = 'fr'): boolean {
        try {
            const db: Database = System.getDb();
            const query: string = 'UPDATE book_spells SET tags=?, last_update=? WHERE spell_id=? AND user_id=?';
            const params: SQLiteValue[] = [tags, System.timeStampInSeconds(), spellId, userId];
            const result: RunResult = db.run(query, params);
            return result.changes > 0;
        } catch (error: unknown) {
            if (error instanceof Error) {
                console.error(`[SpellRepo] DB Error: ${error.message}`);
            } else {
                console.error('[SpellRepo] An unknown error occurred.');
            }
            throw new Error(lang === 'fr' ? `Impossible de mettre à jour les tags du sort.` : `Unable to update spell tags.`);
        }
    }

    /**
     * Fetches all spells for a book with full table data for sync.
     * @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')
     * @returns An array of book spells table records
     */
    static fetchBookSpellsTable(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): BookSpellsTable[] {
        try {
            const db: Database = System.getDb();
            const query: string = 'SELECT spell_id, book_id, user_id, name, name_hash, description, appearance, tags, power_level, components, limitations, notes, last_update FROM book_spells WHERE user_id=? AND book_id=?';
            const params: SQLiteValue[] = [userId, bookId];
            return db.all(query, params) as BookSpellsTable[];
        } catch (error: unknown) {
            if (error instanceof Error) {
                console.error(`[SpellRepo] DB Error: ${error.message}`);
            } else {
                console.error('[SpellRepo] An unknown error occurred.');
            }
            throw new Error(lang === 'fr' ? `Impossible de récupérer les sorts.` : `Unable to retrieve spells.`);
        }
    }

    /**
     * Fetches a complete spell record by its ID.
     * @param userId - The unique identifier of the user
     * @param spellId - The unique identifier of the spell
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns The spell table record or null
     */
    static fetchSpellTableById(userId: string, spellId: string, lang: 'fr' | 'en' = 'fr'): BookSpellsTable | null {
        try {
            const db: Database = System.getDb();
            const query: string = 'SELECT spell_id, book_id, user_id, name, name_hash, description, appearance, tags, power_level, components, limitations, notes, last_update FROM book_spells WHERE user_id=? AND spell_id=?';
            const params: SQLiteValue[] = [userId, spellId];
            const spells: BookSpellsTable[] = db.all(query, params) as BookSpellsTable[];
            return spells.length > 0 ? spells[0] : null;
        } catch (error: unknown) {
            if (error instanceof Error) {
                console.error(`[SpellRepo] DB Error: ${error.message}`);
            } else {
                console.error('[SpellRepo] An unknown error occurred.');
            }
            throw new Error(lang === 'fr' ? `Impossible de récupérer le sort.` : `Unable to retrieve spell.`);
        }
    }

    /**
     * Fetches all synced spells for a user.
     * @param userId - The unique identifier of the user
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns An array of synced spell results
     */
    static fetchSyncedSpells(userId: string, lang: 'fr' | 'en' = 'fr'): SyncedSpellResult[] {
        try {
            const db: Database = System.getDb();
            const query: string = 'SELECT spell_id, book_id, name, last_update FROM book_spells WHERE user_id=?';
            const params: SQLiteValue[] = [userId];
            return db.all(query, params) as SyncedSpellResult[];
        } catch (error: unknown) {
            if (error instanceof Error) {
                console.error(`[SpellRepo] DB Error: ${error.message}`);
            } else {
                console.error('[SpellRepo] An unknown error occurred.');
            }
            throw new Error(lang === 'fr' ? `Impossible de récupérer les sorts synchronisés.` : `Unable to retrieve synced spells.`);
        }
    }

    /**
     * Inserts or updates a spell from synchronization data.
     * @param spellId - The unique identifier for the spell
     * @param bookId - The unique identifier of the book
     * @param userId - The unique identifier of the user
     * @param name - The encrypted name
     * @param nameHash - The hashed name
     * @param description - The encrypted description
     * @param appearance - The encrypted appearance
     * @param tags - The encrypted JSON tags array
     * @param powerLevel - The encrypted power level (nullable)
     * @param components - The encrypted components (nullable)
     * @param limitations - The encrypted limitations (nullable)
     * @param notes - The encrypted notes (nullable)
     * @param lastUpdate - The timestamp of the last update
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns True if the insertion was successful
     */
    static insertSyncSpell(spellId: string, bookId: string, userId: string, name: string, nameHash: string, description: string | null, appearance: string | null, tags: string | null, powerLevel: string | null, components: string | null, limitations: string | null, notes: string | null, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean {
        try {
            const db: Database = System.getDb();
            const query: string = 'INSERT OR REPLACE INTO book_spells (spell_id, book_id, user_id, name, name_hash, description, appearance, tags, power_level, components, limitations, notes, last_update) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)';
            const params: SQLiteValue[] = [spellId, bookId, userId, name, nameHash, description, appearance, tags, powerLevel, components, limitations, notes, lastUpdate];
            const result: RunResult = db.run(query, params);
            return result.changes > 0;
        } catch (error: unknown) {
            if (error instanceof Error) {
                console.error(`[SpellRepo] DB Error: ${error.message}`);
            } else {
                console.error('[SpellRepo] An unknown error occurred.');
            }
            throw new Error(lang === 'fr' ? `Impossible d'insérer le sort.` : `Unable to insert spell.`);
        }
    }

    /**
     * Checks if a spell exists.
     * @param userId - The unique identifier of the user
     * @param spellId - The unique identifier of the spell
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns True if the spell exists
     */
    static isSpellExist(userId: string, spellId: string, lang: 'fr' | 'en' = 'fr'): boolean {
        try {
            const db: Database = System.getDb();
            const query: string = 'SELECT 1 FROM book_spells WHERE spell_id=? AND user_id=?';
            const params: SQLiteValue[] = [spellId, userId];
            const existenceCheck = db.all(query, params);
            return existenceCheck.length > 0;
        } catch (error: unknown) {
            if (error instanceof Error) {
                console.error(`[SpellRepo] DB Error: ${error.message}`);
            } else {
                console.error('[SpellRepo] An unknown error occurred.');
            }
            throw new Error(lang === 'fr' ? `Impossible de vérifier l'existence du sort.` : `Unable to check spell existence.`);
        }
    }

    /**
     * Updates a spell with timestamp for sync.
     * @param userId - The unique identifier of the user
     * @param spellId - The unique identifier of the spell
     * @param name - The encrypted name
     * @param nameHash - The hashed name
     * @param description - The encrypted description
     * @param appearance - The encrypted appearance
     * @param tags - The encrypted JSON tags array
     * @param powerLevel - The encrypted power level (nullable)
     * @param components - The encrypted components (nullable)
     * @param limitations - The encrypted limitations (nullable)
     * @param notes - The encrypted notes (nullable)
     * @param lastUpdate - The timestamp of the last update
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns True if the update was successful
     */
    static updateSyncSpell(userId: string, spellId: string, name: string, nameHash: string, description: string | null, appearance: string | null, tags: string | null, powerLevel: string | null, components: string | null, limitations: string | null, notes: string | null, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean {
        try {
            const db: Database = System.getDb();
            const query: string = 'UPDATE book_spells SET name=?, name_hash=?, description=?, appearance=?, tags=?, power_level=?, components=?, limitations=?, notes=?, last_update=? WHERE spell_id=? AND user_id=?';
            const params: SQLiteValue[] = [name, nameHash, description, appearance, tags, powerLevel, components, limitations, notes, lastUpdate, spellId, userId];
            const result: RunResult = db.run(query, params);
            return result.changes > 0;
        } catch (error: unknown) {
            if (error instanceof Error) {
                console.error(`[SpellRepo] DB Error: ${error.message}`);
            } else {
                console.error('[SpellRepo] An unknown error occurred.');
            }
            throw new Error(lang === 'fr' ? `Impossible de mettre à jour le sort.` : `Unable to update spell.`);
        }
    }
}
