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

fn sample() -> NewAuditEvent {
    NewAuditEvent {
        actor: AuditActor::System,
        action: AuditAction::Created,
        entity_type: "object".into(),
        entity_id: Uuid::new_v4(),
        changes: vec![],
    }
}

async fn count(pool: &PgPool) -> i64 {
    sqlx::query_scalar("SELECT count(*) FROM audit_log")
        .fetch_one(pool)
        .await
        .unwrap()
}

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

    audit::record(db.pool(), &sample()).await.unwrap();

    // Each failing statement poisons its connection (Postgres enters aborted-transaction
    // state). Acquire a fresh connection per statement so later assertions are independent.
    let update_err = sqlx::query("UPDATE audit_log SET action = 'deleted'")
        .execute(db.pool())
        .await
        .unwrap_err()
        .to_string();

    assert!(
        update_err.contains("audit_log is append-only"),
        "UPDATE must be rejected by the trigger, got: {update_err}"
    );

    let delete_err = sqlx::query("DELETE FROM audit_log")
        .execute(db.pool())
        .await
        .unwrap_err()
        .to_string();

    assert!(
        delete_err.contains("audit_log is append-only"),
        "DELETE must be rejected by the trigger, got: {delete_err}"
    );

    let truncate_err = sqlx::query("TRUNCATE audit_log")
        .execute(db.pool())
        .await
        .unwrap_err()
        .to_string();

    assert!(
        truncate_err.contains("audit_log is append-only"),
        "TRUNCATE must be rejected by the trigger, got: {truncate_err}"
    );

    assert_eq!(count(db.pool()).await, 1, "the row is still there");
}

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

    let mut tx = db.pool().begin().await.unwrap();

    audit::record(&mut *tx, &sample()).await.unwrap();

    tx.rollback().await.unwrap();

    assert_eq!(
        count(db.pool()).await,
        0,
        "a rolled-back audit record must not persist"
    );
}

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

    let mut tx = db.pool().begin().await.unwrap();

    audit::record(&mut *tx, &sample()).await.unwrap();

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

    assert_eq!(
        count(db.pool()).await,
        1,
        "a committed audit record persists"
    );
}
