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

export interface BookQuery extends Record<string, SQLiteValue> {
    book_id: string;
    type: string;
    author_id: string;
    title: string;
    hashed_title: string;
    sub_title: string | null;
    hashed_sub_title: string | null;
    summary: string | null;
    serie_id: number | null;
    desired_release_date: string | null;
    desired_word_count: number | null;
    words_count: number | null;
    cover_image: string | null;
}

export interface EritBooksTable extends Record<string, SQLiteValue> {
    book_id: string;
    type: string;
    author_id: string;
    title: string;
    hashed_title: string;
    sub_title: string | null;
    hashed_sub_title: string | null;
    summary: string | null;
    serie_id: number | null;
    desired_release_date: string | null;
    desired_word_count: number | null;
    words_count: number | null;
    last_update: number;
    cover_image: string | null;
}

export interface SyncedBookResult extends Record<string, SQLiteValue> {
    book_id: string;
    type: string;
    title: string;
    sub_title: string | null;
    last_update: number;
}

export interface BookCoverQuery extends Record<string, SQLiteValue> {
    cover_image: string;
}

export interface BookToolsTable extends Record<string, SQLiteValue> {
    book_id: string;
    user_id: string;
    characters_enabled: number;
    worlds_enabled: number;
    locations_enabled: number;
    spells_enabled: number;
    last_update: number;
}

export interface SyncedBookToolsResult extends Record<string, SQLiteValue> {
    last_update: number;
    characters_enabled: number;
    worlds_enabled: number;
    locations_enabled: number;
    spells_enabled: number;
}

export default class BookRepo {
    /**
     * Retrieves all books for a user.
     * @param userId - The user identifier
     * @param lang - The language for error messages
     * @returns List of user's books
     */
    public static fetchBooks(userId: string, lang: 'fr' | 'en'): BookQuery[] {
        try {
            const db: Database = System.getDb();
            const query: string = 'SELECT book_id, type, author_id, title, sub_title, summary, serie_id, desired_release_date, desired_word_count, words_count, cover_image FROM erit_books WHERE author_id = ? ORDER BY book_id DESC';
            const params: SQLiteValue[] = [userId];
            return db.all(query, params) as BookQuery[];
        } catch (error: unknown) {
            if (error instanceof Error) {
                console.error(error.message);
                throw new Error(lang === 'fr' ? 'Impossible de récupérer la liste des livres.' : 'Unable to retrieve book list.');
            }
            console.error(error);
            throw new Error(lang === 'fr' ? 'Une erreur inconnue est survenue.' : 'An unknown error occurred.');
        }
    }

    /**
     * Updates a book's cover image.
     * @param bookId - The book identifier
     * @param coverImageName - The cover image file name
     * @param userId - The user identifier
     * @param lang - The language for error messages
     * @returns true if the update was successful
     */
    public static updateBookCover(bookId: string, coverImageName: string, userId: string, lang: 'fr' | 'en'): boolean {
        try {
            const db: Database = System.getDb();
            const query: string = 'UPDATE erit_books SET cover_image=?, last_update=? WHERE book_id=? AND author_id=?';
            const params: SQLiteValue[] = [coverImageName, System.timeStampInSeconds(), bookId, userId];
            const updateResult: RunResult = db.run(query, params);
            return updateResult.changes > 0;
        } catch (error: unknown) {
            if (error instanceof Error) {
                console.error(`DB Error: ${error.message}`);
                throw new Error(lang === 'fr' ? 'Impossible de mettre à jour la couverture du livre.' : 'Unable to update book cover.');
            }
            console.error("An unknown error occurred.");
            throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
        }
    }

    /**
     * Retrieves a book by its identifier.
     * @param bookId - The book identifier
     * @param userId - The user identifier
     * @param lang - The language for error messages
     * @returns The book information
     */
    public static fetchBook(bookId: string, userId: string, lang: 'fr' | 'en'): BookQuery {
        let book: BookQuery;
        try {
            const db: Database = System.getDb();
            const query: string = 'SELECT book_id, author_id, title, summary, sub_title, cover_image, desired_release_date, desired_word_count, words_count, serie_id FROM erit_books WHERE book_id=? AND author_id=?';
            const params: SQLiteValue[] = [bookId, userId];
            book = db.get(query, params) as BookQuery;
        } catch (error: unknown) {
            if (error instanceof Error) {
                console.error(`DB Error: ${error.message}`);
                throw new Error(lang === 'fr' ? 'Impossible de récupérer les informations du livre.' : 'Unable to retrieve book information.');
            }
            console.error("An unknown error occurred.");
            throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
        }
        if (!book) {
            throw new Error(lang === 'fr' ? 'Livre non trouvé.' : 'Book not found.');
        }
        return book;
    }

