use std::collections::BTreeMap;

use sqlx::{PgConnection, PgPool};
use uuid::Uuid;

use crate::domain::genre::{Genre, GenreRef};
use crate::error::AppResult;

pub async fn list_all(pool: &PgPool) -> AppResult<Vec<Genre>> {
    let rows = sqlx::query_as::<_, Genre>(
        "SELECT id, name FROM genres ORDER BY lower(name)",
    )
    .fetch_all(pool)
    .await?;
    Ok(rows)
}

pub async fn list_for_manga(pool: &PgPool, manga_id: Uuid) -> AppResult<Vec<GenreRef>> {
    let rows = sqlx::query_as::<_, GenreRef>(
        r#"
        SELECT g.id, g.name
          FROM manga_genres mg
          JOIN genres g ON g.id = mg.genre_id
         WHERE mg.manga_id = $1
         ORDER BY lower(g.name)
        "#,
    )
    .bind(manga_id)
    .fetch_all(pool)
    .await?;
    Ok(rows)
}

pub async fn load_for_mangas(
    pool: &PgPool,
    manga_ids: &[Uuid],
) -> AppResult<BTreeMap<Uuid, Vec<GenreRef>>> {
    if manga_ids.is_empty() {
        return Ok(BTreeMap::new());
    }
    let rows: Vec<(Uuid, Uuid, String)> = sqlx::query_as(
        r#"
        SELECT mg.manga_id, g.id, g.name
          FROM manga_genres mg
          JOIN genres g ON g.id = mg.genre_id
         WHERE mg.manga_id = ANY($1)
         ORDER BY mg.manga_id, lower(g.name)
        "#,
    )
    .bind(manga_ids)
    .fetch_all(pool)
    .await?;
    let mut grouped: BTreeMap<Uuid, Vec<GenreRef>> = BTreeMap::new();
    for (manga_id, id, name) in rows {
        grouped.entry(manga_id).or_default().push(GenreRef { id, name });
    }
    Ok(grouped)
}

/// Replace the manga's genres. Unknown ids are silently ignored: the
/// FK constraint would reject them, so we filter upstream rather than
/// surface a 500 here. (The API layer validates the set against
/// `list_all` first.)
///
/// Note: `crawler::repo::sync_genres` does a similar replace, but by
/// *name* and with auto-create of unseen genres — the crawler can't
/// validate against the curated vocabulary on its own. Both paths are
/// intentional; don't merge them without preserving that semantic.
pub async fn set_for_manga(
    conn: &mut PgConnection,
    manga_id: Uuid,
    genre_ids: &[Uuid],
) -> AppResult<()> {
    sqlx::query("DELETE FROM manga_genres WHERE manga_id = $1")
        .bind(manga_id)
        .execute(&mut *conn)
        .await?;
    if genre_ids.is_empty() {
        return Ok(());
    }
    sqlx::query(
        r#"
        INSERT INTO manga_genres (manga_id, genre_id)
        SELECT $1, unnest($2::uuid[])
        ON CONFLICT DO NOTHING
        "#,
    )
    .bind(manga_id)
    .bind(genre_ids)
    .execute(&mut *conn)
    .await?;
    Ok(())
}
