use api::{AppState, build_app, migrate_sessions};
use axum::body::Body;
use axum::http::{Request, StatusCode, header};
use db::users;
use domain::{AuditActor, Email, NewUser, Role};
use http_body_util::BodyExt;
use sqlx::PgPool;
use tower::ServiceExt;

async fn post_json(
    app: &axum::Router,
    cookie: &str,
    uri: &str,
    body: &str,
) -> axum::http::Response<Body> {
    app.clone()
        .oneshot(
            Request::builder()
                .method("POST")
                .uri(uri)
                .header(header::COOKIE, cookie)
                .header(header::CONTENT_TYPE, "application/json")
                .body(Body::from(body.to_owned()))
                .unwrap(),
        )
        .await
        .unwrap()
}

fn state(pool: PgPool) -> AppState {
    AppState {
        db: db::Db::from_pool(pool),
        app_name: "Test".into(),
        cookie_secure: false,
        search: None,
        default_language: "sv".into(),
        default_timezone: "Europe/Stockholm".into(),
    }
}

async fn seed_user(pool: &PgPool, email: &str, password: &str, role: Role) {
    let db = db::Db::from_pool(pool.clone());
    let mut tx = db.pool().begin().await.unwrap();

    users::create_user(
        &mut tx,
        AuditActor::System,
        &NewUser {
            email: Email::parse(email).unwrap(),
            password_hash: auth::hash_password(password).unwrap(),
            role,
        },
    )
    .await
    .unwrap();

    tx.commit().await.unwrap();
}

async fn login(app: &axum::Router, email: &str, password: &str) -> String {
    let resp = app
        .clone()
        .oneshot(
            Request::builder()
                .method("POST")
                .uri("/api/admin/login")
                .header(header::CONTENT_TYPE, "application/json")
                .body(Body::from(format!(
                    r#"{{"email":"{email}","password":"{password}"}}"#
                )))
                .unwrap(),
        )
        .await
        .unwrap();

    assert_eq!(resp.status(), StatusCode::NO_CONTENT);

    resp.headers()
        .get(header::SET_COOKIE)
        .unwrap()
        .to_str()
        .unwrap()
        .split(';')
        .next()
        .unwrap()
        .to_owned()
}

async fn post_field(app: &axum::Router, cookie: &str, body: &str) -> axum::http::Response<Body> {
    post_json(app, cookie, "/api/admin/field-definitions", body).await
}

#[sqlx::test(migrations = "../db/migrations")]
async fn create_requires_auth(pool: PgPool) {
    migrate_sessions(&db::Db::from_pool(pool.clone()))
        .await
        .unwrap();

    let app = build_app(state(pool));

    let resp = app
        .oneshot(
            Request::builder()
                .method("POST")
                .uri("/api/admin/field-definitions")
                .header(header::CONTENT_TYPE, "application/json")
                .body(Body::from(
                    r#"{"key":"x","data_type":"text","required":false,"labels":[{"lang":"en","label":"X"}]}"#,
                ))
                .unwrap(),
        )
        .await
        .unwrap();

    assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
}

#[sqlx::test(migrations = "../db/migrations")]
async fn create_scalar_field_then_lists_it(pool: PgPool) {
    migrate_sessions(&db::Db::from_pool(pool.clone()))
        .await
        .unwrap();

    seed_user(&pool, "ed@example.com", "pw-editor-123", Role::Editor).await;

    let app = build_app(state(pool));
    let cookie = login(&app, "ed@example.com", "pw-editor-123").await;

    let resp = post_field(
        &app,
        &cookie,
        r#"{"key":"height_cm","data_type":"integer","required":true,"group":"Dimensions","labels":[{"lang":"en","label":"Height"},{"lang":"sv","label":"Höjd"}]}"#,
    )
    .await;

    assert_eq!(resp.status(), StatusCode::CREATED);

    let body: serde_json::Value =
        serde_json::from_slice(&resp.into_body().collect().await.unwrap().to_bytes()).unwrap();

    assert_eq!(body["key"], "height_cm");

    let list = app
        .oneshot(
            Request::builder()
                .uri("/api/admin/field-definitions")
                .header(header::COOKIE, &cookie)
                .body(Body::empty())
                .unwrap(),
        )
        .await
        .unwrap();

    let defs: serde_json::Value =
        serde_json::from_slice(&list.into_body().collect().await.unwrap().to_bytes()).unwrap();

    assert!(
        defs.as_array()
            .unwrap()
            .iter()
            .any(|d| d["key"] == "height_cm")
    );
}

