use rusqlite::Connection;
use serde::{Deserialize, 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::book::repo as book_repo;
use crate::domains::tombstone::repo as tombstone_repo;
use crate::domains::world::repo;
use crate::error::{AppError, AppResult};
use crate::helpers::{create_unique_id, timestamp_in_seconds};
use crate::shared::types::Lang;

/// Represents a single world element with its properties.
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct WorldElement {
    pub id: String,
    pub name: String,
    pub description: String,
    #[serde(rename = "type")]
    pub element_type: Option<i64>,
}

/// Represents a complete world with all its properties and elements.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WorldProps {
    pub id: String,
    pub name: String,
    pub history: String,
    pub politics: String,
    pub economy: String,
    pub religion: String,
    pub languages: String,
    pub laws: Vec<WorldElement>,
    pub biomes: Vec<WorldElement>,
    pub issues: Vec<WorldElement>,
    pub customs: Vec<WorldElement>,
    pub kingdoms: Vec<WorldElement>,
    pub climate: Vec<WorldElement>,
    pub resources: Vec<WorldElement>,
    pub wildlife: Vec<WorldElement>,
    pub arts: Vec<WorldElement>,
    pub ethnic_groups: Vec<WorldElement>,
    pub social_classes: Vec<WorldElement>,
    pub important_characters: Vec<WorldElement>,
    pub series_world_id: Option<String>,
}

/// Response containing the list of worlds and whether the tool is enabled.
#[derive(Debug, Serialize, Deserialize)]
pub struct WorldListResponse {
    pub worlds: Vec<WorldProps>,
    pub enabled: bool,
}

/// Mapping of element type keys to their corresponding numeric type identifiers.
fn get_element_types(element_type: &str) -> i64 {
    match element_type {
        "laws" => 1,
        "biomes" => 2,
        "issues" => 3,
        "customs" => 4,
        "kingdoms" => 5,
        "climate" => 6,
        "resources" => 7,
        "wildlife" => 8,
        "arts" => 9,
        "ethnicGroups" => 10,
        "socialClasses" => 11,
        "importantCharacters" => 12,
        _ => 0,
    }
}

/// Pushes a world element into the correct category vector of a WorldProps.
/// * `world` - The world to push the element into
/// * `element_type` - The numeric type identifier
/// * `element` - The world element to push
fn push_element_to_world(world: &mut WorldProps, element_type: i64, element: WorldElement) {
    match element_type {
        1 => world.laws.push(element),
        2 => world.biomes.push(element),
        3 => world.issues.push(element),
        4 => world.customs.push(element),
        5 => world.kingdoms.push(element),
        6 => world.climate.push(element),
        7 => world.resources.push(element),
        8 => world.wildlife.push(element),
        9 => world.arts.push(element),
        10 => world.ethnic_groups.push(element),
        11 => world.social_classes.push(element),
        12 => world.important_characters.push(element),
        _ => {}
    }
}

/// Creates a new world for a book.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user creating the world
/// * `book_id` - The unique identifier of the book to associate the world with
/// * `world_name` - The name of the new world
/// * `lang` - The language for error messages ("fr" or "en")
/// * `existing_world_id` - Optional existing world ID for syncing purposes
/// * `series_world_id` - Optional series world identifier
/// Returns the unique identifier of the newly created world.
/// Errors if a world with the same name already exists for this book.
pub fn add_new_world(
    conn: &Connection, user_id: &str, book_id: &str, world_name: &str, lang: Lang,
    existing_world_id: Option<&str>, series_world_id: Option<&str>,
) -> AppResult<String> {
    let user_encryption_key: String = get_user_encryption_key(user_id)?;
    let hashed_world_name: String = hash_element(world_name);
    if existing_world_id.is_none() && repo::check_world_exist(conn, user_id, book_id, &hashed_world_name, lang)? {
        return Err(AppError::Internal(if lang == Lang::Fr { format!("Tu as d\u{00e9}j\u{00e0} un monde {}.", world_name) } else { format!("You already have a world named {}.", world_name) }));
    }
    let encrypted_world_name: String = encrypt_data_with_user_key(world_name, &user_encryption_key)?;
    let world_id: String = create_unique_id(existing_world_id);
    let last_update: i64 = timestamp_in_seconds();
    repo::insert_new_world(conn, &world_id, user_id, book_id, &encrypted_world_name, &hashed_world_name, last_update, lang, series_world_id)
}

