use rusqlite::{params, Connection};

use crate::error::{AppError, AppResult};
use crate::shared::types::Lang;

pub struct SeriesResult {
    pub series_id: String,
    pub _user_id: String,
    pub name: String,
    pub _hashed_name: String,
    pub description: Option<String>,
    pub cover_image: Option<String>,
    pub _last_update: i64,
}

pub struct SeriesBookResult {
    pub _series_id: String,
    pub book_id: String,
    pub book_order: i64,
    pub title: String,
    pub cover_image: Option<String>,
}

pub struct SeriesListItem {
    pub series_id: String,
    pub name: String,
    pub description: Option<String>,
    pub cover_image: Option<String>,
    pub book_count: i64,
    pub book_ids: Option<String>,
}

pub struct SeriesTableResult {
    pub series_id: String,
    pub user_id: String,
    pub name: String,
    pub hashed_name: String,
    pub description: Option<String>,
    pub cover_image: Option<String>,
    pub last_update: i64,
}

pub struct SeriesBooksTableResult {
    pub series_id: String,
    pub book_id: String,
    pub book_order: i64,
    pub last_update: i64,
}

pub struct SyncedSeriesResult {
    pub series_id: String,
    pub name: String,
    pub description: Option<String>,
    pub last_update: i64,
}

pub struct SyncedSeriesBookResult {
    pub series_id: String,
    pub book_id: String,
    pub book_order: i64,
    pub last_update: i64,
}

pub struct BookOrderItem {
    pub book_id: String,
    pub order: i64,
}

/// Fetches 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 an array of series with book counts.
pub fn fetch_user_series(conn: &Connection, user_id: &str, lang: Lang) -> AppResult<Vec<SeriesListItem>> {
    let mut statement = conn
        .prepare("SELECT series.series_id, series.name, series.description, series.cover_image, COUNT(series_books.book_id) AS book_count, GROUP_CONCAT(series_books.book_id) AS book_ids FROM book_series series LEFT JOIN series_books ON series.series_id = series_books.series_id WHERE series.user_id = ?1 GROUP BY series.series_id, series.last_update ORDER BY series.last_update DESC")
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les séries.".to_string() } else { "Unable to retrieve series.".to_string() }))?;

    let series = statement
        .query_map(params![user_id], |query_row| {
            Ok(SeriesListItem {
                series_id: query_row.get(0)?, name: query_row.get(1)?,
                description: query_row.get(2)?, cover_image: query_row.get(3)?,
                book_count: query_row.get(4)?, book_ids: query_row.get(5)?,
            })
        })
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les séries.".to_string() } else { "Unable to retrieve series.".to_string() }))?
        .collect::<Result<Vec<_>, _>>()
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les séries.".to_string() } else { "Unable to retrieve series.".to_string() }))?;

    Ok(series)
}

