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

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

pub struct BookPlotPointsTable {
    pub plot_point_id: String,
    pub title: String,
    pub hashed_title: String,
    pub summary: Option<String>,
    pub linked_incident_id: Option<String>,
    pub author_id: String,
    pub _book_id: String,
    pub last_update: i64,
}

pub struct SyncedPlotPointResult {
    pub plot_point_id: String,
    pub book_id: String,
    pub title: String,
    pub last_update: i64,
}

pub struct PlotPointQuery {
    pub plot_point_id: String,
    pub title: String,
    pub summary: String,
    pub linked_incident_id: Option<String>,
}

/// Fetches all plot points for a specific book.
/// * `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 plot points with their basic information.
/// Errors if the plot points cannot be retrieved.
pub fn fetch_all_plot_points(conn: &Connection, user_id: &str, book_id: &str, lang: Lang) -> AppResult<Vec<PlotPointQuery>> {
    let mut statement = conn
        .prepare("SELECT plot_point_id, title, summary, linked_incident_id FROM book_plot_points WHERE author_id=?1 AND book_id=?2")
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les points d'intrigue.".to_string() } else { "Unable to retrieve plot points.".to_string() }))?;

    let plot_points = statement
        .query_map(params![user_id, book_id], |query_row| {
            Ok(PlotPointQuery {
                plot_point_id: query_row.get(0)?,
                title: query_row.get(1)?,
                summary: query_row.get::<_, Option<String>>(2)?.unwrap_or_default(),
                linked_incident_id: query_row.get(3)?,
            })
        })
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les points d'intrigue.".to_string() } else { "Unable to retrieve plot points.".to_string() }))?
        .collect::<Result<Vec<_>, _>>()
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les points d'intrigue.".to_string() } else { "Unable to retrieve plot points.".to_string() }))?;

    Ok(plot_points)
}

/// Inserts a new plot point into the database.
/// * `conn` - Database connection
/// * `plot_point_id` - The unique ID for the new plot point
/// * `user_id` - The ID of the user/author
/// * `book_id` - The ID of the book
/// * `encrypted_name` - The encrypted title of the plot point
/// * `hashed_name` - The hashed title for duplicate checking
/// * `incident_id` - The ID of the linked incident (can be empty string)
/// * `last_update` - The timestamp of the last update
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns the ID of the newly created plot point.
/// Errors if the plot point already exists or cannot be added.
pub fn insert_new_plot_point(
    conn: &Connection, plot_point_id: &str, user_id: &str, book_id: &str,
    encrypted_name: &str, hashed_name: &str, incident_id: &str, last_update: i64, lang: Lang,
) -> AppResult<String> {
    let existing_plot_point: Option<String> = conn
        .query_row("SELECT plot_point_id FROM book_plot_points WHERE author_id=?1 AND book_id=?2 AND hashed_title=?3", params![user_id, book_id, hashed_name], |query_row| query_row.get(0))
        .optional()
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de vérifier l'existence du point d'intrigue.".to_string() } else { "Unable to verify plot point existence.".to_string() }))?;

    if existing_plot_point.is_some() {
        return Err(AppError::Validation(if lang == Lang::Fr { "Ce point de l'intrigue existe déjà.".to_string() } else { "This plot point already exists.".to_string() }));
    }

    let insert_result = conn
        .execute("INSERT INTO book_plot_points (plot_point_id,title,hashed_title,author_id,book_id,linked_incident_id,last_update) VALUES (?1,?2,?3,?4,?5,?6,?7)", params![plot_point_id, encrypted_name, hashed_name, user_id, book_id, incident_id, last_update])
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible d'ajouter le point d'intrigue.".to_string() } else { "Unable to add plot point.".to_string() }))?;

    if insert_result == 0 {
        return Err(AppError::Internal(if lang == Lang::Fr { "Une erreur s'est produite lors de l'ajout du point d'intrigue.".to_string() } else { "Error adding plot point.".to_string() }));
    }

    Ok(plot_point_id.to_string())
}

/// Deletes a plot point from the database.
/// * `conn` - Database connection
/// * `user_id` - The ID of the user/author
/// * `plot_point_id` - The ID of the plot point to delete
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns true if the plot point was deleted, false otherwise.
/// Errors if the plot point cannot be deleted.
pub fn delete_plot_point(conn: &Connection, user_id: &str, plot_point_id: &str, lang: Lang) -> AppResult<bool> {
    let delete_result = conn
        .execute("DELETE FROM book_plot_points WHERE author_id=?1 AND plot_point_id=?2", params![user_id, plot_point_id])
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de supprimer le point d'intrigue.".to_string() } else { "Unable to delete plot point.".to_string() }))?;

    Ok(delete_result > 0)
}

/// Updates an existing plot point in the database.
/// * `conn` - Database connection
/// * `user_id` - The ID of the user/author
/// * `book_id` - The ID of the book
/// * `plot_point_id` - The ID of the plot point to update
/// * `encrypted_plot_point_name` - The new encrypted title
/// * `plot_point_hashed_name` - The new hashed title
/// * `plot_point_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 plot point was updated, false otherwise.
/// Errors if the update fails.
pub fn update_plot_point(
    conn: &Connection, user_id: &str, book_id: &str, plot_point_id: &str,
    encrypted_plot_point_name: &str, plot_point_hashed_name: &str, plot_point_summary: &str,
    last_update: i64, lang: Lang,
) -> AppResult<bool> {
    let update_result = conn
        .execute("UPDATE book_plot_points SET title=?1, hashed_title=?2, summary=?3, last_update=?4 WHERE author_id=?5 AND book_id=?6 AND plot_point_id=?7", params![encrypted_plot_point_name, plot_point_hashed_name, plot_point_summary, last_update, user_id, book_id, plot_point_id])
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de mettre à jour le point d'intrigue.".to_string() } else { "Unable to update plot point.".to_string() }))?;

    Ok(update_result > 0)
}

