use std::collections::HashMap;

use rusqlite::Connection;
use serde::{Deserialize, Serialize};

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

#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CharacterPropsPost {
    pub id: Option<String>,
    pub name: String,
    pub last_name: String,
    pub nickname: String,
    pub age: Option<i64>,
    pub gender: String,
    pub species: String,
    pub nationality: String,
    pub status: String,
    pub category: String,
    pub title: String,
    pub image: String,
    pub physical: Vec<AttributeName>,
    pub psychological: Vec<AttributeName>,
    pub relations: Vec<AttributeName>,
    pub skills: Vec<AttributeName>,
    pub weaknesses: Vec<AttributeName>,
    pub strengths: Vec<AttributeName>,
    pub goals: Vec<AttributeName>,
    pub motivations: Vec<AttributeName>,
    pub arc: Vec<AttributeName>,
    pub secrets: Vec<AttributeName>,
    pub fears: Vec<AttributeName>,
    pub flaws: Vec<AttributeName>,
    pub beliefs: Vec<AttributeName>,
    pub conflicts: Vec<AttributeName>,
    pub quotes: Vec<AttributeName>,
    pub distinguishing_marks: Vec<AttributeName>,
    pub items: Vec<AttributeName>,
    pub affiliations: Vec<AttributeName>,
    pub role: String,
    pub biography: Option<String>,
    pub history: Option<String>,
    pub speech_pattern: Option<String>,
    pub catchphrase: Option<String>,
    pub residence: Option<String>,
    pub notes: Option<String>,
    pub color: Option<String>,
    pub series_character_id: Option<String>,
}

