use db::Db;
use db::audit;
use domain::{AuditAction, AuditActor, FieldChange, NewAuditEvent};
use serde_json::json;
use sqlx::PgPool;
use uuid::Uuid;

fn created(entity_id: Uuid, name: &str) -> NewAuditEvent {
    NewAuditEvent {
        actor: AuditActor::System,
        action: AuditAction::Created,
        entity_type: "object".into(),
        entity_id,
        changes: vec![FieldChange {
            field: "name".into(),
            before: None,
            after: Some(json!(name)),
        }],
    }
}

#[sqlx::test]
async fn records_and_reads_back_history_in_order(pool: PgPool) {
    let db = Db::from_pool(pool);
    let id = Uuid::new_v4();
    let user = Uuid::new_v4();

    audit::record(db.pool(), &created(id, "Vase"))
        .await
        .unwrap();
    audit::record(
        db.pool(),
        &NewAuditEvent {
            actor: AuditActor::User(user),
            action: AuditAction::Updated,
            entity_type: "object".into(),
            entity_id: id,
            changes: vec![FieldChange {
                field: "name".into(),
                before: Some(json!("Vase")),
                after: Some(json!("Roman Vase")),
            }],
        },
    )
    .await
    .unwrap();

    let history = audit::history_for(db.pool(), "object", id).await.unwrap();
    assert_eq!(history.len(), 2);
    assert_eq!(history[0].action, AuditAction::Created);
    assert_eq!(history[0].actor, AuditActor::System);
    assert_eq!(history[1].action, AuditAction::Updated);
    assert_eq!(history[1].actor, AuditActor::User(user));
    assert!(history[0].seq < history[1].seq, "ordered by seq");
    assert_eq!(history[1].changes[0].field, "name");
    assert_eq!(history[1].changes[0].after, Some(json!("Roman Vase")));
}

#[sqlx::test]
async fn history_is_scoped_to_one_entity(pool: PgPool) {
    let db = Db::from_pool(pool);
    let a = Uuid::new_v4();
    let b = Uuid::new_v4();
    audit::record(db.pool(), &created(a, "A")).await.unwrap();
    audit::record(db.pool(), &created(b, "B")).await.unwrap();

    let only_a = audit::history_for(db.pool(), "object", a).await.unwrap();
    assert_eq!(only_a.len(), 1);
    assert_eq!(only_a[0].entity_id, a);
}

#[sqlx::test]
async fn deleted_action_with_empty_changes_round_trips(pool: PgPool) {
    let db = Db::from_pool(pool);
    let id = Uuid::new_v4();

    audit::record(
        db.pool(),
        &NewAuditEvent {
            actor: AuditActor::System,
            action: AuditAction::Deleted,
            entity_type: "object".into(),
            entity_id: id,
            changes: vec![],
        },
    )
    .await
    .unwrap();

    let history = audit::history_for(db.pool(), "object", id).await.unwrap();
    assert_eq!(history.len(), 1);
    assert_eq!(history[0].action, AuditAction::Deleted);
    assert!(history[0].changes.is_empty());
}

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

    let history = audit::history_for(db.pool(), "object", Uuid::new_v4())
        .await
        .unwrap();

    assert!(history.is_empty());
}
