//! Integration tests for manga-level content warnings: the deduped union
//! on the detail endpoint and the include/exclude filters on the list.

mod common;

use axum::http::StatusCode;
use axum::Router;
use sqlx::PgPool;
use tower::ServiceExt;
use uuid::Uuid;

use mangalord::domain::page_analysis::{SafetyFlag, VisionAnalysis};
use mangalord::repo;

/// Seed a manga whose pages carry the given per-page warning sets, and
/// return the manga id.
async fn seed_manga(pool: &PgPool, title: &str, pages: &[&[&str]]) -> Uuid {
    let manga_id: Uuid =
        sqlx::query_scalar("INSERT INTO mangas (title) VALUES ($1) RETURNING id")
            .bind(title)
            .fetch_one(pool)
            .await
            .unwrap();
    let chapter_id: Uuid =
        sqlx::query_scalar("INSERT INTO chapters (manga_id, number) VALUES ($1, 1) RETURNING id")
            .bind(manga_id)
            .fetch_one(pool)
            .await
            .unwrap();
    for (i, warnings) in pages.iter().enumerate() {
        let page_id: Uuid = sqlx::query_scalar(
            "INSERT INTO pages (chapter_id, page_number, storage_key, content_type) \
             VALUES ($1, $2, $3, 'image/png') RETURNING id",
        )
        .bind(chapter_id)
        .bind((i + 1) as i32)
        .bind(format!("k/{i}.png"))
        .fetch_one(pool)
        .await
        .unwrap();
        let analysis = VisionAnalysis {
            ocr_results: vec![],
            tagging_results: vec![],
            scene_description: String::new(),
            safety_flag: SafetyFlag {
                is_nsfw: !warnings.is_empty(),
                content_type: warnings.iter().map(|w| w.to_string()).collect(),
            },
        };
        repo::page_analysis::persist_analysis(pool, page_id, &analysis, "m")
            .await
            .unwrap();
    }
    manga_id
}

async fn list_ids(app: &Router, query: &str) -> Vec<String> {
    let resp = app
        .clone()
        .oneshot(common::get(&format!("/api/v1/mangas?{query}")))
        .await
        .unwrap();
    assert_eq!(resp.status(), StatusCode::OK);
    let body = common::body_json(resp).await;
    body["items"]
        .as_array()
        .unwrap()
        .iter()
        .map(|m| m["id"].as_str().unwrap().to_string())
        .collect()
}

#[sqlx::test(migrations = "./migrations")]
async fn detail_returns_deduped_warning_union(pool: PgPool) {
    let h = common::harness(pool.clone());
    // Two pages: one sexual+gore, one gore — union is [gore, sexual].
    let manga_id = seed_manga(&pool, "M", &[&["sexual", "gore"], &["gore"]]).await;

    let resp = h
        .app
        .clone()
        .oneshot(common::get(&format!("/api/v1/mangas/{manga_id}")))
        .await
        .unwrap();
    assert_eq!(resp.status(), StatusCode::OK);
    let body = common::body_json(resp).await;
    let warnings: Vec<String> = body["content_warnings"]
        .as_array()
        .unwrap()
        .iter()
        .map(|w| w.as_str().unwrap().to_string())
        .collect();
    assert_eq!(warnings, vec!["gore", "sexual"], "deduped + alphabetical");
}

#[sqlx::test(migrations = "./migrations")]
async fn detail_has_empty_warnings_when_unflagged(pool: PgPool) {
    let h = common::harness(pool.clone());
    let manga_id = seed_manga(&pool, "Clean", &[&[]]).await;

    let resp = h
        .app
        .clone()
        .oneshot(common::get(&format!("/api/v1/mangas/{manga_id}")))
        .await
        .unwrap();
    let body = common::body_json(resp).await;
    assert_eq!(body["content_warnings"].as_array().unwrap().len(), 0);
}

#[sqlx::test(migrations = "./migrations")]
async fn list_filters_by_content_warning(pool: PgPool) {
    let h = common::harness(pool.clone());
    let gory = seed_manga(&pool, "Gory", &[&["gore"]]).await;
    let sexy = seed_manga(&pool, "Sexy", &[&["sexual"]]).await;
    let clean = seed_manga(&pool, "Clean", &[&[]]).await;

    // include=gore → only the gory manga.
    let ids = list_ids(&h.app, "cw_include=gore").await;
    assert_eq!(ids, vec![gory.to_string()]);

    // exclude=gore → the others (sexy + clean), not gory.
    let mut ids = list_ids(&h.app, "cw_exclude=gore").await;
    ids.sort();
    let mut want = vec![sexy.to_string(), clean.to_string()];
    want.sort();
    assert_eq!(ids, want);
}

#[sqlx::test(migrations = "./migrations")]
async fn list_rejects_unknown_warning(pool: PgPool) {
    let h = common::harness(pool.clone());
    let resp = h
        .app
        .clone()
        .oneshot(common::get("/api/v1/mangas?cw_include=spicy"))
        .await
        .unwrap();
    assert_eq!(resp.status(), StatusCode::UNPROCESSABLE_ENTITY);
}
