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::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)]
#[serde(rename_all = "camelCase")]
pub struct SeriesDetailProps {
    pub id: String,
    pub name: String,
    pub description: String,
    pub cover_image: Option<String>,
    pub books: Vec<SeriesBookProps>,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SeriesBookProps {
    pub book_id: String,
    pub title: String,
    pub order: i64,
    pub cover_image: Option<String>,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SeriesListItemProps {
    pub id: String,
    pub name: String,
    pub description: String,
    pub cover_image: Option<String>,
    pub book_count: i64,
    pub book_ids: Vec<String>,
}

#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BooksOrderPost {
    pub book_id: String,
    pub order: i64,
}

/// Gets the list of all series for a user.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns the list of series with decrypted names and descriptions.
pub fn get_series_list(conn: &Connection, user_id: &str, lang: Lang) -> AppResult<Vec<SeriesListItemProps>> {
    let user_key: String = get_user_encryption_key(user_id)?;
    let series_results: Vec<repo::SeriesListItem> = repo::fetch_user_series(conn, user_id, lang)?;

    let mut series_list: Vec<SeriesListItemProps> = Vec::with_capacity(series_results.len());
    for series_item in series_results {
        let decrypted_name: String = decrypt_data_with_user_key(&series_item.name, &user_key)?;
        let decrypted_description: String = if let Some(ref description) = series_item.description { if description.is_empty() { String::new() } else { decrypt_data_with_user_key(description, &user_key)? } } else { String::new() };
        let book_ids: Vec<String> = if let Some(ref book_ids_str) = series_item.book_ids { book_ids_str.split(',').map(|s| s.to_string()).collect() } else { vec![] };

        series_list.push(SeriesListItemProps {
            id: series_item.series_id,
            name: decrypted_name,
            description: decrypted_description,
            cover_image: series_item.cover_image,
            book_count: series_item.book_count,
            book_ids,
        });
    }

    Ok(series_list)
}

/// Gets the detail of a series including its books.
/// * `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 ("fr" or "en")
/// Returns the series detail with decrypted data.
pub fn get_series_detail(conn: &Connection, user_id: &str, series_id: &str, lang: Lang) -> AppResult<SeriesDetailProps> {
    let user_key: String = get_user_encryption_key(user_id)?;

    let series_result: Option<repo::SeriesResult> = repo::fetch_series_by_id(conn, user_id, series_id, lang)?;
    let series_result: repo::SeriesResult = series_result.ok_or_else(|| {
        AppError::Internal(if lang == Lang::Fr { "Série non trouvée.".to_string() } else { "Series not found.".to_string() })
    })?;

    let books_result: Vec<repo::SeriesBookResult> = repo::fetch_series_books(conn, user_id, series_id, lang)?;

    let mut books: Vec<SeriesBookProps> = Vec::with_capacity(books_result.len());
    for book in books_result {
        let decrypted_title: String = decrypt_data_with_user_key(&book.title, &user_key)?;
        books.push(SeriesBookProps {
            book_id: book.book_id,
            title: decrypted_title,
            order: book.book_order,
            cover_image: book.cover_image,
        });
    }

    let decrypted_name: String = decrypt_data_with_user_key(&series_result.name, &user_key)?;
    let decrypted_description: String = if let Some(ref description) = series_result.description { if description.is_empty() { String::new() } else { decrypt_data_with_user_key(description, &user_key)? } } else { String::new() };

    Ok(SeriesDetailProps {
        id: series_result.series_id,
        name: decrypted_name,
        description: decrypted_description,
        cover_image: series_result.cover_image,
        books,
    })
}

/// Creates a new series.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `name` - The name of the series
/// * `description` - The description of the series
/// * `lang` - The language for error messages ("fr" or "en")
/// * `book_ids` - Optional array of book IDs to add to the series
/// Returns the created series ID.
pub fn create_series(conn: &Connection, user_id: &str, name: &str, description: &str, lang: Lang, book_ids: Option<&[String]>) -> AppResult<String> {
    let user_key: String = get_user_encryption_key(user_id)?;
    let series_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 encrypted_description: Option<String> = if description.is_empty() { None } else { Some(encrypt_data_with_user_key(description, &user_key)?) };
    let last_update: i64 = timestamp_in_seconds();

    repo::insert_series(conn, &series_id, user_id, &encrypted_name, &hashed_name, encrypted_description.as_deref(), last_update, lang)?;

    if let Some(book_ids) = book_ids {
        for (index, book_id) in book_ids.iter().enumerate() {
            let book_order: i64 = (index as i64) + 1;
            repo::add_book_to_series(conn, &series_id, book_id, book_order, last_update, lang)?;
        }
    }

    Ok(series_id)
}

/// Updates an existing 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 series
/// * `description` - The description of the series
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns true if the update was successful.
pub fn update_series(conn: &Connection, user_id: &str, series_id: &str, name: &str, description: &str, lang: Lang) -> AppResult<bool> {
    let exists: bool = repo::is_series_exist(conn, user_id, series_id, lang)?;
    if !exists {
        return Err(AppError::Internal(if lang == Lang::Fr { "Série non trouvée.".to_string() } else { "Series not found.".to_string() }));
    }

    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 encrypted_description: Option<String> = if description.is_empty() { None } else { Some(encrypt_data_with_user_key(description, &user_key)?) };
    let last_update: i64 = timestamp_in_seconds();

    repo::update_series(conn, user_id, series_id, &encrypted_name, &hashed_name, encrypted_description.as_deref(), last_update, lang)
}

/// Deletes a series.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `series_id` - The unique identifier of the series
/// * `deleted_at` - The timestamp of deletion
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns true if the deletion was successful.
pub fn delete_series(conn: &Connection, user_id: &str, series_id: &str, deleted_at: i64, lang: Lang) -> AppResult<bool> {
    let exists: bool = repo::is_series_exist(conn, user_id, series_id, lang)?;
    if !exists {
        return Err(AppError::Internal(if lang == Lang::Fr { "Série non trouvée.".to_string() } else { "Series not found.".to_string() }));
    }

    let deleted: bool = repo::delete_series(conn, user_id, series_id, lang)?;
    if deleted {
        tombstone_repo::insert(conn, series_id, "book_series", series_id, None, user_id, deleted_at, lang)?;
    }

    Ok(deleted)
}

/// Adds a book to a series.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `series_id` - The unique identifier of the series
/// * `book_id` - The unique identifier of the book
/// * `order` - The order of the book in the series
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns true if the addition was successful.
pub fn add_book_to_series(conn: &Connection, user_id: &str, series_id: &str, book_id: &str, order: i64, lang: Lang) -> AppResult<bool> {
    let exists: bool = repo::is_series_exist(conn, user_id, series_id, lang)?;
    if !exists {
        return Err(AppError::Internal(if lang == Lang::Fr { "Série non trouvée.".to_string() } else { "Series not found.".to_string() }));
    }

    let last_update: i64 = timestamp_in_seconds();
    repo::add_book_to_series(conn, series_id, book_id, order, last_update, lang)
}

/// Removes a book from a series.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `series_id` - The unique identifier of the series
/// * `book_id` - The unique identifier of the book
/// * `deleted_at` - The timestamp of deletion
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns true if the removal was successful.
pub fn remove_book_from_series(conn: &Connection, user_id: &str, series_id: &str, book_id: &str, deleted_at: i64, lang: Lang) -> AppResult<bool> {
    let exists: bool = repo::is_series_exist(conn, user_id, series_id, lang)?;
    if !exists {
        return Err(AppError::Internal(if lang == Lang::Fr { "Série non trouvée.".to_string() } else { "Series not found.".to_string() }));
    }

    let deleted: bool = repo::remove_book_from_series(conn, series_id, book_id, lang)?;
    if deleted {
        let entity_id: String = format!("{}_{}", series_id, book_id);
        tombstone_repo::insert(conn, &entity_id, "series_books", &entity_id, None, user_id, deleted_at, lang)?;
    }

    Ok(deleted)
}

/// Updates the order of books in a series.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `series_id` - The unique identifier of the series
/// * `books_order` - An array of BooksOrderPost with book_id and order
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns true if the update was successful.
pub fn update_books_order(conn: &Connection, user_id: &str, series_id: &str, books_order: &[BooksOrderPost], lang: Lang) -> AppResult<bool> {
    let exists: bool = repo::is_series_exist(conn, user_id, series_id, lang)?;
    if !exists {
        return Err(AppError::Internal(if lang == Lang::Fr { "Série non trouvée.".to_string() } else { "Series not found.".to_string() }));
    }

    let repo_books_order: Vec<repo::BookOrderItem> = books_order.iter().map(|item| repo::BookOrderItem {
        book_id: item.book_id.clone(),
        order: item.order,
    }).collect();
    let last_update: i64 = timestamp_in_seconds();

    repo::update_books_order(conn, series_id, &repo_books_order, last_update, lang)
}

/// Gets the series ID for a book if it belongs to one.
/// * `conn` - Database connection
/// * `book_id` - The unique identifier of the book
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns the series ID or None.
pub fn get_series_id_for_book(conn: &Connection, book_id: &str, lang: Lang) -> AppResult<Option<String>> {
    repo::get_series_id_for_book(conn, book_id, lang)
}

/// Gets only the books of a series (without series details).
/// * `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 ("fr" or "en")
/// Returns the list of books in the series.
pub fn get_series_books(conn: &Connection, user_id: &str, series_id: &str, lang: Lang) -> AppResult<Vec<SeriesBookProps>> {
    let user_key: String = get_user_encryption_key(user_id)?;

    let exists: bool = repo::is_series_exist(conn, user_id, series_id, lang)?;
    if !exists {
        return Err(AppError::Internal(if lang == Lang::Fr { "Série non trouvée.".to_string() } else { "Series not found.".to_string() }));
    }

    let books_result: Vec<repo::SeriesBookResult> = repo::fetch_series_books(conn, user_id, series_id, lang)?;

    let mut books: Vec<SeriesBookProps> = Vec::with_capacity(books_result.len());
    for book in books_result {
        let decrypted_title: String = decrypt_data_with_user_key(&book.title, &user_key)?;
        books.push(SeriesBookProps {
            book_id: book.book_id,
            title: decrypted_title,
            order: book.book_order,
            cover_image: book.cover_image,
        });
    }

    Ok(books)
}
