use rusqlite::{params, Connection};

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

pub struct SpellResult {
    pub spell_id: String,
    pub _book_id: String,
    pub name: String,
    pub description: Option<String>,
    pub appearance: Option<String>,
    pub tags: Option<String>,
    pub power_level: Option<String>,
    pub components: Option<String>,
    pub limitations: Option<String>,
    pub notes: Option<String>,
    pub series_spell_id: Option<String>,
}

pub struct BookSpellsTable {
    pub spell_id: String,
    pub book_id: String,
    pub user_id: String,
    pub name: String,
    pub name_hash: String,
    pub description: Option<String>,
    pub appearance: Option<String>,
    pub tags: Option<String>,
    pub power_level: Option<String>,
    pub components: Option<String>,
    pub limitations: Option<String>,
    pub notes: Option<String>,
    pub last_update: i64,
}

pub struct SyncedSpellResult {
    pub spell_id: String,
    pub book_id: String,
    pub name: String,
    pub last_update: i64,
}

/// Fetches all spells for a specific book owned by the user.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `book_id` - The unique identifier of the book
/// * `lang` - The language for error messages
/// Returns an array of spell results.
pub fn fetch_spells(conn: &Connection, user_id: &str, book_id: &str, lang: Lang) -> AppResult<Vec<SpellResult>> {
    let mut statement = conn
        .prepare("SELECT spell_id, book_id, name, description, appearance, tags, power_level, components, limitations, notes, series_spell_id FROM book_spells WHERE user_id=?1 AND book_id=?2")
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les sorts.".to_string() } else { "Unable to retrieve spells.".to_string() }))?;

    let spells = statement
        .query_map(params![user_id, book_id], |query_row| {
            Ok(SpellResult {
                spell_id: query_row.get(0)?, _book_id: query_row.get(1)?,
                name: query_row.get(2)?, description: query_row.get(3)?,
                appearance: query_row.get(4)?, tags: query_row.get(5)?,
                power_level: query_row.get(6)?, components: query_row.get(7)?,
                limitations: query_row.get(8)?, notes: query_row.get(9)?,
                series_spell_id: query_row.get(10)?,
            })
        })
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les sorts.".to_string() } else { "Unable to retrieve spells.".to_string() }))?
        .collect::<Result<Vec<_>, _>>()
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les sorts.".to_string() } else { "Unable to retrieve spells.".to_string() }))?;

    Ok(spells)
}

/// Fetches a single spell by its ID.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `spell_id` - The unique identifier of the spell
/// * `lang` - The language for error messages
/// Returns the spell result or None if not found.
pub fn fetch_spell_by_id(conn: &Connection, user_id: &str, spell_id: &str, lang: Lang) -> AppResult<Option<SpellResult>> {
    let mut statement = conn
        .prepare("SELECT spell_id, book_id, name, description, appearance, tags, power_level, components, limitations, notes, series_spell_id FROM book_spells WHERE user_id=?1 AND spell_id=?2")
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le sort.".to_string() } else { "Unable to retrieve spell.".to_string() }))?;

    let spells = statement
        .query_map(params![user_id, spell_id], |query_row| {
            Ok(SpellResult {
                spell_id: query_row.get(0)?, _book_id: query_row.get(1)?,
                name: query_row.get(2)?, description: query_row.get(3)?,
                appearance: query_row.get(4)?, tags: query_row.get(5)?,
                power_level: query_row.get(6)?, components: query_row.get(7)?,
                limitations: query_row.get(8)?, notes: query_row.get(9)?,
                series_spell_id: query_row.get(10)?,
            })
        })
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le sort.".to_string() } else { "Unable to retrieve spell.".to_string() }))?
        .collect::<Result<Vec<_>, _>>()
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le sort.".to_string() } else { "Unable to retrieve spell.".to_string() }))?;

    Ok(spells.into_iter().next())
}

