use db::{Db, authority, catalog, fields};
use domain::{
    AuditActor, AuthorityKind, LocalizedLabel, NewAuthority, NewFieldDefinition, Visibility,
};
use sqlx::PgPool;

fn sample_object_input() -> domain::ObjectInput {
    domain::ObjectInput {
        object_number: "X.1".into(),
        object_name: "Test".into(),
        number_of_objects: 1,
        brief_description: None,
        current_location: None,
        current_owner: None,
        recorder: None,
        recording_date: None,
        visibility: Visibility::Draft,
    }
}

fn new_person(name_sv: &str, name_en: &str) -> NewAuthority {
    NewAuthority {
        kind: AuthorityKind::Person,
        external_uri: None,
        labels: vec![
            LocalizedLabel {
                lang: "sv".into(),
                label: name_sv.into(),
            },
            LocalizedLabel {
                lang: "en".into(),
                label: name_en.into(),
            },
        ],
    }
}

#[sqlx::test]
async fn authority_round_trips_with_labels(pool: PgPool) {
    let db = Db::from_pool(pool);

    let mut tx = db.pool().begin().await.unwrap();
    let id = authority::create_authority(
        &mut tx,
        AuditActor::System,
        &new_person("Carl Larsson", "Carl Larsson"),
    )
    .await
    .unwrap();
    tx.commit().await.unwrap();

    let got = authority::authority_by_id(db.pool(), id)
        .await
        .unwrap()
        .unwrap();
    assert_eq!(got.id, id);
    assert_eq!(got.kind, AuthorityKind::Person);
    assert_eq!(got.labels.len(), 2);
    assert_eq!(
        domain::pick_label(&got.labels, "sv", "en"),
        Some("Carl Larsson")
    );
}

#[sqlx::test]
async fn list_by_kind_filters(pool: PgPool) {
    let db = Db::from_pool(pool);

    let mut tx = db.pool().begin().await.unwrap();
    authority::create_authority(&mut tx, AuditActor::System, &new_person("A", "A"))
        .await
        .unwrap();
    authority::create_authority(
        &mut tx,
        AuditActor::System,
        &NewAuthority {
            kind: AuthorityKind::Place,
            external_uri: None,
            labels: vec![LocalizedLabel {
                lang: "en".into(),
                label: "Stockholm".into(),
            }],
        },
    )
    .await
    .unwrap();
    tx.commit().await.unwrap();

    let people = authority::list_by_kind(db.pool(), AuthorityKind::Person)
        .await
        .unwrap();
    assert_eq!(people.len(), 1);
    assert_eq!(people[0].kind, AuthorityKind::Person);

    let places = authority::list_by_kind(db.pool(), AuthorityKind::Place)
        .await
        .unwrap();
    assert_eq!(places.len(), 1);
    assert_eq!(places[0].kind, AuthorityKind::Place);
}

#[sqlx::test]
async fn resolve_authority_returns_kind(pool: PgPool) {
    let db = Db::from_pool(pool);

    let mut tx = db.pool().begin().await.unwrap();
    let id = authority::create_authority(&mut tx, AuditActor::System, &new_person("X", "X"))
        .await
        .unwrap();
    tx.commit().await.unwrap();

    let r = authority::resolve_authority(db.pool(), id)
        .await
        .unwrap()
        .unwrap();
    assert_eq!(r.authority_id(), id);
    assert_eq!(r.kind(), AuthorityKind::Person);

    let missing = authority::resolve_authority(db.pool(), domain::AuthorityId::new())
        .await
        .unwrap();
    assert!(missing.is_none());
}

#[sqlx::test]
async fn authority_with_no_labels_round_trips_empty(pool: PgPool) {
    let db = Db::from_pool(pool);

    let mut tx = db.pool().begin().await.unwrap();
    let id = authority::create_authority(
        &mut tx,
        AuditActor::System,
        &NewAuthority {
            kind: AuthorityKind::Organisation,
            external_uri: None,
            labels: vec![],
        },
    )
    .await
    .unwrap();
    tx.commit().await.unwrap();

    let got = authority::authority_by_id(db.pool(), id)
        .await
        .unwrap()
        .unwrap();
    assert_eq!(got.kind, AuthorityKind::Organisation);
    assert!(got.labels.is_empty());
}

#[sqlx::test(migrations = "../db/migrations")]
async fn update_authority_changes_labels(pool: PgPool) {
    let db = Db::from_pool(pool);
    let mut tx = db.pool().begin().await.unwrap();
    let id = authority::create_authority(
        &mut tx,
        AuditActor::System,
        &NewAuthority {
            kind: AuthorityKind::Person,
            external_uri: None,
            labels: vec![LocalizedLabel {
                lang: "sv".into(),
                label: "Anon".into(),
            }],
        },
    )
    .await
    .unwrap();

    let existed = authority::update_authority(
        &mut tx,
        AuditActor::System,
        id,
        Some("https://viaf.org/1"),
        &[LocalizedLabel {
            lang: "sv".into(),
            label: "Astrid".into(),
        }],
    )
    .await
    .unwrap();
    assert!(existed);
    tx.commit().await.unwrap();

    let a = authority::authority_by_id(db.pool(), id)
        .await
        .unwrap()
        .unwrap();
    assert_eq!(a.external_uri.as_deref(), Some("https://viaf.org/1"));
    assert_eq!(a.labels[0].label, "Astrid");
}

#[sqlx::test(migrations = "../db/migrations")]
async fn delete_authority_blocks_when_referenced(pool: PgPool) {
    use db::DeleteOutcome;

    let db = Db::from_pool(pool);
    let mut tx = db.pool().begin().await.unwrap();
    let id = authority::create_authority(
        &mut tx,
        AuditActor::System,
        &NewAuthority {
            kind: AuthorityKind::Person,
            external_uri: None,
            labels: vec![LocalizedLabel {
                lang: "sv".into(),
                label: "Astrid".into(),
            }],
        },
    )
    .await
    .unwrap();

    fields::create_field_definition(
        &mut tx,
        &NewFieldDefinition {
            key: "maker".into(),
            field_type: domain::FieldType::Authority {
                kind: Some(AuthorityKind::Person),
            },
            required: false,
            group_key: None,
            labels: vec![LocalizedLabel {
                lang: "sv".into(),
                label: "Tillverkare".into(),
            }],
        },
    )
    .await
    .unwrap();

    let obj = catalog::create_object(&mut tx, AuditActor::System, &sample_object_input())
        .await
        .unwrap();
    let mut map = serde_json::Map::new();
    map.insert("maker".into(), serde_json::Value::String(id.to_string()));
    catalog::set_object_fields(&mut tx, AuditActor::System, obj, &map)
        .await
        .unwrap();

    assert_eq!(
        authority::delete_authority(&mut tx, AuditActor::System, id)
            .await
            .unwrap(),
        DeleteOutcome::InUse { count: 1 }
    );

    catalog::set_object_fields(&mut tx, AuditActor::System, obj, &serde_json::Map::new())
        .await
        .unwrap();
    assert_eq!(
        authority::delete_authority(&mut tx, AuditActor::System, id)
            .await
            .unwrap(),
        DeleteOutcome::Deleted
    );
    assert_eq!(
        authority::delete_authority(&mut tx, AuditActor::System, id)
            .await
            .unwrap(),
        DeleteOutcome::NotFound
    );
}