/// Fetches all plot points for a book with complete information for synchronization.
/// * `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 plot point records.
/// Errors if the plot points cannot be retrieved.
pub fn fetch_book_plot_points(conn: &Connection, user_id: &str, book_id: &str, lang: Lang) -> AppResult<Vec<BookPlotPointsTable>> {
    let mut statement = conn
        .prepare("SELECT plot_point_id, title, hashed_title, summary, linked_incident_id, author_id, book_id, last_update FROM book_plot_points WHERE author_id=?1 AND book_id=?2")
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les points d'intrigue.".to_string() } else { "Unable to retrieve plot points.".to_string() }))?;

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

    Ok(plot_points)
}

/// Fetches all synced plot points 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 plot point records with minimal information.
/// Errors if the synced plot points cannot be retrieved.
pub fn fetch_synced_plot_points(conn: &Connection, user_id: &str, lang: Lang) -> AppResult<Vec<SyncedPlotPointResult>> {
    let mut statement = conn
        .prepare("SELECT plot_point_id, book_id, title, last_update FROM book_plot_points WHERE author_id = ?1")
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les points d'intrigue synchronisés.".to_string() } else { "Unable to retrieve synced plot points.".to_string() }))?;

    let synced_plot_points = statement
        .query_map(params![user_id], |query_row| {
            Ok(SyncedPlotPointResult {
                plot_point_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 points d'intrigue synchronisés.".to_string() } else { "Unable to retrieve synced plot points.".to_string() }))?
        .collect::<Result<Vec<_>, _>>()
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les points d'intrigue synchronisés.".to_string() } else { "Unable to retrieve synced plot points.".to_string() }))?;

    Ok(synced_plot_points)
}

/// Inserts a plot point during synchronization from remote data.
/// * `conn` - Database connection
/// * `plot_point_id` - The unique ID of the plot point
/// * `title` - The encrypted title
/// * `hashed_title` - The hashed title for duplicate checking
/// * `summary` - The encrypted summary (can be null)
/// * `linked_incident_id` - The ID of the linked incident (can be null)
/// * `author_id` - The ID of the author
/// * `book_id` - The ID of the book
/// * `last_update` - The timestamp of the last update
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns true if the plot point was inserted, false otherwise.
/// Errors if the plot point cannot be inserted.
pub fn insert_sync_plot_point(
    conn: &Connection, plot_point_id: &str, title: &str, hashed_title: &str,
    summary: Option<&str>, linked_incident_id: Option<&str>, author_id: &str,
    book_id: &str, last_update: i64, lang: Lang,
) -> AppResult<bool> {
    let insert_result = conn
        .execute("INSERT INTO book_plot_points (plot_point_id, title, hashed_title, summary, linked_incident_id, author_id, book_id, last_update) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)", params![plot_point_id, title, hashed_title, summary, linked_incident_id, author_id, book_id, last_update])
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible d'insérer le point d'intrigue.".to_string() } else { "Unable to insert plot point.".to_string() }))?;

    Ok(insert_result > 0)
}

/// Fetches complete plot point data by its ID.
/// * `conn` - Database connection
/// * `plot_point_id` - The ID of the plot point to retrieve
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns an array containing the plot point data (empty if not found).
/// Errors if the plot point cannot be retrieved.
pub fn fetch_complete_plot_point_by_id(conn: &Connection, plot_point_id: &str, lang: Lang) -> AppResult<Vec<BookPlotPointsTable>> {
    let mut statement = conn
        .prepare("SELECT plot_point_id, title, hashed_title, summary, linked_incident_id, author_id, book_id, last_update FROM book_plot_points WHERE plot_point_id = ?1")
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le point d'intrigue complet.".to_string() } else { "Unable to retrieve complete plot point.".to_string() }))?;

    let plot_point = statement
        .query_map(params![plot_point_id], |query_row| {
            Ok(BookPlotPointsTable {
                plot_point_id: query_row.get(0)?,
                title: query_row.get(1)?,
                hashed_title: query_row.get(2)?,
                summary: query_row.get(3)?,
                linked_incident_id: query_row.get(4)?,
                author_id: query_row.get(5)?,
                _book_id: query_row.get(6)?,
                last_update: query_row.get(7)?,
            })
        })
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le point d'intrigue complet.".to_string() } else { "Unable to retrieve complete plot point.".to_string() }))?
        .collect::<Result<Vec<_>, _>>()
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le point d'intrigue complet.".to_string() } else { "Unable to retrieve complete plot point.".to_string() }))?;

    Ok(plot_point)
}

/// Checks if a plot point exists in the database.
/// * `conn` - Database connection
/// * `user_id` - The ID of the user/author
/// * `book_id` - The ID of the book
/// * `plot_point_id` - The ID of the plot point to check
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns true if the plot point exists, false otherwise.
/// Errors if the existence check fails.
pub fn plot_point_exist(conn: &Connection, user_id: &str, book_id: &str, plot_point_id: &str, lang: Lang) -> AppResult<bool> {
    let existing_plot_point: Option<i32> = conn
        .query_row("SELECT 1 FROM book_plot_points WHERE author_id =?1 AND book_id =?2 AND plot_point_id =?3", params![user_id, book_id, plot_point_id], |query_row| query_row.get(0))
        .optional()
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de vérifier l'existence du point de intrigue.".to_string() } else { "Unable to check plot point existence.".to_string() }))?;

    Ok(existing_plot_point.is_some())
}