/// Inserts a new spell.
/// * `conn` - Database connection
/// * `spell_id` - The unique identifier for the new spell
/// * `book_id` - The unique identifier of the book
/// * `user_id` - The unique identifier of the user
/// * `name` - The encrypted name
/// * `name_hash` - The hashed name for duplicate detection
/// * `description` - The encrypted description
/// * `appearance` - The encrypted appearance
/// * `tags` - The encrypted JSON tags array
/// * `power_level` - The encrypted power level (nullable)
/// * `components` - The encrypted components (nullable)
/// * `limitations` - The encrypted limitations (nullable)
/// * `notes` - The encrypted notes (nullable)
/// * `lang` - The language for error messages
/// * `series_spell_id` - The optional series spell identifier
/// Returns the spell ID if successful.
pub fn insert_spell(
    conn: &Connection, spell_id: &str, book_id: &str, user_id: &str, name: &str,
    name_hash: &str, description: Option<&str>, appearance: Option<&str>, tags: &str,
    power_level: Option<&str>, components: Option<&str>, limitations: Option<&str>,
    notes: Option<&str>, last_update: i64, lang: Lang, series_spell_id: Option<&str>,
) -> AppResult<String> {
    let insert_result = match series_spell_id {
        Some(series_id) => conn
            .execute("INSERT INTO book_spells (spell_id, book_id, user_id, name, name_hash, description, appearance, tags, power_level, components, limitations, notes, series_spell_id, last_update) VALUES (?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12,?13,?14)", params![spell_id, book_id, user_id, name, name_hash, description, appearance, tags, power_level, components, limitations, notes, series_id, last_update])
            .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible d'ajouter le sort.".to_string() } else { "Unable to add spell.".to_string() }))?,
        None => conn
            .execute("INSERT INTO book_spells (spell_id, book_id, user_id, name, name_hash, description, appearance, tags, power_level, components, limitations, notes, last_update) VALUES (?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12,?13)", params![spell_id, book_id, user_id, name, name_hash, description, appearance, tags, power_level, components, limitations, notes, last_update])
            .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible d'ajouter le sort.".to_string() } else { "Unable to add spell.".to_string() }))?,
    };

    if insert_result > 0 {
        Ok(spell_id.to_string())
    } else {
        Err(AppError::Internal(if lang == Lang::Fr { "Une erreur s'est produite lors de l'ajout du sort.".to_string() } else { "Error adding spell.".to_string() }))
    }
}

/// Updates an existing spell.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `spell_id` - The unique identifier of the spell
/// * `name` - The encrypted name
/// * `name_hash` - The hashed name
/// * `description` - The encrypted description
/// * `appearance` - The encrypted appearance
/// * `tags` - The encrypted JSON tags array
/// * `power_level` - The encrypted power level (nullable)
/// * `components` - The encrypted components (nullable)
/// * `limitations` - The encrypted limitations (nullable)
/// * `notes` - The encrypted notes (nullable)
/// * `lang` - The language for error messages
/// * `series_spell_id` - The optional series spell identifier
/// Returns true if the update was successful.
pub fn update_spell(
    conn: &Connection, user_id: &str, spell_id: &str, name: &str, name_hash: &str,
    description: Option<&str>, appearance: Option<&str>, tags: &str, power_level: Option<&str>,
    components: Option<&str>, limitations: Option<&str>, notes: Option<&str>, last_update: i64, lang: Lang,
    series_spell_id: Option<&str>,
) -> AppResult<bool> {
    let update_result = match series_spell_id {
        Some(series_id) => conn
            .execute("UPDATE book_spells SET name=?1, name_hash=?2, description=?3, appearance=?4, tags=?5, power_level=?6, components=?7, limitations=?8, notes=?9, series_spell_id=?10, last_update=?11 WHERE spell_id=?12 AND user_id=?13", params![name, name_hash, description, appearance, tags, power_level, components, limitations, notes, series_id, last_update, spell_id, user_id])
            .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de mettre à jour le sort.".to_string() } else { "Unable to update spell.".to_string() }))?,
        None => conn
            .execute("UPDATE book_spells SET name=?1, name_hash=?2, description=?3, appearance=?4, tags=?5, power_level=?6, components=?7, limitations=?8, notes=?9, last_update=?10 WHERE spell_id=?11 AND user_id=?12", params![name, name_hash, description, appearance, tags, power_level, components, limitations, notes, last_update, spell_id, user_id])
            .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de mettre à jour le sort.".to_string() } else { "Unable to update spell.".to_string() }))?,
    };

    Ok(update_result > 0)
}