/// Fetches a single series by its ID.
/// * `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 result or None if not found.
pub fn fetch_series_by_id(conn: &Connection, user_id: &str, series_id: &str, lang: Lang) -> AppResult<Option<SeriesResult>> {
    let mut statement = conn
        .prepare("SELECT series_id, user_id, name, hashed_name, description, cover_image, last_update FROM book_series WHERE series_id = ?1 AND user_id = ?2")
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer la série.".to_string() } else { "Unable to retrieve series.".to_string() }))?;

    let series = statement
        .query_row(params![series_id, user_id], |query_row| {
            Ok(SeriesResult {
                series_id: query_row.get(0)?, _user_id: query_row.get(1)?,
                name: query_row.get(2)?, _hashed_name: query_row.get(3)?,
                description: query_row.get(4)?, cover_image: query_row.get(5)?,
                _last_update: query_row.get(6)?,
            })
        });

    match series {
        Ok(row) => Ok(Some(row)),
        Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
        Err(_) => Err(AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer la série.".to_string() } else { "Unable to retrieve series.".to_string() })),
    }
}

/// Inserts a new series.
/// * `conn` - Database connection
/// * `series_id` - The unique identifier for the new series
/// * `user_id` - The unique identifier of the user
/// * `name` - The encrypted name
/// * `hashed_name` - The hashed name for duplicate detection
/// * `description` - The encrypted description (nullable)
/// * `last_update` - The creation timestamp
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns the series ID if successful.
pub fn insert_series(
    conn: &Connection, series_id: &str, user_id: &str, name: &str, hashed_name: &str,
    description: Option<&str>, last_update: i64, lang: Lang,
) -> AppResult<String> {
    let insert_result = conn
        .execute(
            "INSERT INTO book_series (series_id, user_id, name, hashed_name, description, last_update) VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
            params![series_id, user_id, name, hashed_name, description, last_update],
        )
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de créer la série.".to_string() } else { "Unable to create series.".to_string() }))?;

    if insert_result > 0 {
        Ok(series_id.to_string())
    } else {
        Err(AppError::Internal(if lang == Lang::Fr { "Une erreur s'est produite lors de la création de la série.".to_string() } else { "Error creating series.".to_string() }))
    }
}

/// 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 encrypted name
/// * `hashed_name` - The hashed name
/// * `description` - The encrypted description (nullable)
/// * `last_update` - The update timestamp
/// * `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, hashed_name: &str,
    description: Option<&str>, last_update: i64, lang: Lang,
) -> AppResult<bool> {
    let update_result = conn
        .execute(
            "UPDATE book_series SET name = ?1, hashed_name = ?2, description = ?3, last_update = ?4 WHERE series_id = ?5 AND user_id = ?6",
            params![name, hashed_name, description, last_update, series_id, user_id],
        )
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de mettre à jour la série.".to_string() } else { "Unable to update series.".to_string() }))?;

    Ok(update_result > 0)
}

/// Deletes 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 ("fr" or "en")
/// Returns true if the deletion was successful.
pub fn delete_series(conn: &Connection, user_id: &str, series_id: &str, lang: Lang) -> AppResult<bool> {
    let delete_result = conn
        .execute("DELETE FROM book_series WHERE series_id = ?1 AND user_id = ?2", params![series_id, user_id])
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de supprimer la série.".to_string() } else { "Unable to delete series.".to_string() }))?;

    Ok(delete_result > 0)
}

/// Fetches all books in a series with their order.
/// * `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 an array of books in the series.
pub fn fetch_series_books(conn: &Connection, user_id: &str, series_id: &str, lang: Lang) -> AppResult<Vec<SeriesBookResult>> {
    let mut statement = conn
        .prepare("SELECT sb.series_id, sb.book_id, sb.book_order, b.title, b.cover_image FROM series_books sb INNER JOIN erit_books b ON sb.book_id = b.book_id WHERE sb.series_id = ?1 AND b.author_id = ?2 ORDER BY sb.book_order")
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les livres de la série.".to_string() } else { "Unable to retrieve series books.".to_string() }))?;

    let books = statement
        .query_map(params![series_id, user_id], |query_row| {
            Ok(SeriesBookResult {
                _series_id: query_row.get(0)?, book_id: query_row.get(1)?,
                book_order: query_row.get(2)?, title: query_row.get(3)?,
                cover_image: query_row.get(4)?,
            })
        })
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les livres de la série.".to_string() } else { "Unable to retrieve series books.".to_string() }))?
        .collect::<Result<Vec<_>, _>>()
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les livres de la série.".to_string() } else { "Unable to retrieve series books.".to_string() }))?;

    Ok(books)
}

