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

export interface SeriesResult extends Record<string, SQLiteValue> {
    series_id: string;
    user_id: string;
    name: string;
    hashed_name: string;
    description: string | null;
    cover_image: string | null;
    last_update: number;
}

export interface SeriesBookResult extends Record<string, SQLiteValue> {
    series_id: string;
    book_id: string;
    book_order: number;
    title: string;
    cover_image: string | null;
}

export interface SeriesListItem extends Record<string, SQLiteValue> {
    series_id: string;
    name: string;
    description: string | null;
    cover_image: string | null;
    book_count: number;
    book_ids: string | null;
}

export interface SeriesTableResult extends Record<string, SQLiteValue> {
    series_id: string;
    user_id: string;
    name: string;
    hashed_name: string;
    description: string | null;
    cover_image: string | null;
    last_update: number;
}

export interface SeriesBooksTableResult extends Record<string, SQLiteValue> {
    series_id: string;
    book_id: string;
    book_order: number;
    last_update: number;
}

export interface SyncedSeriesResult extends Record<string, SQLiteValue> {
    series_id: string;
    name: string;
    description: string | null;
    last_update: number;
}

export interface SyncedSeriesBookResult extends Record<string, SQLiteValue> {
    series_id: string;
    book_id: string;
    book_order: number;
    last_update: number;
}

export default class SeriesRepo {
    /**
     * Fetches all series 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 series with book counts
     */
    public static fetchUserSeries(userId: string, lang: 'fr' | 'en' = 'fr'): SeriesListItem[] {
        try {
            const db: Database = System.getDb();
            const query: string = 'SELECT series.series_id, series.name, series.description, series.cover_image, COUNT(series_books.book_id) AS book_count, GROUP_CONCAT(series_books.book_id) AS book_ids FROM book_series series LEFT JOIN series_books ON series.series_id = series_books.series_id WHERE series.user_id = ? GROUP BY series.series_id, series.last_update ORDER BY series.last_update DESC';
            const series: SeriesListItem[] = db.all(query, [userId]) as SeriesListItem[];
            return series;
        } catch (error: unknown) {
            if (error instanceof Error) {
                console.error(`DB Error: ${error.message}`);
                throw new Error(lang === 'fr' ? `Impossible de récupérer les séries.` : `Unable to retrieve series.`);
            } else {
                console.error("An unknown error occurred.");
                throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
            }
        }
    }

