use rusqlite::{params, Connection, OptionalExtension};

use crate::error::{AppError, AppResult};
use crate::shared::types::Lang;

pub struct BookIncidentsTable {
    pub incident_id: String,
    pub author_id: String,
    pub book_id: String,
    pub title: String,
    pub hashed_title: String,
    pub summary: Option<String>,
    pub last_update: i64,
}

pub struct SyncedIncidentResult {
    pub incident_id: String,
    pub book_id: String,
    pub title: String,
    pub last_update: i64,
}

pub struct IncidentQuery {
    pub incident_id: String,
    pub title: String,
    pub summary: String,
}

/// Fetches all incidents for a specific book belonging to a user.
/// * `conn` - Database connection
/// * `user_id` - The ID of the user (author)
/// * `book_id` - The ID of the book
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns an array of incidents with their ID, title, and summary.
/// Errors if the database query fails.
pub fn fetch_all_incitent_incidents(conn: &Connection, user_id: &str, book_id: &str, lang: Lang) -> AppResult<Vec<IncidentQuery>> {
    let mut statement = conn
        .prepare("SELECT incident_id, title, summary FROM book_incidents WHERE author_id=?1 AND book_id=?2")
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les incidents.".to_string() } else { "Unable to retrieve incidents.".to_string() }))?;

    let incidents = statement
        .query_map(params![user_id, book_id], |query_row| {
            Ok(IncidentQuery { incident_id: query_row.get(0)?, title: query_row.get(1)?, summary: query_row.get(2)? })
        })
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les incidents.".to_string() } else { "Unable to retrieve incidents.".to_string() }))?
        .collect::<Result<Vec<_>, _>>()
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les incidents.".to_string() } else { "Unable to retrieve incidents.".to_string() }))?;

    Ok(incidents)
}

/// Inserts a new incident into the database.
/// * `conn` - Database connection
/// * `incident_id` - The unique ID for the new incident
/// * `user_id` - The ID of the user (author)
/// * `book_id` - The ID of the book
/// * `encrypted_name` - The encrypted title of the incident
/// * `hashed_name` - The hashed title of the incident
/// * `last_update` - The timestamp of the last update
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns the incident ID if insertion was successful.
/// Errors if the database insertion fails.
pub fn insert_new_incident(
    conn: &Connection, incident_id: &str, user_id: &str, book_id: &str,
    encrypted_name: &str, hashed_name: &str, last_update: i64, lang: Lang,
) -> AppResult<String> {
    let insert_result = conn
        .execute(
            "INSERT INTO book_incidents (incident_id, author_id, book_id, title, hashed_title, last_update) VALUES (?1,?2,?3,?4,?5,?6)",
            params![incident_id, user_id, book_id, encrypted_name, hashed_name, last_update],
        )
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible d'ajouter l'élément déclencheur.".to_string() } else { "Unable to add incident.".to_string() }))?;

    if insert_result > 0 {
        Ok(incident_id.to_string())
    } else {
        Err(AppError::Internal(if lang == Lang::Fr { "Une erreur s'est produite lors de l'ajout de l'élément déclencheur.".to_string() } else { "Error adding incident.".to_string() }))
    }
}

/// Deletes an incident from the database.
/// * `conn` - Database connection
/// * `user_id` - The ID of the user (author)
/// * `book_id` - The ID of the book
/// * `incident_id` - The ID of the incident to delete
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns true if the incident was deleted, false otherwise.
/// Errors if the database deletion fails.
pub fn delete_incident(conn: &Connection, user_id: &str, book_id: &str, incident_id: &str, lang: Lang) -> AppResult<bool> {
    let delete_result = conn
        .execute(
            "DELETE FROM book_incidents WHERE author_id=?1 AND book_id=?2 AND incident_id=?3",
            params![user_id, book_id, incident_id],
        )
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de supprimer l'élément déclencheur.".to_string() } else { "Unable to delete incident.".to_string() }))?;

    Ok(delete_result > 0)
}

/// Updates an existing incident in the database.
/// * `conn` - Database connection
/// * `user_id` - The ID of the user (author)
/// * `book_id` - The ID of the book
/// * `incident_id` - The ID of the incident to update
/// * `encrypted_incident_name` - The new encrypted title
/// * `incident_hashed_name` - The new hashed title
/// * `incident_summary` - The new summary
/// * `last_update` - The timestamp of the last update
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns true if the incident was updated, false otherwise.
/// Errors if the database update fails.
pub fn update_incident(
    conn: &Connection, user_id: &str, book_id: &str, incident_id: &str,
    encrypted_incident_name: &str, incident_hashed_name: &str, incident_summary: &str, last_update: i64, lang: Lang,
) -> AppResult<bool> {
    let update_result = conn
        .execute(
            "UPDATE book_incidents SET title=?1, hashed_title=?2, summary=?3, last_update=?4 WHERE author_id=?5 AND book_id=?6 AND incident_id=?7",
            params![encrypted_incident_name, incident_hashed_name, incident_summary, last_update, user_id, book_id, incident_id],
        )
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de mettre à jour l'incident.".to_string() } else { "Unable to update incident.".to_string() }))?;

    Ok(update_result > 0)
}