/// Adds a book to a series.
/// * `conn` - Database connection
/// * `series_id` - The unique identifier of the series
/// * `book_id` - The unique identifier of the book
/// * `book_order` - The order of the book in the series
/// * `last_update` - The timestamp
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns true if the addition was successful.
pub fn add_book_to_series(conn: &Connection, series_id: &str, book_id: &str, book_order: i64, last_update: i64, lang: Lang) -> AppResult<bool> {
    let insert_result = conn
        .execute(
            "INSERT INTO series_books (series_id, book_id, book_order, last_update) VALUES (?1, ?2, ?3, ?4) ON CONFLICT(series_id, book_id) DO UPDATE SET book_order = excluded.book_order, last_update = excluded.last_update",
            params![series_id, book_id, book_order, last_update],
        )
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible d'ajouter le livre à la série.".to_string() } else { "Unable to add book to series.".to_string() }))?;

    Ok(insert_result > 0)
}

/// Removes a book from a series.
/// * `conn` - Database connection
/// * `series_id` - The unique identifier of the series
/// * `book_id` - The unique identifier of the book
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns true if the removal was successful.
pub fn remove_book_from_series(conn: &Connection, series_id: &str, book_id: &str, lang: Lang) -> AppResult<bool> {
    let delete_result = conn
        .execute("DELETE FROM series_books WHERE series_id = ?1 AND book_id = ?2", params![series_id, book_id])
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de retirer le livre de la série.".to_string() } else { "Unable to remove book from series.".to_string() }))?;

    Ok(delete_result > 0)
}

/// Updates the order of books in a series.
/// * `conn` - Database connection
/// * `series_id` - The unique identifier of the series
/// * `books_order` - An array of BookOrderItem with book_id and order
/// * `last_update` - The timestamp
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns true if the update was successful.
pub fn update_books_order(conn: &Connection, series_id: &str, books_order: &[BookOrderItem], last_update: i64, lang: Lang) -> AppResult<bool> {
    for book_order in books_order {
        conn.execute(
            "UPDATE series_books SET book_order = ?1, last_update = ?2 WHERE series_id = ?3 AND book_id = ?4",
            params![book_order.order, last_update, series_id, book_order.book_id],
        )
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de réordonner les livres.".to_string() } else { "Unable to reorder books.".to_string() }))?;
    }

    Ok(true)
}

/// Checks if a series exists for a user.
/// * `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 true if the series exists.
pub fn is_series_exist(conn: &Connection, user_id: &str, series_id: &str, lang: Lang) -> AppResult<bool> {
    let mut statement = conn
        .prepare("SELECT 1 FROM book_series WHERE series_id = ?1 AND user_id = ?2")
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de vérifier l'existence de la série.".to_string() } else { "Unable to check series existence.".to_string() }))?;

    let exists = statement
        .query_row(params![series_id, user_id], |_query_row| Ok(true));

    match exists {
        Ok(_) => Ok(true),
        Err(rusqlite::Error::QueryReturnedNoRows) => Ok(false),
        Err(_) => Err(AppError::Internal(if lang == Lang::Fr { "Impossible de vérifier l'existence de la série.".to_string() } else { "Unable to check series existence.".to_string() })),
    }
}

/// 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>> {
    let mut statement = conn
        .prepare("SELECT series_id FROM series_books WHERE book_id = ?1")
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de vérifier la série du livre.".to_string() } else { "Unable to check book series.".to_string() }))?;

    let result = statement
        .query_row(params![book_id], |query_row| query_row.get::<_, String>(0));

    match result {
        Ok(series_id) => Ok(Some(series_id)),
        Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
        Err(_) => Err(AppError::Internal(if lang == Lang::Fr { "Impossible de vérifier la série du livre.".to_string() } else { "Unable to check book series.".to_string() })),
    }
}