/// Retrieves all worlds and their elements for a specific book.
/// * `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 ("fr" or "en")
/// Returns a WorldListResponse containing an array of WorldProps and enabled flag.
pub fn get_worlds(conn: &Connection, user_id: &str, book_id: &str, lang: Lang) -> AppResult<WorldListResponse> {
    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.worlds_enabled == 1);

    let world_query_results: Vec<repo::WorldQuery> = repo::fetch_worlds(conn, user_id, book_id, lang)?;
    let user_encryption_key: String = get_user_encryption_key(user_id)?;
    let mut worlds: Vec<WorldProps> = Vec::new();

    for query_row in &world_query_results {
        let existing_world_index: Option<usize> = worlds.iter().position(|world| world.id == query_row.world_id);

        if let Some(index) = existing_world_index {
            if let Some(element_type) = query_row.element_type {
                let world_element: WorldElement = WorldElement {
                    id: query_row.element_id.clone().unwrap_or_default(),
                    name: if let Some(ref element_name) = query_row.element_name { if element_name.is_empty() { String::new() } else { decrypt_data_with_user_key(element_name, &user_encryption_key)? } } else { String::new() },
                    description: if let Some(ref element_description) = query_row.element_description { if element_description.is_empty() { String::new() } else { decrypt_data_with_user_key(element_description, &user_encryption_key)? } } else { String::new() },
                    element_type: None,
                };
                push_element_to_world(&mut worlds[index], element_type, world_element);
            }
        } else {
            let mut new_world: WorldProps = WorldProps {
                id: query_row.world_id.clone(),
                name: if query_row.world_name.is_empty() { String::new() } else { decrypt_data_with_user_key(&query_row.world_name, &user_encryption_key)? },
                history: if let Some(ref history) = query_row.history { if history.is_empty() { String::new() } else { decrypt_data_with_user_key(history, &user_encryption_key)? } } else { String::new() },
                politics: if let Some(ref politics) = query_row.politics { if politics.is_empty() { String::new() } else { decrypt_data_with_user_key(politics, &user_encryption_key)? } } else { String::new() },
                economy: if let Some(ref economy) = query_row.economy { if economy.is_empty() { String::new() } else { decrypt_data_with_user_key(economy, &user_encryption_key)? } } else { String::new() },
                religion: if let Some(ref religion) = query_row.religion { if religion.is_empty() { String::new() } else { decrypt_data_with_user_key(religion, &user_encryption_key)? } } else { String::new() },
                languages: if let Some(ref languages) = query_row.languages { if languages.is_empty() { String::new() } else { decrypt_data_with_user_key(languages, &user_encryption_key)? } } else { String::new() },
                laws: Vec::new(),
                biomes: Vec::new(),
                issues: Vec::new(),
                customs: Vec::new(),
                kingdoms: Vec::new(),
                climate: Vec::new(),
                resources: Vec::new(),
                wildlife: Vec::new(),
                arts: Vec::new(),
                ethnic_groups: Vec::new(),
                social_classes: Vec::new(),
                important_characters: Vec::new(),
                series_world_id: query_row.series_world_id.clone(),
            };

            if let Some(element_type) = query_row.element_type {
                let world_element: WorldElement = WorldElement {
                    id: query_row.element_id.clone().unwrap_or_default(),
                    name: if let Some(ref element_name) = query_row.element_name { if element_name.is_empty() { String::new() } else { decrypt_data_with_user_key(element_name, &user_encryption_key)? } } else { String::new() },
                    description: if let Some(ref element_description) = query_row.element_description { if element_description.is_empty() { String::new() } else { decrypt_data_with_user_key(element_description, &user_encryption_key)? } } else { String::new() },
                    element_type: None,
                };
                push_element_to_world(&mut new_world, element_type, world_element);
            }

            worlds.push(new_world);
        }
    }

    Ok(WorldListResponse { worlds, enabled })
}