    /**
     * Verifies if a book already exists for a user.
     * @param hashedTitle - The hashed book title
     * @param hashedSubTitle - The hashed book subtitle
     * @param userId - The user identifier
     * @param lang - The language for error messages
     * @returns true if the book exists
     */
    public static verifyBookExist(hashedTitle: string, hashedSubTitle: string, userId: string, lang: 'fr' | 'en'): boolean {
        try {
            const db: Database = System.getDb();
            const query: string = 'SELECT book_id FROM erit_books WHERE hashed_title=? AND author_id=? AND hashed_sub_title=?';
            const params: SQLiteValue[] = [hashedTitle, userId, hashedSubTitle];
            const book: QueryResult | null = db.get(query, params);
            return book !== null;
        } catch (error: unknown) {
            if (error instanceof Error) {
                console.error(`DB Error: ${error.message}`);
                throw new Error(lang === 'fr' ? "Impossible de vérifier l'existence du livre." : 'Unable to verify book existence.');
            }
            console.error("An unknown error occurred.");
            throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
        }
    }

    /**
     * Inserts a new book into the database.
     * @param bookId - The book identifier
     * @param userId - The user identifier
     * @param encryptedTitle - The encrypted title
     * @param hashedTitle - The hashed title
     * @param encryptedSubTitle - The encrypted subtitle
     * @param hashedSubTitle - The hashed subtitle
     * @param encryptedSummary - The encrypted summary
     * @param type - The book type
     * @param serie - The series identifier
     * @param publicationDate - The desired publication date
     * @param desiredWordCount - The desired word count
     * @param lang - The language for error messages
     * @returns The created book identifier
     */
    public static insertBook(bookId: string, userId: string, encryptedTitle: string, hashedTitle: string, encryptedSubTitle: string, hashedSubTitle: string, encryptedSummary: string, type: string, serie: number, publicationDate: string, desiredWordCount: number, lang: 'fr' | 'en'): string {
        let insertResult: RunResult;
        try {
            const db: Database = System.getDb();
            const query: string = 'INSERT INTO erit_books (book_id, type, author_id, title, hashed_title, sub_title, hashed_sub_title, summary, serie_id, desired_release_date, desired_word_count, last_update) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)';
            const params: SQLiteValue[] = [bookId, type, userId, encryptedTitle, hashedTitle, encryptedSubTitle, hashedSubTitle, encryptedSummary, serie, publicationDate ? publicationDate : null, desiredWordCount, System.timeStampInSeconds()];
            insertResult = db.run(query, params);
        } catch (error: unknown) {
            if (error instanceof Error) {
                console.error(`DB Error: ${error.message}`);
                throw new Error(lang === 'fr' ? "Impossible d'ajouter le livre." : 'Unable to add book.');
            }
            console.error("An unknown error occurred.");
            throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
        }
        if (!insertResult || insertResult.changes === 0) {
            throw new Error(lang === 'fr' ? "Erreur lors de l'ajout du livre." : 'Error adding book.');
        }
        return bookId;
    }

    /**
     * Retrieves a book's cover image.
     * @param userId - The user identifier
     * @param bookId - The book identifier
     * @param lang - The language for error messages
     * @returns The cover information
     */
    public static fetchBookCover(userId: string, bookId: string, lang: 'fr' | 'en'): BookCoverQuery {
        try {
            const db: Database = System.getDb();
            const query: string = 'SELECT cover_image FROM erit_books WHERE author_id=? AND book_id=?';
            const params: SQLiteValue[] = [userId, bookId];
            return db.get(query, params) as BookCoverQuery;
        } catch (error: unknown) {
            if (error instanceof Error) {
                console.error(`DB Error: ${error.message}`);
                throw new Error(lang === 'fr' ? 'Impossible de récupérer la couverture du livre.' : 'Unable to retrieve book cover.');
            }
            console.error("An unknown error occurred.");
            throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
        }
    }