#[derive(Deserialize)]
pub struct AttributeName {
    pub name: String,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CharacterProps {
    pub id: String,
    pub name: String,
    pub last_name: String,
    pub nickname: String,
    pub age: Option<i64>,
    pub gender: String,
    pub species: String,
    pub nationality: String,
    pub status: String,
    pub title: String,
    pub category: String,
    pub image: String,
    pub role: String,
    pub biography: String,
    pub history: String,
    pub speech_pattern: String,
    pub catchphrase: String,
    pub residence: String,
    pub notes: String,
    pub color: String,
    pub series_character_id: Option<String>,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CharacterListResponse {
    pub characters: Vec<CharacterProps>,
    pub enabled: bool,
}

#[derive(Serialize)]
pub struct Attribute {
    pub id: String,
    pub name: String,
}

#[derive(Serialize)]
pub struct CharacterAttribute {
    pub r#type: String,
    pub values: Vec<Attribute>,
}

/// Retrieves a list of all characters for a specific book.
/// Decrypts character data using the user's encryption key.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `book_id` - The unique identifier of the book
/// * `lang` - The language code for localization
/// Returns a CharacterListResponse containing decrypted characters and enabled status.
pub fn get_character_list(conn: &Connection, user_id: &str, book_id: &str, lang: Lang) -> AppResult<CharacterListResponse> {
    let book_tools: Option<book_repo::BookToolsTable> = book_repo::fetch_book_tools(conn, user_id, book_id, lang)?;
    let enabled: bool = book_tools.map_or(false, |book_tools_row| book_tools_row.characters_enabled == 1);

    let user_key: String = get_user_encryption_key(user_id)?;
    let encrypted_characters: Vec<repo::CharacterResult> = repo::fetch_characters(conn, user_id, book_id, lang)?;

    if encrypted_characters.is_empty() {
        return Ok(CharacterListResponse { characters: vec![], enabled });
    }

    let mut decrypted_character_list: Vec<CharacterProps> = Vec::with_capacity(encrypted_characters.len());
    for encrypted_character in encrypted_characters {
        decrypted_character_list.push(CharacterProps {
            id: encrypted_character.character_id,
            name: if encrypted_character.first_name.is_empty() { String::new() } else { decrypt_data_with_user_key(&encrypted_character.first_name, &user_key)? },
            last_name: if encrypted_character.last_name.is_empty() { String::new() } else { decrypt_data_with_user_key(&encrypted_character.last_name, &user_key)? },
            nickname: if let Some(ref nickname) = encrypted_character.nickname { if nickname.is_empty() { String::new() } else { decrypt_data_with_user_key(nickname, &user_key)? } } else { String::new() },
            age: if let Some(ref age_val) = encrypted_character.age { if age_val.is_empty() { None } else { Some(decrypt_data_with_user_key(age_val, &user_key)?.parse::<i64>().unwrap_or(0)) } } else { None },
            gender: if let Some(ref gender) = encrypted_character.gender { if gender.is_empty() { String::new() } else { decrypt_data_with_user_key(gender, &user_key)? } } else { String::new() },
            species: if let Some(ref species) = encrypted_character.species { if species.is_empty() { String::new() } else { decrypt_data_with_user_key(species, &user_key)? } } else { String::new() },
            nationality: if let Some(ref nationality) = encrypted_character.nationality { if nationality.is_empty() { String::new() } else { decrypt_data_with_user_key(nationality, &user_key)? } } else { String::new() },
            status: if let Some(ref status) = encrypted_character.status { if status.is_empty() { "alive".to_string() } else { decrypt_data_with_user_key(status, &user_key)? } } else { "alive".to_string() },
            title: if encrypted_character.title.is_empty() { String::new() } else { decrypt_data_with_user_key(&encrypted_character.title, &user_key)? },
            category: if encrypted_character.category.is_empty() { String::new() } else { decrypt_data_with_user_key(&encrypted_character.category, &user_key)? },
            image: if encrypted_character.image.is_empty() { String::new() } else { decrypt_data_with_user_key(&encrypted_character.image, &user_key)? },
            role: if encrypted_character.role.is_empty() { String::new() } else { decrypt_data_with_user_key(&encrypted_character.role, &user_key)? },
            biography: if encrypted_character.biography.is_empty() { String::new() } else { decrypt_data_with_user_key(&encrypted_character.biography, &user_key)? },
            history: if encrypted_character.history.is_empty() { String::new() } else { decrypt_data_with_user_key(&encrypted_character.history, &user_key)? },
            speech_pattern: if let Some(ref speech_pattern) = encrypted_character.speech_pattern { if speech_pattern.is_empty() { String::new() } else { decrypt_data_with_user_key(speech_pattern, &user_key)? } } else { String::new() },
            catchphrase: if let Some(ref catchphrase) = encrypted_character.catchphrase { if catchphrase.is_empty() { String::new() } else { decrypt_data_with_user_key(catchphrase, &user_key)? } } else { String::new() },
            residence: if let Some(ref residence) = encrypted_character.residence { if residence.is_empty() { String::new() } else { decrypt_data_with_user_key(residence, &user_key)? } } else { String::new() },
            notes: if let Some(ref notes) = encrypted_character.notes { if notes.is_empty() { String::new() } else { decrypt_data_with_user_key(notes, &user_key)? } } else { String::new() },
            color: if let Some(ref color) = encrypted_character.color { if color.is_empty() { String::new() } else { decrypt_data_with_user_key(color, &user_key)? } } else { String::new() },
            series_character_id: encrypted_character.series_character_id,
        });
    }

    Ok(CharacterListResponse { characters: decrypted_character_list, enabled })
}

/// Creates a new character with all its attributes for a specific book.
/// Encrypts all character data before storing in the database.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `character` - The character data to be created
/// * `book_id` - The unique identifier of the book
/// * `lang` - The language code for localization
/// * `existing_character_id` - Optional existing character ID for updates or imports
/// Returns the unique identifier of the newly created character.
pub fn add_new_character(conn: &Connection, user_id: &str, character: &CharacterPropsPost, book_id: &str, lang: Lang, existing_character_id: Option<&str>) -> AppResult<String> {
    let user_key: String = get_user_encryption_key(user_id)?;
    let character_id: String = create_unique_id(existing_character_id);
    let last_update: i64 = timestamp_in_seconds();

    let character_data = repo::CharacterData {
        first_name: encrypt_data_with_user_key(&character.name, &user_key)?,
        last_name: Some(encrypt_data_with_user_key(&character.last_name, &user_key)?),
        nickname: Some(encrypt_data_with_user_key(if character.nickname.is_empty() { "" } else { &character.nickname }, &user_key)?),
        age: if let Some(age_value) = character.age { Some(encrypt_data_with_user_key(&age_value.to_string(), &user_key)?) } else { None },
        gender: Some(encrypt_data_with_user_key(if character.gender.is_empty() { "" } else { &character.gender }, &user_key)?),
        species: Some(encrypt_data_with_user_key(if character.species.is_empty() { "" } else { &character.species }, &user_key)?),
        nationality: Some(encrypt_data_with_user_key(if character.nationality.is_empty() { "" } else { &character.nationality }, &user_key)?),
        status: Some(encrypt_data_with_user_key(if character.status.is_empty() { "alive" } else { &character.status }, &user_key)?),
        title: Some(encrypt_data_with_user_key(&character.title, &user_key)?),
        category: Some(encrypt_data_with_user_key(&character.category, &user_key)?),
        image: Some(encrypt_data_with_user_key(&character.image, &user_key)?),
        role: Some(encrypt_data_with_user_key(&character.role, &user_key)?),
        biography: Some(encrypt_data_with_user_key(character.biography.as_deref().unwrap_or(""), &user_key)?),
        history: Some(encrypt_data_with_user_key(character.history.as_deref().unwrap_or(""), &user_key)?),
        speech_pattern: Some(encrypt_data_with_user_key(character.speech_pattern.as_deref().unwrap_or(""), &user_key)?),
        catchphrase: Some(encrypt_data_with_user_key(character.catchphrase.as_deref().unwrap_or(""), &user_key)?),
        residence: Some(encrypt_data_with_user_key(character.residence.as_deref().unwrap_or(""), &user_key)?),
        notes: Some(encrypt_data_with_user_key(character.notes.as_deref().unwrap_or(""), &user_key)?),
        color: Some(encrypt_data_with_user_key(character.color.as_deref().unwrap_or(""), &user_key)?),
    };

    let series_character_id: Option<&str> = character.series_character_id.as_deref();
    repo::add_new_character(conn, user_id, &character_id, &character_data, book_id, lang, series_character_id, last_update)?;

    let attribute_arrays: Vec<(&str, &Vec<AttributeName>)> = vec![
        ("physical", &character.physical),
        ("psychological", &character.psychological),
        ("relations", &character.relations),
        ("skills", &character.skills),
        ("weaknesses", &character.weaknesses),
        ("strengths", &character.strengths),
        ("goals", &character.goals),
        ("motivations", &character.motivations),
        ("arc", &character.arc),
        ("secrets", &character.secrets),
        ("fears", &character.fears),
        ("flaws", &character.flaws),
        ("beliefs", &character.beliefs),
        ("conflicts", &character.conflicts),
        ("quotes", &character.quotes),
        ("distinguishingMarks", &character.distinguishing_marks),
        ("items", &character.items),
        ("affiliations", &character.affiliations),
    ];

    for (attribute_type, attribute_items) in attribute_arrays {
        if !attribute_items.is_empty() {
            for attribute_item in attribute_items {
                add_new_attribute(conn, &character_id, user_id, attribute_type, &attribute_item.name, lang, None)?;
            }
        }
    }

    Ok(character_id)
}

/// Updates an existing character's core properties.
/// Encrypts all updated data before storing in the database.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `character` - The character data with updated values
/// * `lang` - The language code for localization
/// Returns true if the update was successful, false otherwise.
pub fn update_character(conn: &Connection, user_id: &str, character: &CharacterPropsPost, lang: Lang) -> AppResult<bool> {
    let user_key: String = get_user_encryption_key(user_id)?;

    let character_id: &str = match character.id.as_deref() {
        Some(id) => id,
        None => return Ok(false),
    };

    let last_update: i64 = timestamp_in_seconds();

    let character_data = repo::CharacterData {
        first_name: encrypt_data_with_user_key(&character.name, &user_key)?,
        last_name: Some(encrypt_data_with_user_key(&character.last_name, &user_key)?),
        nickname: Some(encrypt_data_with_user_key(if character.nickname.is_empty() { "" } else { &character.nickname }, &user_key)?),
        age: if let Some(age_value) = character.age { Some(encrypt_data_with_user_key(&age_value.to_string(), &user_key)?) } else { None },
        gender: Some(encrypt_data_with_user_key(if character.gender.is_empty() { "" } else { &character.gender }, &user_key)?),
        species: Some(encrypt_data_with_user_key(if character.species.is_empty() { "" } else { &character.species }, &user_key)?),
        nationality: Some(encrypt_data_with_user_key(if character.nationality.is_empty() { "" } else { &character.nationality }, &user_key)?),
        status: Some(encrypt_data_with_user_key(if character.status.is_empty() { "alive" } else { &character.status }, &user_key)?),
        title: Some(encrypt_data_with_user_key(&character.title, &user_key)?),
        category: Some(encrypt_data_with_user_key(&character.category, &user_key)?),
        image: Some(encrypt_data_with_user_key(&character.image, &user_key)?),
        role: Some(encrypt_data_with_user_key(&character.role, &user_key)?),
        biography: Some(encrypt_data_with_user_key(character.biography.as_deref().unwrap_or(""), &user_key)?),
        history: Some(encrypt_data_with_user_key(character.history.as_deref().unwrap_or(""), &user_key)?),
        speech_pattern: Some(encrypt_data_with_user_key(character.speech_pattern.as_deref().unwrap_or(""), &user_key)?),
        catchphrase: Some(encrypt_data_with_user_key(character.catchphrase.as_deref().unwrap_or(""), &user_key)?),
        residence: Some(encrypt_data_with_user_key(character.residence.as_deref().unwrap_or(""), &user_key)?),
        notes: Some(encrypt_data_with_user_key(character.notes.as_deref().unwrap_or(""), &user_key)?),
        color: Some(encrypt_data_with_user_key(character.color.as_deref().unwrap_or(""), &user_key)?),
    };

    let series_character_id: Option<&str> = character.series_character_id.as_deref();
    repo::update_character(conn, user_id, character_id, &character_data, last_update, lang, series_character_id)
}

/// Adds a new attribute to a character.
/// Attributes are categorized properties like physical traits, skills, or goals.
/// * `conn` - Database connection
/// * `character_id` - The unique identifier of the character
/// * `user_id` - The unique identifier of the user
/// * `r#type` - The type/category of the attribute (e.g., 'physical', 'skills')
/// * `name` - The value/name of the attribute
/// * `lang` - The language code for localization
/// * `existing_attribute_id` - Optional existing attribute ID for updates or imports
/// Returns the unique identifier of the newly created attribute.
pub fn add_new_attribute(conn: &Connection, character_id: &str, user_id: &str, r#type: &str, name: &str, lang: Lang, existing_attribute_id: Option<&str>) -> AppResult<String> {
    let user_key: String = get_user_encryption_key(user_id)?;
    let attribute_id: String = create_unique_id(existing_attribute_id);
    let encrypted_type: String = encrypt_data_with_user_key(r#type, &user_key)?;
    let encrypted_name: String = encrypt_data_with_user_key(name, &user_key)?;
    let last_update: i64 = timestamp_in_seconds();
    repo::insert_attribute(conn, &attribute_id, character_id, user_id, &encrypted_type, &encrypted_name, last_update, lang)
}

/// Deletes an attribute from a character.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `book_id` - The unique identifier of the book
/// * `attribute_id` - The unique identifier of the attribute to delete
/// * `deleted_at` - The timestamp of deletion
/// * `lang` - The language code for localization
/// Returns true if the deletion was successful, false otherwise.
pub fn delete_attribute(conn: &Connection, user_id: &str, book_id: &str, attribute_id: &str, deleted_at: i64, lang: Lang) -> AppResult<bool> {
    let deleted: bool = repo::delete_attribute(conn, user_id, attribute_id, lang)?;
    if deleted {
        let removal_id: String = create_unique_id(None);
        tombstone_repo::insert(conn, &removal_id, "book_characters_attributes", attribute_id, Some(book_id), user_id, deleted_at, lang)?;
    }
    Ok(deleted)
}

/// Deletes a character and all its related data.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `book_id` - The unique identifier of the book
/// * `character_id` - The unique identifier of the character to delete
/// * `deleted_at` - The timestamp of deletion
/// * `lang` - The language code for localization
/// Returns true if the deletion was successful.
pub fn delete_character(conn: &Connection, user_id: &str, book_id: &str, character_id: &str, deleted_at: i64, lang: Lang) -> AppResult<bool> {
    let deleted: bool = repo::delete_character(conn, user_id, character_id, lang)?;
    if deleted {
        let removal_id: String = create_unique_id(None);
        tombstone_repo::insert(conn, &removal_id, "book_characters", character_id, Some(book_id), user_id, deleted_at, lang)?;
    }
    Ok(deleted)
}

/// Retrieves all attributes for a specific character, grouped by type.
/// Decrypts attribute data using the user's encryption key.
/// * `conn` - Database connection
/// * `character_id` - The unique identifier of the character
/// * `user_id` - The unique identifier of the user
/// * `lang` - The language code for localization
/// Returns an array of character attributes grouped by type.
pub fn get_attributes(conn: &Connection, character_id: &str, user_id: &str, lang: Lang) -> AppResult<Vec<CharacterAttribute>> {
    let user_key: String = get_user_encryption_key(user_id)?;
    let encrypted_attributes: Vec<repo::AttributeResult> = repo::fetch_attributes(conn, character_id, user_id, lang)?;

    if encrypted_attributes.is_empty() {
        return Ok(vec![]);
    }

    let mut attributes_by_type: HashMap<String, Vec<Attribute>> = HashMap::new();

    for encrypted_attribute in encrypted_attributes {
        let decrypted_type: String = decrypt_data_with_user_key(&encrypted_attribute.attribute_name, &user_key)?;
        let decrypted_value: String = if encrypted_attribute.attribute_value.is_empty() { String::new() } else { decrypt_data_with_user_key(&encrypted_attribute.attribute_value, &user_key)? };

        attributes_by_type.entry(decrypted_type).or_insert_with(Vec::new).push(Attribute {
            id: encrypted_attribute.attr_id,
            name: decrypted_value,
        });
    }

    let character_attributes: Vec<CharacterAttribute> = attributes_by_type.into_iter().map(|(attribute_type, values)| CharacterAttribute {
        r#type: attribute_type,
        values,
    }).collect();

    Ok(character_attributes)
}