    /**
     * Fetches a single series by its ID.
     * @param userId - The unique identifier of the user
     * @param seriesId - The unique identifier of the series
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns The series result or null if not found
     */
    public static fetchSeriesById(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesResult | null {
        try {
            const db: Database = System.getDb();
            const query: string = 'SELECT series_id, user_id, name, hashed_name, description, cover_image, last_update FROM book_series WHERE series_id = ? AND user_id = ?';
            const series: SeriesResult | undefined = db.get(query, [seriesId, userId]) as SeriesResult | undefined;
            return series || null;
        } catch (error: unknown) {
            if (error instanceof Error) {
                console.error(`DB Error: ${error.message}`);
                throw new Error(lang === 'fr' ? `Impossible de récupérer la série.` : `Unable to retrieve series.`);
            } else {
                console.error("An unknown error occurred.");
                throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
            }
        }
    }

    /**
     * Inserts a new series.
     * @param seriesId - The unique identifier for the new series
     * @param userId - The unique identifier of the user
     * @param name - The encrypted name
     * @param hashedName - The hashed name for duplicate detection
     * @param description - The encrypted description (nullable)
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns The series ID if successful
     */
    public static insertSeries(seriesId: string, userId: string, name: string, hashedName: string, description: string | null, lang: 'fr' | 'en' = 'fr'): string {
        let insertResult: RunResult;
        try {
            const db: Database = System.getDb();
            const query: string = 'INSERT INTO book_series (series_id, user_id, name, hashed_name, description, last_update) VALUES (?, ?, ?, ?, ?, ?)';
            const params: SQLiteValue[] = [seriesId, userId, name, hashedName, description, 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 de créer la série.` : `Unable to create series.`);
            } else {
                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' ? `Une erreur s'est produite lors de la création de la série.` : `Error creating series.`);
        }
        return seriesId;
    }

    /**
     * Updates an existing series.
     * @param userId - The unique identifier of the user
     * @param seriesId - The unique identifier of the series
     * @param name - The encrypted name
     * @param hashedName - The hashed name
     * @param description - The encrypted description (nullable)
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns True if the update was successful
     */
    public static updateSeries(userId: string, seriesId: string, name: string, hashedName: string, description: string | null, lang: 'fr' | 'en' = 'fr'): boolean {
        try {
            const db: Database = System.getDb();
            const query: string = 'UPDATE book_series SET name = ?, hashed_name = ?, description = ?, last_update = ? WHERE series_id = ? AND user_id = ?';
            const params: SQLiteValue[] = [name, hashedName, description, System.timeStampInSeconds(), seriesId, 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 série.` : `Unable to update series.`);
            } else {
                console.error("An unknown error occurred.");
                throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
            }
        }
    }

    /**
     * Deletes a series.
     * @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 True if the deletion was successful
     */
    public static deleteSeries(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): boolean {
        try {
            const db: Database = System.getDb();
            const query: string = 'DELETE FROM book_series WHERE series_id = ? AND user_id = ?';
            const params: SQLiteValue[] = [seriesId, userId];
            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 la série.` : `Unable to delete series.`);
            } else {
                console.error("An unknown error occurred.");
                throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
            }
        }
    }

    /**
     * Fetches all books in a series with their order.
     * @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 An array of books in the series
     */
    public static fetchSeriesBooks(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesBookResult[] {
        try {
            const db: Database = System.getDb();
            const query: string = 'SELECT sb.series_id, sb.book_id, sb.book_order, b.title, b.cover_image FROM series_books sb INNER JOIN erit_books b ON sb.book_id = b.book_id WHERE sb.series_id = ? AND b.author_id = ? ORDER BY sb.book_order';
            const books: SeriesBookResult[] = db.all(query, [seriesId, userId]) as SeriesBookResult[];
            return books;
        } 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 de la série.` : `Unable to retrieve series books.`);
            } else {
                console.error("An unknown error occurred.");
                throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
            }
        }
    }

    /**
     * Adds a book to a series.
     * @param seriesId - The unique identifier of the series
     * @param bookId - The unique identifier of the book
     * @param bookOrder - The order of the book in the series
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns True if the addition was successful
     */
    public static addBookToSeries(seriesId: string, bookId: string, bookOrder: number, lang: 'fr' | 'en' = 'fr'): boolean {
        try {
            const db: Database = System.getDb();
            const query: string = 'INSERT INTO series_books (series_id, book_id, book_order, last_update) VALUES (?, ?, ?, ?) ON CONFLICT(series_id, book_id) DO UPDATE SET book_order = excluded.book_order, last_update = excluded.last_update';
            const params: SQLiteValue[] = [seriesId, bookId, bookOrder, System.timeStampInSeconds()];
            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'ajouter le livre à la série.` : `Unable to add book to series.`);
            } else {
                console.error("An unknown error occurred.");
                throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
            }
        }
    }

    /**
     * Removes a book from a series.
     * @param seriesId - The unique identifier of the series
     * @param bookId - The unique identifier of the book
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns True if the removal was successful
     */
    public static removeBookFromSeries(seriesId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): boolean {
        try {
            const db: Database = System.getDb();
            const query: string = 'DELETE FROM series_books WHERE series_id = ? AND book_id = ?';
            const params: SQLiteValue[] = [seriesId, 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 retirer le livre de la série.` : `Unable to remove book from series.`);
            } else {
                console.error("An unknown error occurred.");
                throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
            }
        }
    }

    /**
     * Updates the order of books in a series.
     * @param seriesId - The unique identifier of the series
     * @param booksOrder - An array of {bookId, order} objects
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns True if the update was successful
     */
    public static updateBooksOrder(seriesId: string, booksOrder: {bookId: string, order: number}[], lang: 'fr' | 'en' = 'fr'): boolean {
        try {
            const db: Database = System.getDb();
            const timestamp: number = System.timeStampInSeconds();
            for (const bookOrder of booksOrder) {
                const query: string = 'UPDATE series_books SET book_order = ?, last_update = ? WHERE series_id = ? AND book_id = ?';
                db.run(query, [bookOrder.order, timestamp, seriesId, bookOrder.bookId]);
            }
            return true;
        } catch (error: unknown) {
            if (error instanceof Error) {
                console.error(`DB Error: ${error.message}`);
                throw new Error(lang === 'fr' ? `Impossible de réordonner les livres.` : `Unable to reorder books.`);
            } else {
                console.error("An unknown error occurred.");
                throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
            }
        }
    }

    /**
     * Checks if a series exists for a user.
     * @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 True if the series exists
     */
    public static isSeriesExist(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): boolean {
        try {
            const db: Database = System.getDb();
            const query: string = 'SELECT 1 FROM book_series WHERE series_id = ? AND user_id = ?';
            const params: SQLiteValue[] = [seriesId, userId];
            const result: QueryResult | null = db.get(query, params);
            return result !== 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 de la série.` : `Unable to check series existence.`);
            } else {
                console.error("An unknown error occurred.");
                throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
            }
        }
    }

    /**
     * Gets the series ID for a book if it belongs to one.
     * @param bookId - The unique identifier of the book
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns The series ID or null
     */
    public static getSeriesIdForBook(bookId: string, lang: 'fr' | 'en' = 'fr'): string | null {
        try {
            const db: Database = System.getDb();
            const query: string = 'SELECT series_id FROM series_books WHERE book_id = ?';
            const result = db.get(query, [bookId]) as { series_id: string } | undefined;
            return result ? result.series_id : null;
        } catch (error: unknown) {
            if (error instanceof Error) {
                console.error(`DB Error: ${error.message}`);
                throw new Error(lang === 'fr' ? `Impossible de vérifier la série du livre.` : `Unable to check book series.`);
            } else {
                console.error("An unknown error occurred.");
                throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
            }
        }
    }

    /**
     * Fetches a series table row for sync purposes.
     * @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 An array containing the series table row
     */
    public static fetchSeriesTableForSync(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesTableResult[] {
        try {
            const db: Database = System.getDb();
            const query: string = 'SELECT series_id, user_id, name, hashed_name, description, cover_image, last_update FROM book_series WHERE series_id = ? AND user_id = ?';
            const series: SeriesTableResult[] = db.all(query, [seriesId, userId]) as SeriesTableResult[];
            return series;
        } catch (error: unknown) {
            if (error instanceof Error) {
                console.error(`DB Error: ${error.message}`);
                throw new Error(lang === 'fr' ? `Impossible de récupérer la série pour sync.` : `Unable to retrieve series for sync.`);
            } else {
                throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
            }
        }
    }

    /**
     * Fetches all series-books relationships for sync.
     * @param seriesId - The unique identifier of the series
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns An array of series-books table rows
     */
    public static fetchSeriesBooksTable(seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesBooksTableResult[] {
        try {
            const db: Database = System.getDb();
            const query: string = 'SELECT series_id, book_id, book_order, last_update FROM series_books WHERE series_id = ? ORDER BY book_order';
            const books: SeriesBooksTableResult[] = db.all(query, [seriesId]) as SeriesBooksTableResult[];
            return books;
        } 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 de la série pour sync.` : `Unable to retrieve series books for sync.`);
            } else {
                throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
            }
        }
    }

    /**
     * Fetches all series for a user for sync comparison.
     * @param userId - The unique identifier of the user
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns An array of synced series results
     */
    public static fetchSyncedSeries(userId: string, lang: 'fr' | 'en' = 'fr'): SyncedSeriesResult[] {
        try {
            const db: Database = System.getDb();
            const query: string = 'SELECT series_id, name, description, last_update FROM book_series WHERE user_id = ? ORDER BY last_update DESC';
            const series: SyncedSeriesResult[] = db.all(query, [userId]) as SyncedSeriesResult[];
            return series;
        } catch (error: unknown) {
            if (error instanceof Error) {
                console.error(`DB Error: ${error.message}`);
                throw new Error(lang === 'fr' ? `Impossible de récupérer les séries pour sync.` : `Unable to retrieve series for sync.`);
            } else {
                throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
            }
        }
    }

    /**
     * Fetches all series-books relationships for a user for sync comparison.
     * @param userId - The unique identifier of the user
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns An array of synced series book results
     */
    public static fetchSyncedSeriesBooks(userId: string, lang: 'fr' | 'en' = 'fr'): SyncedSeriesBookResult[] {
        try {
            const db: Database = System.getDb();
            const query: string = 'SELECT sb.series_id, sb.book_id, sb.book_order, sb.last_update FROM series_books sb INNER JOIN book_series bs ON sb.series_id = bs.series_id WHERE bs.user_id = ? ORDER BY sb.book_order';
            const books: SyncedSeriesBookResult[] = db.all(query, [userId]) as SyncedSeriesBookResult[];
            return books;
        } 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 de séries pour sync.` : `Unable to retrieve series books for sync.`);
            } else {
                throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
            }
        }
    }

    /**
     * Fetches a complete series by ID for sync.
     * @param seriesId - The unique identifier of the series
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns An array containing the series
     */
    public static fetchCompleteSeriesById(seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesTableResult[] {
        try {
            const db: Database = System.getDb();
            const query: string = 'SELECT series_id, user_id, name, hashed_name, description, cover_image, last_update FROM book_series WHERE series_id = ?';
            const series: SeriesTableResult[] = db.all(query, [seriesId]) as SeriesTableResult[];
            return series;
        } catch (error: unknown) {
            if (error instanceof Error) {
                console.error(`DB Error: ${error.message}`);
                throw new Error(lang === 'fr' ? `Impossible de récupérer la série complète.` : `Unable to retrieve complete series.`);
            } else {
                throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
            }
        }
    }

    /**
     * Inserts a series for sync purposes.
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns True if the insertion was successful
     */
    public static insertSyncSeries(seriesId: string, userId: string, name: string, hashedName: string, description: string | null, coverImage: string | null, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean {
        try {
            const db: Database = System.getDb();
            const query: string = 'INSERT INTO book_series (series_id, user_id, name, hashed_name, description, cover_image, last_update) VALUES (?, ?, ?, ?, ?, ?, ?) ON CONFLICT(series_id) DO UPDATE SET name = excluded.name, hashed_name = excluded.hashed_name, description = excluded.description, cover_image = excluded.cover_image, last_update = excluded.last_update';
            const params: SQLiteValue[] = [seriesId, userId, name, hashedName, description, 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 la série pour sync.` : `Unable to insert series for sync.`);
            } else {
                throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
            }
        }
    }

    /**
     * Updates a series for sync purposes.
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns True if the update was successful
     */
    public static updateSyncSeries(userId: string, seriesId: string, name: string, hashedName: string, description: string | null, coverImage: string | null, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean {
        try {
            const db: Database = System.getDb();
            const query: string = 'UPDATE book_series SET name = ?, hashed_name = ?, description = ?, cover_image = ?, last_update = ? WHERE series_id = ? AND user_id = ?';
            const params: SQLiteValue[] = [name, hashedName, description, coverImage, lastUpdate, seriesId, 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 série pour sync.` : `Unable to update series for sync.`);
            } else {
                throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
            }
        }
    }

    /**
     * Inserts a series-book relationship for sync.
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns True if the insertion was successful
     */
    public static insertSyncSeriesBook(seriesId: string, bookId: string, bookOrder: number, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean {
        try {
            const db: Database = System.getDb();
            const query: string = 'INSERT INTO series_books (series_id, book_id, book_order, last_update) VALUES (?, ?, ?, ?) ON CONFLICT(series_id, book_id) DO UPDATE SET book_order = excluded.book_order, last_update = excluded.last_update';
            const params: SQLiteValue[] = [seriesId, bookId, bookOrder, 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 la liaison série-livre pour sync.` : `Unable to insert series-book for sync.`);
            } else {
                throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
            }
        }
    }

    /**
     * Checks if a series-book relationship exists.
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns True if the relationship exists
     */
    public static isSeriesBookExist(seriesId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): boolean {
        try {
            const db: Database = System.getDb();
            const query: string = 'SELECT 1 FROM series_books WHERE series_id = ? AND book_id = ?';
            const result: QueryResult | null = db.get(query, [seriesId, bookId]);
            return result !== null;
        } catch (error: unknown) {
            if (error instanceof Error) {
                console.error(`DB Error: ${error.message}`);
                throw new Error(lang === 'fr' ? `Impossible de vérifier la liaison série-livre.` : `Unable to check series-book.`);
            } else {
                throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
            }
        }
    }

    /**
     * Checks if a series exists for a user (alias for isSeriesExist).
     * @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 True if the series exists
     */
    public static seriesExists(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): boolean {
        return this.isSeriesExist(userId, seriesId, lang);
    }
}