/// Deletes a spell.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `spell_id` - The unique identifier of the spell
/// * `lang` - The language for error messages
/// Returns true if the deletion was successful.
pub fn delete_spell(conn: &Connection, user_id: &str, spell_id: &str, lang: Lang) -> AppResult<bool> {
    let delete_result = conn
        .execute("DELETE FROM book_spells WHERE spell_id=?1 AND user_id=?2", params![spell_id, user_id])
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de supprimer le sort.".to_string() } else { "Unable to delete spell.".to_string() }))?;

    Ok(delete_result > 0)
}

/// Updates the tags field of a spell.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `spell_id` - The unique identifier of the spell
/// * `tags` - The new encrypted JSON tags array
/// * `lang` - The language for error messages
/// Returns true if the update was successful.
pub fn update_spell_tags(conn: &Connection, user_id: &str, spell_id: &str, tags: &str, last_update: i64, lang: Lang) -> AppResult<bool> {
    let update_result = conn
        .execute("UPDATE book_spells SET tags=?1, last_update=?2 WHERE spell_id=?3 AND user_id=?4", params![tags, last_update, spell_id, user_id])
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de mettre à jour les tags du sort.".to_string() } else { "Unable to update spell tags.".to_string() }))?;

    Ok(update_result > 0)
}

/// Fetches all spells for a book with full table data for sync.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `book_id` - The unique identifier of the book
/// * `lang` - The language for error messages
/// Returns an array of book spells table records.
pub fn fetch_book_spells_table(conn: &Connection, user_id: &str, book_id: &str, lang: Lang) -> AppResult<Vec<BookSpellsTable>> {
    let mut statement = conn
        .prepare("SELECT spell_id, book_id, user_id, name, name_hash, description, appearance, tags, power_level, components, limitations, notes, last_update FROM book_spells WHERE user_id=?1 AND book_id=?2")
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les sorts.".to_string() } else { "Unable to retrieve spells.".to_string() }))?;

    let spells = statement
        .query_map(params![user_id, book_id], |query_row| {
            Ok(BookSpellsTable {
                spell_id: query_row.get(0)?, book_id: query_row.get(1)?,
                user_id: query_row.get(2)?, name: query_row.get(3)?,
                name_hash: query_row.get(4)?, description: query_row.get(5)?,
                appearance: query_row.get(6)?, tags: query_row.get(7)?,
                power_level: query_row.get(8)?, components: query_row.get(9)?,
                limitations: query_row.get(10)?, notes: query_row.get(11)?,
                last_update: query_row.get(12)?,
            })
        })
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les sorts.".to_string() } else { "Unable to retrieve spells.".to_string() }))?
        .collect::<Result<Vec<_>, _>>()
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les sorts.".to_string() } else { "Unable to retrieve spells.".to_string() }))?;

    Ok(spells)
}

/// Fetches a complete spell record by its ID.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `spell_id` - The unique identifier of the spell
/// * `lang` - The language for error messages
/// Returns the spell table record or None.
pub fn fetch_spell_table_by_id(conn: &Connection, user_id: &str, spell_id: &str, lang: Lang) -> AppResult<Option<BookSpellsTable>> {
    let mut statement = conn
        .prepare("SELECT spell_id, book_id, user_id, name, name_hash, description, appearance, tags, power_level, components, limitations, notes, last_update FROM book_spells WHERE user_id=?1 AND spell_id=?2")
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le sort.".to_string() } else { "Unable to retrieve spell.".to_string() }))?;

    let spells = statement
        .query_map(params![user_id, spell_id], |query_row| {
            Ok(BookSpellsTable {
                spell_id: query_row.get(0)?, book_id: query_row.get(1)?,
                user_id: query_row.get(2)?, name: query_row.get(3)?,
                name_hash: query_row.get(4)?, description: query_row.get(5)?,
                appearance: query_row.get(6)?, tags: query_row.get(7)?,
                power_level: query_row.get(8)?, components: query_row.get(9)?,
                limitations: query_row.get(10)?, notes: query_row.get(11)?,
                last_update: query_row.get(12)?,
            })
        })
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le sort.".to_string() } else { "Unable to retrieve spell.".to_string() }))?
        .collect::<Result<Vec<_>, _>>()
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le sort.".to_string() } else { "Unable to retrieve spell.".to_string() }))?;

    Ok(spells.into_iter().next())
}

