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

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

pub struct BookIssuesTable {
    pub issue_id: String,
    pub author_id: String,
    pub book_id: String,
    pub name: String,
    pub hashed_issue_name: String,
    pub last_update: i64,
}

pub struct SyncedIssueResult {
    pub issue_id: String,
    pub book_id: String,
    pub name: String,
    pub last_update: i64,
}

pub struct IssueQuery {
    pub issue_id: String,
    pub name: String,
}

/// Fetches all issues associated with a specific book.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user/author
/// * `book_id` - The unique identifier of the book
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns an array of issues with their IDs and names.
pub fn fetch_issues_from_book(conn: &Connection, user_id: &str, book_id: &str, lang: Lang) -> AppResult<Vec<IssueQuery>> {
    let mut statement = conn
        .prepare("SELECT issue_id, name FROM book_issues WHERE author_id=?1 AND book_id=?2")
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les problématiques.".to_string() } else { "Unable to retrieve issues.".to_string() }))?;

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

    Ok(issues)
}

/// Inserts a new issue into the database after verifying it doesn't already exist.
/// * `conn` - Database connection
/// * `issue_id` - The unique identifier for the new issue
/// * `user_id` - The unique identifier of the user/author
/// * `book_id` - The unique identifier of the book
/// * `encrypted_name` - The encrypted name of the issue
/// * `hashed_name` - The hashed name of the issue for duplicate checking
/// * `last_update` - The timestamp of the last update
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns the issue ID if successfully inserted.
pub fn insert_new_issue(
    conn: &Connection, issue_id: &str, user_id: &str, book_id: &str,
    encrypted_name: &str, hashed_name: &str, last_update: i64, lang: Lang,
) -> AppResult<String> {
    let existing_issue: Option<String> = conn
        .query_row("SELECT issue_id FROM book_issues WHERE hashed_issue_name=?1 AND book_id=?2 AND author_id=?3", params![hashed_name, book_id, user_id], |query_row| query_row.get(0))
        .optional()
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de vérifier l'existence de la problématique.".to_string() } else { "Unable to verify issue existence.".to_string() }))?;

    if existing_issue.is_some() {
        return Err(AppError::Validation(if lang == Lang::Fr { "La problématique existe déjà.".to_string() } else { "This issue already exists.".to_string() }));
    }

    let insert_result = conn
        .execute("INSERT INTO book_issues (issue_id, author_id, book_id, name, hashed_issue_name, last_update) VALUES (?1, ?2, ?3, ?4, ?5, ?6)", params![issue_id, user_id, book_id, encrypted_name, hashed_name, last_update])
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible d'ajouter la problématique.".to_string() } else { "Unable to add issue.".to_string() }))?;

    if insert_result == 0 {
        return Err(AppError::Internal(if lang == Lang::Fr { "Erreur pendant l'ajout de la problématique.".to_string() } else { "Error adding issue.".to_string() }));
    }

    Ok(issue_id.to_string())
}

/// Deletes an issue from the database.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user/author
/// * `issue_id` - The unique identifier of the issue to delete
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns true if the issue was successfully deleted, false otherwise.
pub fn delete_issue(conn: &Connection, user_id: &str, issue_id: &str, lang: Lang) -> AppResult<bool> {
    let delete_result = conn
        .execute("DELETE FROM book_issues WHERE author_id=?1 AND issue_id=?2", params![user_id, issue_id])
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de supprimer la problématique.".to_string() } else { "Unable to delete issue.".to_string() }))?;

    Ok(delete_result > 0)
}

/// Fetches all complete issue records for a specific book.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user/author
/// * `book_id` - The unique identifier of the book
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns an array of complete issue records.
pub fn fetch_book_issues(conn: &Connection, user_id: &str, book_id: &str, lang: Lang) -> AppResult<Vec<BookIssuesTable>> {
    let mut statement = conn
        .prepare("SELECT issue_id, author_id, book_id, name, hashed_issue_name, last_update FROM book_issues WHERE author_id=?1 AND book_id=?2")
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les problématiques.".to_string() } else { "Unable to retrieve issues.".to_string() }))?;

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

    Ok(issues)
}