    /**
     * Updates a book's basic information.
     * @param userId - The user identifier
     * @param title - The new title
     * @param hashedTitle - The hashed title
     * @param subTitle - The new subtitle
     * @param hashedSubTitle - The hashed subtitle
     * @param summary - The new summary
     * @param publicationDate - The new publication date
     * @param wordCount - The new desired word count
     * @param bookId - The book identifier
     * @param lang - The language for error messages
     * @returns true if the update was successful
     */
    static updateBookBasicInformation(userId: string, title: string, hashedTitle: string, subTitle: string, hashedSubTitle: string, summary: string, publicationDate: string, wordCount: number, bookId: string, lang: 'fr' | 'en'): boolean {
        try {
            const db: Database = System.getDb();
            const query: string = 'UPDATE erit_books SET title=?, hashed_title=?, sub_title=?, hashed_sub_title=?, summary=?, serie_id=?, desired_release_date=?, desired_word_count=?, last_update=? WHERE author_id=? AND book_id=?';
            const params: SQLiteValue[] = [title, hashedTitle, subTitle, hashedSubTitle, summary, 0, publicationDate ? System.dateToMySqlDate(publicationDate) : null, wordCount, System.timeStampInSeconds(), userId, bookId];
            const updateResult: RunResult = db.run(query, params);
            return updateResult.changes > 0;
        } catch (error: unknown) {
            if (error instanceof Error) {
                console.error(`DB Error: ${error.message}`);
                throw new Error(lang === 'fr' ? 'Impossible de mettre à jour les informations du livre.' : 'Unable to update book information.');
            }
            console.error("An unknown error occurred.");
            throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
        }
    }

    /**
     * Deletes a book from the database.
     * @param userId - The user identifier
     * @param bookId - The book identifier to delete
     * @param lang - The language for error messages
     * @returns true if the deletion was successful
     */
    public static deleteBook(userId: string, bookId: string, lang: 'fr' | 'en'): boolean {
            console.log(`Deleting book with ID ${bookId} for user ${userId}`)
        try {
            const db: Database = System.getDb();
            const query: string = 'DELETE FROM erit_books WHERE author_id=? AND book_id=?';
            const params: SQLiteValue[] = [userId, bookId];
            const deleteResult: RunResult = db.run(query, params);
            return deleteResult.changes > 0;
        } catch (error: unknown) {
            if (error instanceof Error) {
                console.error(`DB Error: ${error.message}`);
                throw new Error(lang === 'fr' ? 'Impossible de supprimer le livre.' : 'Unable to delete book.');
            }
            console.error("An unknown error occurred.");
            throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
        }
    }

    /**
     * Retrieves all columns from erit_books table for a book.
     * @param userId - The user identifier
     * @param bookId - The book identifier
     * @param lang - The language for error messages
     * @returns The complete book data
     */
    static async fetchEritBooksTable(userId: string, bookId: string, lang: 'fr' | 'en'): Promise<EritBooksTable[]> {
        try {
            const db: Database = System.getDb();
            const query: string = 'SELECT book_id, type, author_id, title, hashed_title, sub_title, hashed_sub_title, summary, serie_id, desired_release_date, desired_word_count, words_count, cover_image, last_update FROM erit_books WHERE book_id=? AND author_id=?';
            const params: SQLiteValue[] = [bookId, userId];
            return db.all(query, params) as EritBooksTable[];
        } catch (error: unknown) {
            if (error instanceof Error) {
                console.error(`DB Error: ${error.message}`);
                throw new Error(lang === 'fr' ? 'Impossible de récupérer les informations du livre.' : 'Unable to retrieve book information.');
            }
            console.error("An unknown error occurred.");
            throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
        }
    }

    /**
     * Retrieves synced books for a user.
     * @param userId - The user identifier
     * @param lang - The language for error messages
     * @returns List of books with sync information
     */
    static fetchSyncedBooks(userId: string, lang: 'fr' | 'en'): SyncedBookResult[] {
        try {
            const db: Database = System.getDb();
            const query: string = 'SELECT book_id, type, title, sub_title, last_update FROM erit_books WHERE author_id = ?';
            const params: SQLiteValue[] = [userId];
            return db.all(query, params) as SyncedBookResult[];
        } catch (error: unknown) {
            if (error instanceof Error) {
                console.error(`DB Error: ${error.message}`);
                throw new Error(lang === 'fr' ? 'Impossible de récupérer les livres synchronisés.' : 'Unable to retrieve synced books.');
            }
            console.error("An unknown error occurred.");
            throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
        }
    }

