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, hash_element};
use crate::crypto::key_manager::get_user_encryption_key;
use crate::domains::series_world::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 SeriesWorldElementProps {
    pub id: String,
    pub name: String,
    pub description: String,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SeriesWorldListProps {
    pub id: String,
    pub name: String,
    pub history: String,
    pub politics: String,
    pub economy: String,
    pub religion: String,
    pub languages: String,
    pub laws: Vec<SeriesWorldElementProps>,
    pub biomes: Vec<SeriesWorldElementProps>,
    pub issues: Vec<SeriesWorldElementProps>,
    pub customs: Vec<SeriesWorldElementProps>,
    pub kingdoms: Vec<SeriesWorldElementProps>,
    pub climate: Vec<SeriesWorldElementProps>,
    pub resources: Vec<SeriesWorldElementProps>,
    pub wildlife: Vec<SeriesWorldElementProps>,
    pub arts: Vec<SeriesWorldElementProps>,
    pub ethnic_groups: Vec<SeriesWorldElementProps>,
    pub social_classes: Vec<SeriesWorldElementProps>,
    pub important_characters: Vec<SeriesWorldElementProps>,
}

#[derive(Deserialize)]
pub struct SeriesWorldUpdateProps {
    pub name: String,
    pub history: Option<String>,
    pub politics: Option<String>,
    pub economy: Option<String>,
    pub religion: Option<String>,
    pub languages: Option<String>,
}

/// Retrieves all worlds and their elements 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 worlds with their categorized elements.
pub fn get_world_list(conn: &Connection, user_id: &str, series_id: &str, lang: Lang) -> AppResult<Vec<SeriesWorldListProps>> {
    let user_key: String = get_user_encryption_key(user_id)?;
    let worlds_result: Vec<repo::SeriesWorldResult> = repo::fetch_worlds(conn, user_id, series_id, lang)?;

    let mut worlds_map: HashMap<String, SeriesWorldListProps> = HashMap::new();
    let mut insertion_order: Vec<String> = Vec::new();

    for row in &worlds_result {
        if !worlds_map.contains_key(&row.world_id) {
            insertion_order.push(row.world_id.clone());
            worlds_map.insert(row.world_id.clone(), SeriesWorldListProps {
                id: row.world_id.clone(),
                name: if row.world_name.is_empty() { String::new() } else { decrypt_data_with_user_key(&row.world_name, &user_key)? },
                history: if let Some(ref history) = row.history { if history.is_empty() { String::new() } else { decrypt_data_with_user_key(history, &user_key)? } } else { String::new() },
                politics: if let Some(ref politics) = row.politics { if politics.is_empty() { String::new() } else { decrypt_data_with_user_key(politics, &user_key)? } } else { String::new() },
                economy: if let Some(ref economy) = row.economy { if economy.is_empty() { String::new() } else { decrypt_data_with_user_key(economy, &user_key)? } } else { String::new() },
                religion: if let Some(ref religion) = row.religion { if religion.is_empty() { String::new() } else { decrypt_data_with_user_key(religion, &user_key)? } } else { String::new() },
                languages: if let Some(ref languages) = row.languages { if languages.is_empty() { String::new() } else { decrypt_data_with_user_key(languages, &user_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(),
            });
        }

        if let Some(ref element_id) = row.element_id {
            let world = worlds_map.get_mut(&row.world_id).unwrap();
            let element = SeriesWorldElementProps {
                id: element_id.clone(),
                name: if let Some(ref element_name) = row.element_name { if element_name.is_empty() { String::new() } else { decrypt_data_with_user_key(element_name, &user_key)? } } else { String::new() },
                description: if let Some(ref element_description) = row.element_description { if element_description.is_empty() { String::new() } else { decrypt_data_with_user_key(element_description, &user_key)? } } else { String::new() },
            };

            if let Some(element_type) = row.element_type {
                match element_type {
                    0 => world.laws.push(element),
                    1 => world.biomes.push(element),
                    2 => world.issues.push(element),
                    3 => world.customs.push(element),
                    4 => world.kingdoms.push(element),
                    5 => world.climate.push(element),
                    6 => world.resources.push(element),
                    7 => world.wildlife.push(element),
                    8 => world.arts.push(element),
                    9 => world.ethnic_groups.push(element),
                    10 => world.social_classes.push(element),
                    11 => world.important_characters.push(element),
                    _ => {}
                }
            }
        }
    }

    let worlds_list: Vec<SeriesWorldListProps> = insertion_order
        .into_iter()
        .filter_map(|world_id| worlds_map.remove(&world_id))
        .collect();

    Ok(worlds_list)
}

/// Adds a new world 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 world
/// * `lang` - The language for error messages
/// Returns the new world ID.
/// Errors if a world with the same name already exists.
pub fn add_world(conn: &Connection, user_id: &str, series_id: &str, name: &str, lang: Lang) -> AppResult<String> {
    let hashed_name: String = hash_element(name);

    let exists: bool = repo::check_world_exist(conn, user_id, series_id, &hashed_name, lang)?;
    if exists {
        return Err(AppError::Internal(if lang == Lang::Fr { "Un monde avec ce nom existe déjà.".to_string() } else { "A world with this name already exists.".to_string() }));
    }

    let user_key: String = get_user_encryption_key(user_id)?;
    let world_id: String = create_unique_id(None);
    let encrypted_name: String = encrypt_data_with_user_key(name, &user_key)?;
    let last_update: i64 = timestamp_in_seconds();

    repo::insert_new_world(conn, &world_id, user_id, series_id, &encrypted_name, &hashed_name, last_update, lang)?;
    Ok(world_id)
}

/// Updates a world's information.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `world_id` - The unique identifier of the world
/// * `world` - The updated world data
/// * `lang` - The language for error messages
/// Returns true if successful.
pub fn update_world(conn: &Connection, user_id: &str, world_id: &str, world: SeriesWorldUpdateProps, lang: Lang) -> AppResult<bool> {
    let user_key: String = get_user_encryption_key(user_id)?;
    let encrypted_name: String = encrypt_data_with_user_key(&world.name, &user_key)?;
    let hashed_name: String = hash_element(&world.name);
    let encrypted_history: Option<String> = if let Some(ref history) = world.history { Some(encrypt_data_with_user_key(history, &user_key)?) } else { None };
    let encrypted_politics: Option<String> = if let Some(ref politics) = world.politics { Some(encrypt_data_with_user_key(politics, &user_key)?) } else { None };
    let encrypted_economy: Option<String> = if let Some(ref economy) = world.economy { Some(encrypt_data_with_user_key(economy, &user_key)?) } else { None };
    let encrypted_religion: Option<String> = if let Some(ref religion) = world.religion { Some(encrypt_data_with_user_key(religion, &user_key)?) } else { None };
    let encrypted_languages: Option<String> = if let Some(ref languages) = world.languages { Some(encrypt_data_with_user_key(languages, &user_key)?) } else { None };
    let last_update: i64 = timestamp_in_seconds();

    repo::update_world(
        conn, user_id, world_id, &encrypted_name, &hashed_name,
        encrypted_history.as_deref(), encrypted_politics.as_deref(),
        encrypted_economy.as_deref(), encrypted_religion.as_deref(),
        encrypted_languages.as_deref(), last_update, lang,
    )
}

/// Adds a new element to a world.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `world_id` - The unique identifier of the world
/// * `element_type` - The type of element (0-11)
/// * `name` - The name of the element
/// * `lang` - The language for error messages
/// * `description` - The description of the element (optional)
/// Returns the new element ID.
pub fn add_element(conn: &Connection, user_id: &str, world_id: &str, element_type: i64, name: &str, lang: Lang, description: Option<&str>) -> AppResult<String> {
    let user_key: String = get_user_encryption_key(user_id)?;
    let element_id: String = create_unique_id(None);
    let encrypted_name: String = encrypt_data_with_user_key(name, &user_key)?;
    let original_name: String = hash_element(name);
    let encrypted_description: Option<String> = if let Some(description_value) = description { Some(encrypt_data_with_user_key(description_value, &user_key)?) } else { None };
    let last_update: i64 = timestamp_in_seconds();

    repo::insert_element(conn, &element_id, world_id, user_id, element_type, &encrypted_name, &original_name, encrypted_description.as_deref(), last_update, lang)?;
    Ok(element_id)
}

/// Deletes an element from a world.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `element_id` - The unique identifier of the element
/// * `deleted_at` - The timestamp of deletion
/// * `lang` - The language for error messages
/// Returns true if successful.
pub fn delete_element(conn: &Connection, user_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, "series_world_elements", element_id, None, user_id, deleted_at, lang)?;
    }
    Ok(deleted)
}