/// Fetches a series table row for sync purposes.
/// * `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 an array containing the series table row.
pub fn fetch_series_table_for_sync(conn: &Connection, user_id: &str, series_id: &str, lang: Lang) -> AppResult<Vec<SeriesTableResult>> {
    let mut statement = conn
        .prepare("SELECT series_id, user_id, name, hashed_name, description, cover_image, last_update FROM book_series WHERE series_id = ?1 AND user_id = ?2")
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer la série pour sync.".to_string() } else { "Unable to retrieve series for sync.".to_string() }))?;

    let series = statement
        .query_map(params![series_id, user_id], |query_row| {
            Ok(SeriesTableResult {
                series_id: query_row.get(0)?, user_id: query_row.get(1)?,
                name: query_row.get(2)?, hashed_name: query_row.get(3)?,
                description: query_row.get(4)?, cover_image: query_row.get(5)?,
                last_update: query_row.get(6)?,
            })
        })
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer la série pour sync.".to_string() } else { "Unable to retrieve series for sync.".to_string() }))?
        .collect::<Result<Vec<_>, _>>()
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer la série pour sync.".to_string() } else { "Unable to retrieve series for sync.".to_string() }))?;

    Ok(series)
}

/// Fetches all series-books relationships for sync.
/// * `conn` - Database connection
/// * `series_id` - The unique identifier of the series
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns an array of series-books table rows.
pub fn fetch_series_books_table(conn: &Connection, series_id: &str, lang: Lang) -> AppResult<Vec<SeriesBooksTableResult>> {
    let mut statement = conn
        .prepare("SELECT series_id, book_id, book_order, last_update FROM series_books WHERE series_id = ?1 ORDER BY book_order")
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les livres de la série pour sync.".to_string() } else { "Unable to retrieve series books for sync.".to_string() }))?;

    let books = statement
        .query_map(params![series_id], |query_row| {
            Ok(SeriesBooksTableResult {
                series_id: query_row.get(0)?, book_id: query_row.get(1)?,
                book_order: query_row.get(2)?, last_update: query_row.get(3)?,
            })
        })
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les livres de la série pour sync.".to_string() } else { "Unable to retrieve series books for sync.".to_string() }))?
        .collect::<Result<Vec<_>, _>>()
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les livres de la série pour sync.".to_string() } else { "Unable to retrieve series books for sync.".to_string() }))?;

    Ok(books)
}

/// Fetches all series for a user for sync comparison.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns an array of synced series results.
pub fn fetch_synced_series(conn: &Connection, user_id: &str, lang: Lang) -> AppResult<Vec<SyncedSeriesResult>> {
    let mut statement = conn
        .prepare("SELECT series_id, name, description, last_update FROM book_series WHERE user_id = ?1 ORDER BY last_update DESC")
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les séries pour sync.".to_string() } else { "Unable to retrieve series for sync.".to_string() }))?;

    let series = statement
        .query_map(params![user_id], |query_row| {
            Ok(SyncedSeriesResult {
                series_id: query_row.get(0)?, name: query_row.get(1)?,
                description: query_row.get(2)?, last_update: query_row.get(3)?,
            })
        })
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les séries pour sync.".to_string() } else { "Unable to retrieve series for sync.".to_string() }))?
        .collect::<Result<Vec<_>, _>>()
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les séries pour sync.".to_string() } else { "Unable to retrieve series for sync.".to_string() }))?;

    Ok(series)
}

/// Fetches all series-books relationships for a user for sync comparison.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns an array of synced series book results.
pub fn fetch_synced_series_books(conn: &Connection, user_id: &str, lang: Lang) -> AppResult<Vec<SyncedSeriesBookResult>> {
    let mut statement = conn
        .prepare("SELECT sb.series_id, sb.book_id, sb.book_order, sb.last_update FROM series_books sb INNER JOIN book_series bs ON sb.series_id = bs.series_id WHERE bs.user_id = ?1 ORDER BY sb.book_order")
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les livres de séries pour sync.".to_string() } else { "Unable to retrieve series books for sync.".to_string() }))?;

    let books = statement
        .query_map(params![user_id], |query_row| {
            Ok(SyncedSeriesBookResult {
                series_id: query_row.get(0)?, book_id: query_row.get(1)?,
                book_order: query_row.get(2)?, last_update: query_row.get(3)?,
            })
        })
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les livres de séries pour sync.".to_string() } else { "Unable to retrieve series books for sync.".to_string() }))?
        .collect::<Result<Vec<_>, _>>()
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les livres de séries pour sync.".to_string() } else { "Unable to retrieve series books for sync.".to_string() }))?;

    Ok(books)
}

