use rusqlite::Connection;
use serde::Serialize;

use crate::crypto::encryption::{encrypt_data_with_user_key, decrypt_data_with_user_key, hash_element};
use crate::crypto::key_manager::get_user_encryption_key;
use crate::domains::series_spell::repo;
use crate::domains::tombstone::repo as tombstone_repo;
use crate::error::{AppError, AppResult};
use crate::helpers::{create_unique_id, timestamp_in_seconds};
use crate::shared::types::Lang;

#[derive(Serialize)]
pub struct SeriesSpellTagProps {
    pub id: String,
    pub name: String,
    pub color: Option<String>,
}

#[derive(Serialize)]
pub struct SeriesSpellListProps {
    pub id: String,
    pub name: String,
    pub description: String,
    pub tags: Vec<String>,
}

#[derive(Serialize)]
pub struct SeriesSpellListResponse {
    pub spells: Vec<SeriesSpellListProps>,
    pub tags: Vec<SeriesSpellTagProps>,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SeriesSpellDetailProps {
    pub id: String,
    pub name: String,
    pub description: String,
    pub appearance: String,
    pub tags: Vec<String>,
    pub power_level: Option<String>,
    pub components: Option<String>,
    pub limitations: Option<String>,
    pub notes: Option<String>,
}

/// Retrieves all spells and tags for a series.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `series_id` - The unique identifier of the series
/// * `lang` - The language for error messages
/// Returns the list of spells and tags.
pub fn get_spell_list(conn: &Connection, user_id: &str, series_id: &str, lang: Lang) -> AppResult<SeriesSpellListResponse> {
    let user_key: String = get_user_encryption_key(user_id)?;
    let spells_result: Vec<repo::SeriesSpellResult> = repo::fetch_spells(conn, user_id, series_id, lang)?;
    let tags_result: Vec<repo::SeriesSpellTagResult> = repo::fetch_tags(conn, user_id, series_id, lang)?;

    let mut spells: Vec<SeriesSpellListProps> = Vec::with_capacity(spells_result.len());
    for spell in spells_result {
        let decrypted_name: String = if spell.name.is_empty() { String::new() } else { decrypt_data_with_user_key(&spell.name, &user_key)? };
        let decrypted_description: String = if spell.description.is_empty() { String::new() } else { decrypt_data_with_user_key(&spell.description, &user_key)? };
        let decrypted_tags: Vec<String> = if spell.tags.is_empty() {
            vec![]
        } else {
            let decrypted_tags_string: String = decrypt_data_with_user_key(&spell.tags, &user_key)?;
            serde_json::from_str(&decrypted_tags_string).unwrap_or_default()
        };

        spells.push(SeriesSpellListProps {
            id: spell.spell_id,
            name: decrypted_name,
            description: decrypted_description,
            tags: decrypted_tags,
        });
    }

    let mut tags: Vec<SeriesSpellTagProps> = Vec::with_capacity(tags_result.len());
    for tag in tags_result {
        let decrypted_name: String = if tag.name.is_empty() { String::new() } else { decrypt_data_with_user_key(&tag.name, &user_key)? };

        tags.push(SeriesSpellTagProps {
            id: tag.tag_id,
            name: decrypted_name,
            color: tag.color,
        });
    }

    Ok(SeriesSpellListResponse { spells, tags })
}

/// Retrieves the details of a specific 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 the spell details.
pub fn get_spell_detail(conn: &Connection, user_id: &str, spell_id: &str, lang: Lang) -> AppResult<SeriesSpellDetailProps> {
    let user_key: String = get_user_encryption_key(user_id)?;
    let spell: Option<repo::SeriesSpellResult> = repo::fetch_spell_by_id(conn, user_id, spell_id, lang)?;

    let spell: repo::SeriesSpellResult = spell.ok_or_else(|| {
        AppError::Internal(if lang == Lang::Fr { "Sort non trouvé.".to_string() } else { "Spell not found.".to_string() })
    })?;

    let decrypted_name: String = if spell.name.is_empty() { String::new() } else { decrypt_data_with_user_key(&spell.name, &user_key)? };
    let decrypted_description: String = if spell.description.is_empty() { String::new() } else { decrypt_data_with_user_key(&spell.description, &user_key)? };
    let decrypted_appearance: String = if spell.appearance.is_empty() { String::new() } else { decrypt_data_with_user_key(&spell.appearance, &user_key)? };
    let decrypted_tags: Vec<String> = if spell.tags.is_empty() {
        vec![]
    } else {
        let decrypted_tags_string: String = decrypt_data_with_user_key(&spell.tags, &user_key)?;
        serde_json::from_str(&decrypted_tags_string).unwrap_or_default()
    };
    let decrypted_power_level: Option<String> = if let Some(ref power_level) = spell.power_level { Some(decrypt_data_with_user_key(power_level, &user_key)?) } else { None };
    let decrypted_components: Option<String> = if let Some(ref components) = spell.components { Some(decrypt_data_with_user_key(components, &user_key)?) } else { None };
    let decrypted_limitations: Option<String> = if let Some(ref limitations) = spell.limitations { Some(decrypt_data_with_user_key(limitations, &user_key)?) } else { None };
    let decrypted_notes: Option<String> = if let Some(ref notes) = spell.notes { Some(decrypt_data_with_user_key(notes, &user_key)?) } else { None };

    Ok(SeriesSpellDetailProps {
        id: spell.spell_id,
        name: decrypted_name,
        description: decrypted_description,
        appearance: decrypted_appearance,
        tags: decrypted_tags,
        power_level: decrypted_power_level,
        components: decrypted_components,
        limitations: decrypted_limitations,
        notes: decrypted_notes,
    })
}

/// Adds a new spell to a series.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `series_id` - The unique identifier of the series
/// * `name` - The spell name
/// * `lang` - The language for error messages
/// * `description` - The spell description
/// * `appearance` - The spell appearance
/// * `tags` - The spell tags
/// * `power_level` - The spell power level
/// * `components` - The spell components
/// * `limitations` - The spell limitations
/// * `notes` - The spell notes
/// Returns the new spell ID.
pub fn add_spell(
    conn: &Connection, user_id: &str, series_id: &str, name: &str, lang: Lang,
    description: Option<&str>, appearance: Option<&str>, tags: Option<Vec<String>>,
    power_level: Option<&str>, components: Option<&str>, limitations: Option<&str>, notes: Option<&str>,
) -> AppResult<String> {
    let user_key: String = get_user_encryption_key(user_id)?;
    let spell_id: String = create_unique_id(None);
    let encrypted_name: String = encrypt_data_with_user_key(name, &user_key)?;
    let name_hash: String = hash_element(name);
    let encrypted_description: String = if let Some(description) = description { encrypt_data_with_user_key(description, &user_key)? } else { String::new() };
    let encrypted_appearance: String = if let Some(appearance) = appearance { encrypt_data_with_user_key(appearance, &user_key)? } else { String::new() };
    let tags_json: String = serde_json::to_string(&tags.unwrap_or_default()).unwrap_or_else(|_| "[]".to_string());
    let encrypted_tags: String = encrypt_data_with_user_key(&tags_json, &user_key)?;
    let encrypted_power_level: Option<String> = if let Some(power_level) = power_level { Some(encrypt_data_with_user_key(power_level, &user_key)?) } else { None };
    let encrypted_components: Option<String> = if let Some(components) = components { Some(encrypt_data_with_user_key(components, &user_key)?) } else { None };
    let encrypted_limitations: Option<String> = if let Some(limitations) = limitations { Some(encrypt_data_with_user_key(limitations, &user_key)?) } else { None };
    let encrypted_notes: Option<String> = if let Some(notes) = notes { Some(encrypt_data_with_user_key(notes, &user_key)?) } else { None };
    let last_update: i64 = timestamp_in_seconds();

    repo::insert_spell(
        conn, &spell_id, series_id, user_id, &encrypted_name, &name_hash,
        &encrypted_description, &encrypted_appearance, &encrypted_tags,
        encrypted_power_level.as_deref(), encrypted_components.as_deref(),
        encrypted_limitations.as_deref(), encrypted_notes.as_deref(), last_update, lang,
    )?;

    Ok(spell_id)
}

/// 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 spell name
/// * `lang` - The language for error messages
/// * `description` - The spell description
/// * `appearance` - The spell appearance
/// * `tags` - The spell tags
/// * `power_level` - The spell power level
/// * `components` - The spell components
/// * `limitations` - The spell limitations
/// * `notes` - The spell notes
/// Returns true if successful.
pub fn update_spell(
    conn: &Connection, user_id: &str, spell_id: &str, name: &str, lang: Lang,
    description: Option<&str>, appearance: Option<&str>, tags: Option<Vec<String>>,
    power_level: Option<&str>, components: Option<&str>, limitations: Option<&str>, notes: Option<&str>,
) -> AppResult<bool> {
    let user_key: String = get_user_encryption_key(user_id)?;
    let encrypted_name: String = encrypt_data_with_user_key(name, &user_key)?;
    let name_hash: String = hash_element(name);
    let encrypted_description: String = if let Some(description) = description { encrypt_data_with_user_key(description, &user_key)? } else { String::new() };
    let encrypted_appearance: String = if let Some(appearance) = appearance { encrypt_data_with_user_key(appearance, &user_key)? } else { String::new() };
    let tags_json: String = serde_json::to_string(&tags.unwrap_or_default()).unwrap_or_else(|_| "[]".to_string());
    let encrypted_tags: String = encrypt_data_with_user_key(&tags_json, &user_key)?;
    let encrypted_power_level: Option<String> = if let Some(power_level) = power_level { Some(encrypt_data_with_user_key(power_level, &user_key)?) } else { None };
    let encrypted_components: Option<String> = if let Some(components) = components { Some(encrypt_data_with_user_key(components, &user_key)?) } else { None };
    let encrypted_limitations: Option<String> = if let Some(limitations) = limitations { Some(encrypt_data_with_user_key(limitations, &user_key)?) } else { None };
    let encrypted_notes: Option<String> = if let Some(notes) = notes { Some(encrypt_data_with_user_key(notes, &user_key)?) } else { None };
    let last_update: i64 = timestamp_in_seconds();

    repo::update_spell(
        conn, user_id, spell_id, &encrypted_name, &name_hash,
        &encrypted_description, &encrypted_appearance, &encrypted_tags,
        encrypted_power_level.as_deref(), encrypted_components.as_deref(),
        encrypted_limitations.as_deref(), encrypted_notes.as_deref(), last_update, lang,
    )
}

/// Deletes a spell.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `spell_id` - The unique identifier of the spell
/// * `deleted_at` - The timestamp of deletion
/// * `lang` - The language for error messages
/// Returns true if successful.
pub fn delete_spell(conn: &Connection, user_id: &str, spell_id: &str, deleted_at: i64, lang: Lang) -> AppResult<bool> {
    let deleted: bool = repo::delete_spell(conn, user_id, spell_id, lang)?;
    if deleted {
        tombstone_repo::insert(conn, spell_id, "series_spells", spell_id, None, user_id, deleted_at, lang)?;
    }
    Ok(deleted)
}

/// Adds a new tag to a series.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `series_id` - The unique identifier of the series
/// * `name` - The name of the tag
/// * `lang` - The language for error messages
/// * `color` - The color of the tag (optional)
/// Returns the new tag ID.
pub fn add_tag(
    conn: &Connection, user_id: &str, series_id: &str, name: &str, lang: Lang, color: Option<&str>,
) -> AppResult<String> {
    let user_key: String = get_user_encryption_key(user_id)?;
    let tag_id: String = create_unique_id(None);
    let encrypted_name: String = encrypt_data_with_user_key(name, &user_key)?;
    let hashed_name: String = hash_element(name);
    let last_update: i64 = timestamp_in_seconds();

    repo::insert_tag(conn, &tag_id, series_id, user_id, &encrypted_name, &hashed_name, color, last_update, lang)?;

    Ok(tag_id)
}

/// Updates an existing tag.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `tag_id` - The unique identifier of the tag
/// * `name` - The new name of the tag
/// * `lang` - The language for error messages
/// * `color` - The new color of the tag (optional)
/// Returns true if successful.
pub fn update_tag(
    conn: &Connection, user_id: &str, tag_id: &str, name: &str, lang: Lang, color: Option<&str>,
) -> AppResult<bool> {
    let user_key: String = get_user_encryption_key(user_id)?;
    let encrypted_name: String = encrypt_data_with_user_key(name, &user_key)?;
    let hashed_name: String = hash_element(name);
    let last_update: i64 = timestamp_in_seconds();

    repo::update_tag(conn, user_id, tag_id, &encrypted_name, &hashed_name, color, last_update, lang)
}

/// Deletes a tag.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `tag_id` - The unique identifier of the tag
/// * `deleted_at` - The timestamp of deletion
/// * `lang` - The language for error messages
/// Returns true if successful.
pub fn delete_tag(conn: &Connection, user_id: &str, tag_id: &str, deleted_at: i64, lang: Lang) -> AppResult<bool> {
    let deleted: bool = repo::delete_tag(conn, user_id, tag_id, lang)?;
    if deleted {
        tombstone_repo::insert(conn, tag_id, "series_spell_tags", tag_id, None, user_id, deleted_at, lang)?;
    }
    Ok(deleted)
}
