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

export interface BookIncidentsTable extends Record<string, SQLiteValue> {
    incident_id: string;
    author_id: string;
    book_id: string;
    title: string;
    hashed_title: string;
    summary: string | null;
    last_update: number;
}

export interface SyncedIncidentResult extends Record<string, SQLiteValue> {
    incident_id: string;
    book_id: string;
    title: string;
    last_update: number;
}

export interface IncidentQuery extends Record<string, SQLiteValue> {
    incident_id: string;
    title: string;
    summary: string;
}

export default class IncidentRepository {
    /**
     * Fetches all incidents for a specific book belonging to a user.
     * @param userId - The ID of the user (author)
     * @param bookId - The ID of the book
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns An array of incidents with their ID, title, and summary
     * @throws Error if the database query fails
     */
    public static fetchAllIncitentIncidents(userId: string, bookId: string, lang: 'fr' | 'en'): IncidentQuery[] {
        try {
            const db: Database = System.getDb();
            const query: string = 'SELECT incident_id, title, summary FROM book_incidents WHERE author_id=? AND book_id=?';
            const params: SQLiteValue[] = [userId, bookId];
            const incidents: IncidentQuery[] = db.all(query, params) as IncidentQuery[];
            return incidents;
        } catch (error: unknown) {
            if (error instanceof Error) {
                console.error(`DB Error: ${error.message}`);
                throw new Error(lang === 'fr' ? `Impossible de récupérer les incidents.` : `Unable to retrieve incidents.`);
            } 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 incident into the database.
     * @param incidentId - The unique ID for the new incident
     * @param userId - The ID of the user (author)
     * @param bookId - The ID of the book
     * @param encryptedName - The encrypted title of the incident
     * @param hashedName - The hashed title of the incident
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns The incident ID if insertion was successful
     * @throws Error if the database insertion fails
     */
    public static insertNewIncident(incidentId: string, userId: string, bookId: string, encryptedName: string, hashedName: string, lang: 'fr' | 'en'): string {
        let insertResult: RunResult;
        try {
            const db: Database = System.getDb();
            const query: string = 'INSERT INTO book_incidents (incident_id,author_id, book_id, title, hashed_title, last_update) VALUES (?,?,?,?,?,?)';
            const params: SQLiteValue[] = [incidentId, userId, bookId, encryptedName, hashedName, 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 l'élément déclencheur.` : `Unable to add incident.`);
            } 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 l'ajout de l'élément déclencheur.` : `Error adding incident.`);
        }
        return incidentId;
    }

    /**
     * Deletes an incident from the database.
     * @param userId - The ID of the user (author)
     * @param bookId - The ID of the book
     * @param incidentId - The ID of the incident to delete
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns True if the incident was deleted, false otherwise
     * @throws Error if the database deletion fails
     */
    public static deleteIncident(userId: string, bookId: string, incidentId: string, lang: 'fr' | 'en'): boolean {
        try {
            const db: Database = System.getDb();
            const query: string = 'DELETE FROM book_incidents WHERE author_id=? AND book_id=? AND incident_id=?';
            const params: SQLiteValue[] = [userId, bookId, incidentId];
            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 l'élément déclencheur.` : `Unable to delete incident.`);
            } else {
                console.error("An unknown error occurred.");
                throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
            }
        }
    }