/// Updates a world's properties and all its elements.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `world` - The WorldProps object containing updated world data
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns true if the update was successful, false otherwise.
pub fn update_world(conn: &Connection, user_id: &str, world: &WorldProps, lang: Lang) -> AppResult<bool> {
    let user_encryption_key: String = get_user_encryption_key(user_id)?;
    let encrypted_name: String = if world.name.is_empty() { String::new() } else { encrypt_data_with_user_key(&world.name, &user_encryption_key)? };
    let encrypted_history: String = if world.history.is_empty() { String::new() } else { encrypt_data_with_user_key(&world.history, &user_encryption_key)? };
    let encrypted_politics: String = if world.politics.is_empty() { String::new() } else { encrypt_data_with_user_key(&world.politics, &user_encryption_key)? };
    let encrypted_economy: String = if world.economy.is_empty() { String::new() } else { encrypt_data_with_user_key(&world.economy, &user_encryption_key)? };
    let encrypted_religion: String = if world.religion.is_empty() { String::new() } else { encrypt_data_with_user_key(&world.religion, &user_encryption_key)? };
    let encrypted_languages: String = if world.languages.is_empty() { String::new() } else { encrypt_data_with_user_key(&world.languages, &user_encryption_key)? };

    let element_categories: Vec<(&str, &Vec<WorldElement>)> = vec![
        ("laws", &world.laws),
        ("biomes", &world.biomes),
        ("issues", &world.issues),
        ("customs", &world.customs),
        ("kingdoms", &world.kingdoms),
        ("climate", &world.climate),
        ("resources", &world.resources),
        ("wildlife", &world.wildlife),
        ("arts", &world.arts),
        ("ethnicGroups", &world.ethnic_groups),
        ("socialClasses", &world.social_classes),
        ("importantCharacters", &world.important_characters),
    ];

    let mut elements_to_update: Vec<repo::WorldElementValue> = Vec::new();
    for (key, elements) in &element_categories {
        for world_element in *elements {
            let encrypted_element_name: String = encrypt_data_with_user_key(&world_element.name, &user_encryption_key)?;
            let encrypted_description: String = if world_element.description.is_empty() { String::new() } else { encrypt_data_with_user_key(&world_element.description, &user_encryption_key)? };
            let element_type_id: i64 = get_element_types(key);
            elements_to_update.push(repo::WorldElementValue {
                id: world_element.id.clone(),
                name: encrypted_element_name,
                description: encrypted_description,
                element_type: element_type_id,
            });
        }
    }

    let hashed_name: String = hash_element(&world.name);
    let last_update: i64 = timestamp_in_seconds();
    repo::update_world(conn, user_id, &world.id, &encrypted_name, &hashed_name, &encrypted_history, &encrypted_politics, &encrypted_economy, &encrypted_religion, &encrypted_languages, last_update, lang, world.series_world_id.as_deref())?;
    repo::update_world_elements(conn, user_id, &elements_to_update, last_update, lang)
}

/// Adds a new element to an existing world.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `world_id` - The unique identifier of the world to add the element to
/// * `element_name` - The name of the new element
/// * `element_type` - The type of element (e.g., "laws", "biomes", "customs")
/// * `lang` - The language for error messages ("fr" or "en")
/// * `existing_element_id` - Optional existing element ID for syncing purposes
/// Returns the unique identifier of the newly created element.
/// Errors if an element with the same name already exists in this world.
pub fn add_new_element_to_world(
    conn: &Connection, user_id: &str, world_id: &str, element_name: &str, element_type: &str,
    lang: Lang, existing_element_id: Option<&str>,
) -> AppResult<String> {
    let user_encryption_key: String = get_user_encryption_key(user_id)?;
    let hashed_element_name: String = hash_element(element_name);
    if existing_element_id.is_none() && repo::check_element_exist(conn, world_id, &hashed_element_name, lang)? {
        return Err(AppError::Internal(if lang == Lang::Fr { format!("Vous avez d\u{00e9}j\u{00e0} un \u{00e9}l\u{00e9}ment avec ce nom {}.", element_name) } else { format!("You already have an element named {}.", element_name) }));
    }
    let element_type_id: i64 = get_element_types(element_type);
    let encrypted_element_name: String = encrypt_data_with_user_key(element_name, &user_encryption_key)?;
    let element_id: String = create_unique_id(existing_element_id);
    let last_update: i64 = timestamp_in_seconds();
    repo::insert_new_element(conn, user_id, &element_id, element_type_id, world_id, &encrypted_element_name, &hashed_element_name, last_update, lang)
}

/// Removes an element from a world.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `book_id` - The unique identifier of the book
/// * `element_id` - The unique identifier of the element to remove
/// * `deleted_at` - The timestamp of deletion
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns true if the deletion was successful, false otherwise.
pub fn remove_element_from_world(conn: &Connection, user_id: &str, book_id: &str, element_id: &str, deleted_at: i64, lang: Lang) -> AppResult<bool> {
    let deleted: bool = repo::delete_element(conn, user_id, element_id, lang)?;
    if deleted {
        tombstone_repo::insert(conn, element_id, "book_world_elements", element_id, Some(book_id), user_id, deleted_at, lang)?;
    }
    Ok(deleted)
}