    /**
     * Inserts a synced book from the server.
     * @param bookId - The book identifier
     * @param userId - The user identifier
     * @param type - The book type
     * @param title - The encrypted title
     * @param hashedTitle - The hashed title
     * @param subTitle - The encrypted subtitle
     * @param hashedSubTitle - The hashed subtitle
     * @param summary - The encrypted summary
     * @param serieId - The series identifier
     * @param desiredReleaseDate - The desired release date
     * @param desiredWordCount - The desired word count
     * @param wordsCount - The current word count
     * @param coverImage - The cover image file name
     * @param lastUpdate - The last update timestamp
     * @param lang - The language for error messages
     * @returns true if the insertion was successful
     */
    static insertSyncBook(bookId: string, userId: string, type: string, title: string, hashedTitle: string, subTitle: string | null, hashedSubTitle: string | null, summary: string | null, serieId: number | null, desiredReleaseDate: string | null, desiredWordCount: number | null, wordsCount: number | null, coverImage: string | null, lastUpdate: number, lang: 'fr' | 'en'): boolean {
        try {
            const db: Database = System.getDb();
            const query: string = 'INSERT INTO erit_books (book_id, author_id, type, title, hashed_title, sub_title, hashed_sub_title, summary, serie_id, desired_release_date, desired_word_count, words_count, cover_image, last_update) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
            const params: SQLiteValue[] = [bookId, userId, type, title, hashedTitle, subTitle, hashedSubTitle, summary, serieId, desiredReleaseDate, desiredWordCount, wordsCount, coverImage, lastUpdate];
            const insertResult: RunResult = db.run(query, params);
            return insertResult.changes > 0;
        } catch (error: unknown) {
            if (error instanceof Error) {
                console.error(`DB Error: ${error.message}`);
                throw new Error(lang === 'fr' ? "Impossible d'insérer le livre synchronisé." : 'Unable to insert synced book.');
            }
            throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
        }
    }

    /**
     * Retrieves a complete book by its identifier (without author verification).
     * @param bookId - The book identifier
     * @param lang - The language for error messages
     * @returns The complete book data
     */
    static async fetchCompleteBookById(bookId: string, lang: 'fr' | 'en'): Promise<EritBooksTable[]> {
        try {
            const db: Database = System.getDb();
            const query: string = 'SELECT * FROM erit_books WHERE book_id = ?';
            const params: SQLiteValue[] = [bookId];
            return db.all(query, params) as EritBooksTable[];
        } catch (error: unknown) {
            if (error instanceof Error) {
                throw new Error(lang === 'fr' ? 'Impossible de récupérer le livre complet.' : 'Unable to retrieve complete book.');
            }
            throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
        }
    }

    static fetchBookTools(userId: string, bookId: string, lang: 'fr' | 'en'): BookToolsTable | null {
        try {
            const db: Database = System.getDb();
            const query: string = 'SELECT book_id, user_id, characters_enabled, worlds_enabled, locations_enabled, spells_enabled, last_update FROM book_tools WHERE user_id=? AND book_id=?';
            const params: SQLiteValue[] = [userId, bookId];
            const result = db.get(query, params) as BookToolsTable | undefined;
            return result ?? null;
        } catch (error: unknown) {
            if (error instanceof Error) {
                console.error(`DB Error: ${error.message}`);
                throw new Error(lang === 'fr' ? 'Impossible de récupérer les paramètres des outils.' : 'Unable to fetch tools settings.');
            }
            throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
        }
    }