/// Fetches all incidents for a book with complete information.
/// * `conn` - Database connection
/// * `user_id` - The ID of the user (author)
/// * `book_id` - The ID of the book
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns an array of complete incident records.
/// Errors if the database query fails.
pub fn fetch_book_incidents(conn: &Connection, user_id: &str, book_id: &str, lang: Lang) -> AppResult<Vec<BookIncidentsTable>> {
    let mut statement = conn
        .prepare("SELECT incident_id, author_id, book_id, title, hashed_title, summary, last_update FROM book_incidents WHERE author_id=?1 AND book_id=?2")
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les incidents.".to_string() } else { "Unable to retrieve incidents.".to_string() }))?;

    let incidents = statement
        .query_map(params![user_id, book_id], |query_row| {
            Ok(BookIncidentsTable {
                incident_id: query_row.get(0)?, author_id: query_row.get(1)?,
                book_id: query_row.get(2)?, title: query_row.get(3)?,
                hashed_title: query_row.get(4)?, summary: query_row.get(5)?,
                last_update: query_row.get(6)?,
            })
        })
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les incidents.".to_string() } else { "Unable to retrieve incidents.".to_string() }))?
        .collect::<Result<Vec<_>, _>>()
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les incidents.".to_string() } else { "Unable to retrieve incidents.".to_string() }))?;

    Ok(incidents)
}

/// Fetches all synced incidents for a user across all books.
/// * `conn` - Database connection
/// * `user_id` - The ID of the user (author)
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns an array of synced incident records with minimal information.
/// Errors if the database query fails.
pub fn fetch_synced_incidents(conn: &Connection, user_id: &str, lang: Lang) -> AppResult<Vec<SyncedIncidentResult>> {
    let mut statement = conn
        .prepare("SELECT incident_id, book_id, title, last_update FROM book_incidents WHERE author_id = ?1")
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les incidents synchronisés.".to_string() } else { "Unable to retrieve synced incidents.".to_string() }))?;

    let synced_incidents = statement
        .query_map(params![user_id], |query_row| {
            Ok(SyncedIncidentResult { incident_id: query_row.get(0)?, book_id: query_row.get(1)?, title: query_row.get(2)?, last_update: query_row.get(3)? })
        })
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les incidents synchronisés.".to_string() } else { "Unable to retrieve synced incidents.".to_string() }))?
        .collect::<Result<Vec<_>, _>>()
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les incidents synchronisés.".to_string() } else { "Unable to retrieve synced incidents.".to_string() }))?;

    Ok(synced_incidents)
}

/// Inserts a synced incident into the database.
/// * `conn` - Database connection
/// * `incident_id` - The unique ID for the incident
/// * `author_id` - The ID of the author
/// * `book_id` - The ID of the book
/// * `title` - The encrypted title
/// * `hashed_title` - The hashed title
/// * `summary` - The encrypted summary (can be null)
/// * `last_update` - The timestamp of the last update
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns true if the incident was inserted, false otherwise.
/// Errors if the database insertion fails.
pub fn insert_sync_incident(
    conn: &Connection, incident_id: &str, author_id: &str, book_id: &str,
    title: &str, hashed_title: &str, summary: Option<&str>, last_update: i64, lang: Lang,
) -> AppResult<bool> {
    let insert_result = conn
        .execute(
            "INSERT INTO book_incidents (incident_id, author_id, book_id, title, hashed_title, summary, last_update) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
            params![incident_id, author_id, book_id, title, hashed_title, summary, last_update],
        )
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible d'insérer l'incident.".to_string() } else { "Unable to insert incident.".to_string() }))?;

    Ok(insert_result > 0)
}

/// Fetches complete incident information by its ID.
/// * `conn` - Database connection
/// * `incident_id` - The ID of the incident to fetch
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns an array containing the incident record (empty if not found).
/// Errors if the database query fails.
pub fn fetch_complete_incident_by_id(conn: &Connection, incident_id: &str, lang: Lang) -> AppResult<Vec<BookIncidentsTable>> {
    let mut statement = conn
        .prepare("SELECT incident_id, author_id, book_id, title, hashed_title, summary, last_update FROM book_incidents WHERE incident_id = ?1")
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer l'incident complet.".to_string() } else { "Unable to retrieve complete incident.".to_string() }))?;

    let incident = statement
        .query_map(params![incident_id], |query_row| {
            Ok(BookIncidentsTable {
                incident_id: query_row.get(0)?, author_id: query_row.get(1)?,
                book_id: query_row.get(2)?, title: query_row.get(3)?,
                hashed_title: query_row.get(4)?, summary: query_row.get(5)?,
                last_update: query_row.get(6)?,
            })
        })
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer l'incident complet.".to_string() } else { "Unable to retrieve complete incident.".to_string() }))?
        .collect::<Result<Vec<_>, _>>()
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer l'incident complet.".to_string() } else { "Unable to retrieve complete incident.".to_string() }))?;

    Ok(incident)
}

/// Checks if an incident exists in the database.
/// * `conn` - Database connection
/// * `user_id` - The ID of the user (author)
/// * `book_id` - The ID of the book
/// * `incident_id` - The ID of the incident to check
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns true if the incident exists, false otherwise.
/// Errors if the database query fails.
pub fn incident_exist(conn: &Connection, user_id: &str, book_id: &str, incident_id: &str, lang: Lang) -> AppResult<bool> {
    let mut statement = conn
        .prepare("SELECT 1 FROM book_incidents WHERE book_id=?1 AND incident_id=?2 AND author_id=?3")
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de vérifier l'existence de l'incident.".to_string() } else { "Unable to check incident existence.".to_string() }))?;

    let existing_incident = statement
        .query_row(params![book_id, incident_id, user_id], |query_row| query_row.get::<_, i64>(0))
        .optional()
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de vérifier l'existence de l'incident.".to_string() } else { "Unable to check incident existence.".to_string() }))?;

    Ok(existing_incident.is_some())
}