    /**
     * Updates an existing incident in the database.
     * @param userId - The ID of the user (author)
     * @param bookId - The ID of the book
     * @param incidentId - The ID of the incident to update
     * @param encryptedIncidentName - The new encrypted title
     * @param incidentHashedName - The new hashed title
     * @param incidentSummary - The new summary
     * @param lastUpdate - The timestamp of the last update
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns True if the incident was updated, false otherwise
     * @throws Error if the database update fails
     */
    public static updateIncident(userId: string, bookId: string, incidentId: string, encryptedIncidentName: string, incidentHashedName: string, incidentSummary: string, lastUpdate: number, lang: 'fr' | 'en'): boolean {
        try {
            const db: Database = System.getDb();
            const query: string = 'UPDATE book_incidents SET title=?, hashed_title=?, summary=?, last_update=? WHERE author_id=? AND book_id=? AND incident_id=?';
            const params: SQLiteValue[] = [encryptedIncidentName, incidentHashedName, incidentSummary, lastUpdate, userId, bookId, incidentId];
            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 l'incident.` : `Unable to update incident.`);
            } else {
                console.error("An unknown error occurred.");
                throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
            }
        }
    }

    /**
     * Fetches all incidents for a book with complete information.
     * @param userId - The ID of the user (author)
     * @param bookId - The ID of the book
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns An array of complete incident records
     * @throws Error if the database query fails
     */
    static async fetchBookIncidents(userId: string, bookId: string, lang: 'fr' | 'en'): Promise<BookIncidentsTable[]> {
        try {
            const db: Database = System.getDb();
            const query: string = 'SELECT incident_id, author_id, book_id, title, hashed_title, summary, last_update FROM book_incidents WHERE author_id=? AND book_id=?';
            const params: SQLiteValue[] = [userId, bookId];
            const incidents: BookIncidentsTable[] = db.all(query, params) as BookIncidentsTable[];
            return incidents;
        } catch (error: unknown) {
            if (error instanceof Error) {
                console.error(`DB Error: ${error.message}`);
                throw new Error(lang === 'fr' ? `Impossible de récupérer les incidents.` : `Unable to retrieve incidents.`);
            } else {
                throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
            }
        }
    }

    /**
     * Fetches all synced incidents for a user across all books.
     * @param userId - The ID of the user (author)
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns An array of synced incident records with minimal information
     * @throws Error if the database query fails
     */
    static fetchSyncedIncidents(userId: string, lang: 'fr' | 'en'): SyncedIncidentResult[] {
        try {
            const db: Database = System.getDb();
            const query: string = 'SELECT incident_id, book_id, title, last_update FROM book_incidents WHERE author_id = ?';
            const params: SQLiteValue[] = [userId];
            const syncedIncidents: SyncedIncidentResult[] = db.all(query, params) as SyncedIncidentResult[];
            return syncedIncidents;
        } catch (error: unknown) {
            if (error instanceof Error) {
                console.error(`DB Error: ${error.message}`);
                throw new Error(lang === 'fr' ? `Impossible de récupérer les incidents synchronisés.` : `Unable to retrieve synced incidents.`);
            } else {
                console.error("An unknown error occurred.");
                throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
            }
        }
    }

    /**
     * Inserts a synced incident into the database.
     * @param incidentId - The unique ID for the incident
     * @param authorId - The ID of the author
     * @param bookId - The ID of the book
     * @param title - The encrypted title
     * @param hashedTitle - The hashed title
     * @param summary - The encrypted summary (can be null)
     * @param lastUpdate - The timestamp of the last update
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns True if the incident was inserted, false otherwise
     * @throws Error if the database insertion fails
     */
    static insertSyncIncident(incidentId: string, authorId: string, bookId: string, title: string, hashedTitle: string, summary: string | null, lastUpdate: number, lang: 'fr' | 'en'): boolean {
        try {
            const db: Database = System.getDb();
            const query: string = `INSERT INTO book_incidents (incident_id, author_id, book_id, title, hashed_title, summary, last_update)
                 VALUES (?, ?, ?, ?, ?, ?, ?)`;
            const params: SQLiteValue[] = [incidentId, authorId, bookId, title, hashedTitle, summary, 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 l'incident.` : `Unable to insert incident.`);
            } else {
                throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
            }
        }
    }

    /**
     * Fetches complete incident information by its ID.
     * @param id - The ID of the incident to fetch
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns An array containing the incident record (empty if not found)
     * @throws Error if the database query fails
     */
    static async fetchCompleteIncidentById(id: string, lang: "fr" | "en"): Promise<BookIncidentsTable[]> {
        try {
            const db: Database = System.getDb();
            const query: string = `SELECT incident_id, author_id, book_id, title, hashed_title, summary, last_update
                 FROM book_incidents
                 WHERE incident_id = ?`;
            const params: SQLiteValue[] = [id];
            const incident: BookIncidentsTable[] = db.all(query, params) as BookIncidentsTable[];
            return incident;
        } catch (error: unknown) {
            if (error instanceof Error) {
                throw new Error(lang === 'fr' ? `Impossible de récupérer l'incident complet.` : `Unable to retrieve complete incident.`);
            } else {
                throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
            }
        }
    }

    /**
     * Checks if an incident exists in the database.
     * @param userId - The ID of the user (author)
     * @param bookId - The ID of the book
     * @param incidentId - The ID of the incident to check
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns True if the incident exists, false otherwise
     * @throws Error if the database query fails
     */
    static incidentExist(userId: string, bookId: string, incidentId: string, lang: "fr" | "en"): boolean {
        try {
            const db: Database = System.getDb();
            const query: string = 'SELECT 1 FROM book_incidents WHERE book_id=? AND incident_id=? AND author_id=?';
            const params: SQLiteValue[] = [bookId, incidentId, userId];
            const existingIncident: QueryResult | null = db.get(query, params) || null;
            return existingIncident !== 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 l'incident.` : `Unable to check incident existence.`);
            } else {
                throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
            }
        }
    }
}