/// Fetches all synced issues for a specific user.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user/author
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns an array of synced issue records.
pub fn fetch_synced_issues(conn: &Connection, user_id: &str, lang: Lang) -> AppResult<Vec<SyncedIssueResult>> {
    let mut statement = conn
        .prepare("SELECT issue_id, book_id, name, last_update FROM book_issues WHERE author_id = ?1")
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les problématiques synchronisées.".to_string() } else { "Unable to retrieve synced issues.".to_string() }))?;

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

    Ok(synced_issues)
}

/// Inserts a synced issue from remote into the local database.
/// * `conn` - Database connection
/// * `issue_id` - The unique identifier of the issue
/// * `author_id` - The unique identifier of the author
/// * `book_id` - The unique identifier of the book
/// * `name` - The encrypted name of the issue
/// * `hashed_issue_name` - The hashed name of the issue
/// * `last_update` - The timestamp of the last update
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns true if the issue was successfully inserted, false otherwise.
pub fn insert_sync_issue(
    conn: &Connection, issue_id: &str, author_id: &str, book_id: &str,
    name: &str, hashed_issue_name: &str, last_update: i64, lang: Lang,
) -> AppResult<bool> {
    let insert_result = conn
        .execute("INSERT INTO book_issues (issue_id, author_id, book_id, name, hashed_issue_name, last_update) VALUES (?1, ?2, ?3, ?4, ?5, ?6)", params![issue_id, author_id, book_id, name, hashed_issue_name, last_update])
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible d'insérer la problématique.".to_string() } else { "Unable to insert issue.".to_string() }))?;

    Ok(insert_result > 0)
}

/// Fetches a complete issue record by its ID.
/// * `conn` - Database connection
/// * `issue_id` - The unique identifier of the issue
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns an array of complete issue records.
pub fn fetch_complete_issue_by_id(conn: &Connection, issue_id: &str, lang: Lang) -> AppResult<Vec<BookIssuesTable>> {
    let mut statement = conn
        .prepare("SELECT issue_id, author_id, book_id, name, hashed_issue_name, last_update FROM book_issues WHERE issue_id = ?1")
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le problème complet.".to_string() } else { "Unable to retrieve complete issue.".to_string() }))?;

    let issues = statement
        .query_map(params![issue_id], |query_row| {
            Ok(BookIssuesTable {
                issue_id: query_row.get(0)?, author_id: query_row.get(1)?,
                book_id: query_row.get(2)?, name: query_row.get(3)?,
                hashed_issue_name: query_row.get(4)?, last_update: query_row.get(5)?,
            })
        })
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le problème complet.".to_string() } else { "Unable to retrieve complete issue.".to_string() }))?
        .collect::<Result<Vec<_>, _>>()
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le problème complet.".to_string() } else { "Unable to retrieve complete issue.".to_string() }))?;

    Ok(issues)
}

/// Updates an existing issue in the database.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user/author
/// * `book_id` - The unique identifier of the book
/// * `issue_id` - The unique identifier of the issue to update
/// * `name` - The new encrypted name of the issue
/// * `hashed_name` - The new hashed name of the issue
/// * `last_update` - The timestamp of the update
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns true if the issue was successfully updated, false otherwise.
pub fn update_issue(
    conn: &Connection, user_id: &str, book_id: &str, issue_id: &str,
    name: &str, hashed_name: &str, last_update: i64, lang: Lang,
) -> AppResult<bool> {
    let update_result = conn
        .execute("UPDATE book_issues SET name = ?1, hashed_issue_name = ?2, last_update = ?3 WHERE issue_id = ?4 AND author_id = ?5 AND book_id = ?6", params![name, hashed_name, last_update, issue_id, user_id, book_id])
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de mettre à jour la problématique.".to_string() } else { "Unable to update issue.".to_string() }))?;

    Ok(update_result > 0)
}

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

    Ok(existing_issue.is_some())
}