#[sqlx::test(migrations = "../db/migrations")]
async fn term_without_vocabulary_is_422(pool: PgPool) {
    migrate_sessions(&db::Db::from_pool(pool.clone()))
        .await
        .unwrap();

    seed_user(&pool, "ed@example.com", "pw-editor-123", Role::Editor).await;

    let app = build_app(state(pool));
    let cookie = login(&app, "ed@example.com", "pw-editor-123").await;

    let resp = post_field(
        &app,
        &cookie,
        r#"{"key":"material","data_type":"term","required":false,"labels":[{"lang":"en","label":"Material"}]}"#,
    )
    .await;

    assert_eq!(resp.status(), StatusCode::UNPROCESSABLE_ENTITY);
}

#[sqlx::test(migrations = "../db/migrations")]
async fn duplicate_key_is_409(pool: PgPool) {
    migrate_sessions(&db::Db::from_pool(pool.clone()))
        .await
        .unwrap();

    seed_user(&pool, "ed@example.com", "pw-editor-123", Role::Editor).await;

    let app = build_app(state(pool));
    let cookie = login(&app, "ed@example.com", "pw-editor-123").await;

    let body = r#"{"key":"dup","data_type":"text","required":false,"labels":[{"lang":"en","label":"Dup"}]}"#;

    assert_eq!(
        post_field(&app, &cookie, body).await.status(),
        StatusCode::CREATED
    );
    assert_eq!(
        post_field(&app, &cookie, body).await.status(),
        StatusCode::CONFLICT
    );
}

#[sqlx::test(migrations = "../db/migrations")]
async fn create_term_field_with_valid_vocabulary(pool: PgPool) {
    migrate_sessions(&db::Db::from_pool(pool.clone()))
        .await
        .unwrap();

    seed_user(&pool, "ed@example.com", "pw-editor-123", Role::Editor).await;

    let app = build_app(state(pool));
    let cookie = login(&app, "ed@example.com", "pw-editor-123").await;

    let vocab_resp = post_json(
        &app,
        &cookie,
        "/api/admin/vocabularies",
        r#"{"key":"material"}"#,
    )
    .await;

    assert_eq!(vocab_resp.status(), StatusCode::CREATED);

    let vocab_body: serde_json::Value =
        serde_json::from_slice(&vocab_resp.into_body().collect().await.unwrap().to_bytes())
            .unwrap();

    let vocab_id = vocab_body["id"].as_str().unwrap();

    let resp = post_field(
        &app,
        &cookie,
        &format!(
            r#"{{"key":"material_ref","data_type":"term","vocabulary_id":"{vocab_id}","required":false,"labels":[{{"lang":"en","label":"Material"}}]}}"#
        ),
    )
    .await;

    assert_eq!(resp.status(), StatusCode::CREATED);
}

#[sqlx::test(migrations = "../db/migrations")]
async fn term_with_nonexistent_vocabulary_is_422(pool: PgPool) {
    migrate_sessions(&db::Db::from_pool(pool.clone()))
        .await
        .unwrap();

    seed_user(&pool, "ed@example.com", "pw-editor-123", Role::Editor).await;

    let app = build_app(state(pool));
    let cookie = login(&app, "ed@example.com", "pw-editor-123").await;

    let resp = post_field(
        &app,
        &cookie,
        r#"{"key":"bad_ref","data_type":"term","vocabulary_id":"00000000-0000-0000-0000-000000000000","required":false,"labels":[{"lang":"en","label":"Bad"}]}"#,
    )
    .await;

    assert_eq!(resp.status(), StatusCode::UNPROCESSABLE_ENTITY);
}

#[sqlx::test(migrations = "../db/migrations")]
async fn create_authority_field(pool: PgPool) {
    migrate_sessions(&db::Db::from_pool(pool.clone()))
        .await
        .unwrap();

    seed_user(&pool, "ed@example.com", "pw-editor-123", Role::Editor).await;

    let app = build_app(state(pool));
    let cookie = login(&app, "ed@example.com", "pw-editor-123").await;

    let resp = post_field(
        &app,
        &cookie,
        r#"{"key":"maker_ref","data_type":"authority","authority_kind":"person","required":false,"labels":[{"lang":"en","label":"Maker"}]}"#,
    )
    .await;

    assert_eq!(resp.status(), StatusCode::CREATED);
}

#[sqlx::test(migrations = "../db/migrations")]
async fn empty_key_is_422(pool: PgPool) {
    migrate_sessions(&db::Db::from_pool(pool.clone()))
        .await
        .unwrap();

    seed_user(&pool, "ed@example.com", "pw-editor-123", Role::Editor).await;

    let app = build_app(state(pool));
    let cookie = login(&app, "ed@example.com", "pw-editor-123").await;

    let resp = post_field(
        &app,
        &cookie,
        r#"{"key":"","data_type":"text","required":false,"labels":[{"lang":"en","label":"X"}]}"#,
    )
    .await;

    assert_eq!(resp.status(), StatusCode::UNPROCESSABLE_ENTITY);
}
