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_location::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(Serialize)]
pub struct SeriesLocationSubElementProps {
    pub id: String,
    pub name: String,
    pub description: String,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SeriesLocationElementProps {
    pub id: String,
    pub name: String,
    pub description: String,
    pub sub_elements: Vec<SeriesLocationSubElementProps>,
}

#[derive(Serialize)]
pub struct SeriesLocationListProps {
    pub id: String,
    pub name: String,
    pub elements: Vec<SeriesLocationElementProps>,
}

/// Retrieves all locations for a series with their elements and sub-elements.
/// * `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 locations.
pub fn get_location_list(conn: &Connection, user_id: &str, series_id: &str, lang: Lang) -> AppResult<Vec<SeriesLocationListProps>> {
    let user_key: String = get_user_encryption_key(user_id)?;
    let locations_result: Vec<repo::SeriesLocationResult> = repo::fetch_locations(conn, user_id, series_id, lang)?;

    let mut location_list: Vec<SeriesLocationListProps> = Vec::with_capacity(locations_result.len());
    for loc in locations_result {
        let elements_result: Vec<repo::SeriesLocationElementResult> = repo::fetch_elements(conn, user_id, &loc.loc_id, lang)?;

        let mut elements: Vec<SeriesLocationElementProps> = Vec::with_capacity(elements_result.len());
        for elem in elements_result {
            let sub_elements_result: Vec<repo::SeriesLocationSubElementResult> = repo::fetch_sub_elements(conn, user_id, &elem.element_id, lang)?;

            let mut sub_elements: Vec<SeriesLocationSubElementProps> = Vec::with_capacity(sub_elements_result.len());
            for sub in sub_elements_result {
                sub_elements.push(SeriesLocationSubElementProps {
                    id: sub.sub_element_id,
                    name: if sub.sub_elem_name.is_empty() { String::new() } else { decrypt_data_with_user_key(&sub.sub_elem_name, &user_key)? },
                    description: if sub.sub_elem_description.is_empty() { String::new() } else { decrypt_data_with_user_key(&sub.sub_elem_description, &user_key)? },
                });
            }

            elements.push(SeriesLocationElementProps {
                id: elem.element_id,
                name: if elem.element_name.is_empty() { String::new() } else { decrypt_data_with_user_key(&elem.element_name, &user_key)? },
                description: if elem.element_description.is_empty() { String::new() } else { decrypt_data_with_user_key(&elem.element_description, &user_key)? },
                sub_elements,
            });
        }

        location_list.push(SeriesLocationListProps {
            id: loc.loc_id,
            name: if loc.loc_name.is_empty() { String::new() } else { decrypt_data_with_user_key(&loc.loc_name, &user_key)? },
            elements,
        });
    }

    Ok(location_list)
}

/// Adds a new location section 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 location
/// * `lang` - The language for error messages
/// Returns the new location ID.
pub fn add_location_section(conn: &Connection, user_id: &str, series_id: &str, name: &str, lang: Lang) -> AppResult<String> {
    let user_key: String = get_user_encryption_key(user_id)?;
    let location_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 last_update: i64 = timestamp_in_seconds();

    repo::insert_location(conn, &location_id, series_id, user_id, &encrypted_name, &original_name, last_update, lang)
}

/// Adds a new element to a location.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `location_id` - The unique identifier of the location
/// * `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, location_id: &str, 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(desc) = description { Some(encrypt_data_with_user_key(desc, &user_key)?) } else { None };
    let last_update: i64 = timestamp_in_seconds();

    repo::insert_element(conn, &element_id, location_id, user_id, &encrypted_name, &original_name, encrypted_description.as_deref(), last_update, lang)
}

/// Adds a new sub-element to an element.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `element_id` - The unique identifier of the element
/// * `name` - The name of the sub-element
/// * `lang` - The language for error messages
/// * `description` - The description of the sub-element (optional)
/// Returns the new sub-element ID.
pub fn add_sub_element(conn: &Connection, user_id: &str, element_id: &str, name: &str, lang: Lang, description: Option<&str>) -> AppResult<String> {
    let user_key: String = get_user_encryption_key(user_id)?;
    let sub_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(desc) = description { Some(encrypt_data_with_user_key(desc, &user_key)?) } else { None };
    let last_update: i64 = timestamp_in_seconds();

    repo::insert_sub_element(conn, &sub_element_id, element_id, user_id, &encrypted_name, &original_name, encrypted_description.as_deref(), last_update, lang)
}

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

/// Deletes an element.
/// * `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_location_elements", element_id, None, user_id, deleted_at, lang)?;
    }
    Ok(deleted)
}

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