    static updateBookToolSetting(userId: string, bookId: string, toolName: 'characters_enabled' | 'worlds_enabled' | 'locations_enabled' | 'spells_enabled', enabled: boolean, lastUpdate: number, lang: 'fr' | 'en'): boolean {
        const enabledValue: number = enabled ? 1 : 0;
        try {
            const db: Database = System.getDb();
            const updateQuery: string = `UPDATE book_tools SET ${toolName}=?, last_update=? WHERE user_id=? AND book_id=?`;
            const updateResult: RunResult = db.run(updateQuery, [enabledValue, lastUpdate, userId, bookId]);
            if (updateResult.changes > 0) {
                return true;
            }
            const charactersValue: number = toolName === 'characters_enabled' ? enabledValue : 0;
            const worldsValue: number = toolName === 'worlds_enabled' ? enabledValue : 0;
            const locationsValue: number = toolName === 'locations_enabled' ? enabledValue : 0;
            const spellsValue: number = toolName === 'spells_enabled' ? enabledValue : 0;
            const insertQuery: string = 'INSERT INTO book_tools (book_id, user_id, characters_enabled, worlds_enabled, locations_enabled, spells_enabled, last_update) VALUES (?, ?, ?, ?, ?, ?, ?)';
            const insertResult: RunResult = db.run(insertQuery, [bookId, userId, charactersValue, worldsValue, locationsValue, spellsValue, lastUpdate]);
            return insertResult.changes > 0;
        } catch (error: unknown) {
            if (error instanceof Error) {
                console.error(`DB Error: ${error.message}`);
                throw new Error(lang === 'fr' ? 'Impossible de mettre à jour les paramètres des outils.' : 'Unable to update tools settings.');
            }
            throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
        }
    }

    /**
     * Upserts book tools settings during sync.
     * Inserts if not exists, updates if exists.
     */
    static insertSyncBookTools(bookId: string, userId: string, charactersEnabled: number, worldsEnabled: number, locationsEnabled: number, spellsEnabled: number, lastUpdate: number, lang: 'fr' | 'en'): boolean {
        try {
            const db: Database = System.getDb();
            const query: string = 'INSERT INTO book_tools (book_id, user_id, characters_enabled, worlds_enabled, locations_enabled, spells_enabled, last_update) VALUES (?, ?, ?, ?, ?, ?, ?) ON CONFLICT (book_id, user_id) DO UPDATE SET characters_enabled = excluded.characters_enabled, worlds_enabled = excluded.worlds_enabled, locations_enabled = excluded.locations_enabled, spells_enabled = excluded.spells_enabled, last_update = excluded.last_update';
            const params: SQLiteValue[] = [bookId, userId, charactersEnabled, worldsEnabled, locationsEnabled, spellsEnabled, lastUpdate];
            db.run(query, params);
            return true;
        } catch (error: unknown) {
            if (error instanceof Error) {
                console.error(`[BookRepository] DB Error: ${error.message}`);
            }
            return false;
        }
    }

    static fetchSyncedBookTools(userId: string, bookId: string, lang: 'fr' | 'en'): SyncedBookToolsResult | null {
        try {
            const db: Database = System.getDb();
            const query: string = 'SELECT last_update, characters_enabled, worlds_enabled, locations_enabled, spells_enabled FROM book_tools WHERE user_id = ? AND book_id = ?';
            const params: SQLiteValue[] = [userId, bookId];
            const result = db.get(query, params) as SyncedBookToolsResult | undefined;
            return result ?? null;
        } catch (error: unknown) {
            if (error instanceof Error) {
                console.error(`DB Error: ${error.message}`);
                throw new Error(lang === 'fr' ? 'Impossible de récupérer les paramètres des outils.' : 'Unable to fetch tools settings.');
            }
            throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
        }
    }

    static isBookExist(userId: string, bookId: string, lang: 'fr' | 'en'): boolean {
        try {
            const db: Database = System.getDb();
            const query: string = 'SELECT 1 FROM erit_books WHERE author_id = ? AND book_id = ? LIMIT 1';
            const params: SQLiteValue[] = [userId, bookId];
            const result = db.get(query, params);
            return result !== undefined && result !== null;
        } catch (error: unknown) {
            if (error instanceof Error) {
                console.error(`DB Error: ${error.message}`);
            }
            return false;
        }
    }

    /**
     * Retrieves the series_id for a book from series_books table.
     * @param bookId - The book identifier
     * @param lang - The language for error messages
     * @returns The series_id or null if book is not in a series
     */
    static fetchBookSeriesId(bookId: string, lang: 'fr' | 'en'): string | null {
        try {
            const db: Database = System.getDb();
            const query: string = 'SELECT series_id FROM series_books WHERE book_id = ? LIMIT 1';
            const params: SQLiteValue[] = [bookId];
            const result = db.get(query, params) as { series_id: string } | undefined;
            return result?.series_id || null;
        } catch (error: unknown) {
            if (error instanceof Error) {
                console.error(`DB Error: ${error.message}`);
            }
            return null;
        }
    }
}