/// Inserts a series for sync purposes.
/// * `conn` - Database connection
/// * `series_id` - The unique identifier of the series
/// * `user_id` - The unique identifier of the user
/// * `name` - The encrypted name
/// * `hashed_name` - The hashed name
/// * `description` - The encrypted description (nullable)
/// * `cover_image` - The cover image (nullable)
/// * `last_update` - The sync timestamp
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns true if the insertion was successful.
pub fn insert_sync_series(
    conn: &Connection, series_id: &str, user_id: &str, name: &str, hashed_name: &str,
    description: Option<&str>, cover_image: Option<&str>, last_update: i64, lang: Lang,
) -> AppResult<bool> {
    let insert_result = conn
        .execute(
            "INSERT INTO book_series (series_id, user_id, name, hashed_name, description, cover_image, last_update) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7) ON CONFLICT(series_id) DO UPDATE SET name = excluded.name, hashed_name = excluded.hashed_name, description = excluded.description, cover_image = excluded.cover_image, last_update = excluded.last_update",
            params![series_id, user_id, name, hashed_name, description, cover_image, last_update],
        )
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible d'insérer la série pour sync.".to_string() } else { "Unable to insert series for sync.".to_string() }))?;

    Ok(insert_result > 0)
}

/// Updates a series for sync purposes.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `series_id` - The unique identifier of the series
/// * `name` - The encrypted name
/// * `hashed_name` - The hashed name
/// * `description` - The encrypted description (nullable)
/// * `cover_image` - The cover image (nullable)
/// * `last_update` - The sync timestamp
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns true if the update was successful.
pub fn update_sync_series(
    conn: &Connection, user_id: &str, series_id: &str, name: &str, hashed_name: &str,
    description: Option<&str>, cover_image: Option<&str>, last_update: i64, lang: Lang,
) -> AppResult<bool> {
    let update_result = conn
        .execute(
            "UPDATE book_series SET name = ?1, hashed_name = ?2, description = ?3, cover_image = ?4, last_update = ?5 WHERE series_id = ?6 AND user_id = ?7",
            params![name, hashed_name, description, cover_image, last_update, series_id, user_id],
        )
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de mettre à jour la série pour sync.".to_string() } else { "Unable to update series for sync.".to_string() }))?;

    Ok(update_result > 0)
}

/// Inserts a series-book relationship for sync.
/// * `conn` - Database connection
/// * `series_id` - The unique identifier of the series
/// * `book_id` - The unique identifier of the book
/// * `book_order` - The order of the book
/// * `last_update` - The sync timestamp
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns true if the insertion was successful.
pub fn insert_sync_series_book(conn: &Connection, series_id: &str, book_id: &str, book_order: i64, last_update: i64, lang: Lang) -> AppResult<bool> {
    let insert_result = conn
        .execute(
            "INSERT INTO series_books (series_id, book_id, book_order, last_update) VALUES (?1, ?2, ?3, ?4) ON CONFLICT(series_id, book_id) DO UPDATE SET book_order = excluded.book_order, last_update = excluded.last_update",
            params![series_id, book_id, book_order, last_update],
        )
        .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible d'insérer la liaison série-livre pour sync.".to_string() } else { "Unable to insert series-book for sync.".to_string() }))?;

    Ok(insert_result > 0)
}

/// Checks if a series exists for a user (alias for is_series_exist).
/// * `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 true if the series exists.
pub fn series_exists(conn: &Connection, user_id: &str, series_id: &str, lang: Lang) -> AppResult<bool> {
    is_series_exist(conn, user_id, series_id, lang)
}