/// Fetches all synced spells for a user.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `lang` - The language for error messages
/// Returns an array of synced spell results.
pub fn fetch_synced_spells(conn: &Connection, user_id: &str, lang: Lang) -> AppResult<Vec<SyncedSpellResult>> {
    let mut statement = conn
        .prepare("SELECT spell_id, book_id, name, last_update FROM book_spells WHERE user_id=?1")
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les sorts synchronisés.".to_string() } else { "Unable to retrieve synced spells.".to_string() }))?;

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

    Ok(spells)
}

/// Inserts or updates a spell from synchronization data.
/// * `conn` - Database connection
/// * `spell_id` - The unique identifier for the spell
/// * `book_id` - The unique identifier of the book
/// * `user_id` - The unique identifier of the user
/// * `name` - The encrypted name
/// * `name_hash` - The hashed name
/// * `description` - The encrypted description
/// * `appearance` - The encrypted appearance
/// * `tags` - The encrypted JSON tags array
/// * `power_level` - The encrypted power level (nullable)
/// * `components` - The encrypted components (nullable)
/// * `limitations` - The encrypted limitations (nullable)
/// * `notes` - The encrypted notes (nullable)
/// * `last_update` - The timestamp of the last update
/// * `lang` - The language for error messages
/// Returns true if the insertion was successful.
pub fn insert_sync_spell(
    conn: &Connection, spell_id: &str, book_id: &str, user_id: &str, name: &str,
    name_hash: &str, description: Option<&str>, appearance: Option<&str>, tags: Option<&str>,
    power_level: Option<&str>, components: Option<&str>, limitations: Option<&str>,
    notes: Option<&str>, last_update: i64, lang: Lang,
) -> AppResult<bool> {
    let insert_result = conn
        .execute("INSERT OR REPLACE INTO book_spells (spell_id, book_id, user_id, name, name_hash, description, appearance, tags, power_level, components, limitations, notes, last_update) VALUES (?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12,?13)", params![spell_id, book_id, user_id, name, name_hash, description, appearance, tags, power_level, components, limitations, notes, last_update])
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible d'insérer le sort.".to_string() } else { "Unable to insert spell.".to_string() }))?;

    Ok(insert_result > 0)
}

/// Checks if a spell exists.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `spell_id` - The unique identifier of the spell
/// * `lang` - The language for error messages
/// Returns true if the spell exists.
pub fn is_spell_exist(conn: &Connection, user_id: &str, spell_id: &str, lang: Lang) -> AppResult<bool> {
    let mut statement = conn
        .prepare("SELECT 1 FROM book_spells WHERE spell_id=?1 AND user_id=?2")
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de vérifier l'existence du sort.".to_string() } else { "Unable to check spell existence.".to_string() }))?;

    let exists = statement
        .exists(params![spell_id, user_id])
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de vérifier l'existence du sort.".to_string() } else { "Unable to check spell existence.".to_string() }))?;

    Ok(exists)
}

/// Updates a spell with timestamp for sync.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `spell_id` - The unique identifier of the spell
/// * `name` - The encrypted name
/// * `name_hash` - The hashed name
/// * `description` - The encrypted description
/// * `appearance` - The encrypted appearance
/// * `tags` - The encrypted JSON tags array
/// * `power_level` - The encrypted power level (nullable)
/// * `components` - The encrypted components (nullable)
/// * `limitations` - The encrypted limitations (nullable)
/// * `notes` - The encrypted notes (nullable)
/// * `last_update` - The timestamp of the last update
/// * `lang` - The language for error messages
/// Returns true if the update was successful.
pub fn update_sync_spell(
    conn: &Connection, user_id: &str, spell_id: &str, name: &str, name_hash: &str,
    description: Option<&str>, appearance: Option<&str>, tags: Option<&str>,
    power_level: Option<&str>, components: Option<&str>, limitations: Option<&str>,
    notes: Option<&str>, last_update: i64, lang: Lang,
) -> AppResult<bool> {
    let update_result = conn
        .execute("UPDATE book_spells SET name=?1, name_hash=?2, description=?3, appearance=?4, tags=?5, power_level=?6, components=?7, limitations=?8, notes=?9, last_update=?10 WHERE spell_id=?11 AND user_id=?12", params![name, name_hash, description, appearance, tags, power_level, components, limitations, notes, last_update, spell_id, user_id])
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de mettre à jour le sort.".to_string() } else { "Unable to update spell.".to_string() }))?;

    Ok(update_result > 0)
}
