use db::{Db, audit, users};
use domain::{AuditAction, AuditActor, Email, NewUser, Role};
use sqlx::PgPool;

fn new_user(email: &str, role: Role) -> NewUser {
    NewUser {
        email: Email::parse(email).unwrap(),
        password_hash: "$argon2id$dummy".to_owned(),
        role,
    }
}

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

    let mut tx = db.pool().begin().await.unwrap();
    let id = users::create_user(
        &mut tx,
        AuditActor::System,
        &new_user("anna@example.com", Role::Admin),
    )
    .await
    .unwrap();
    tx.commit().await.unwrap();

    let user = users::user_by_id(db.pool(), id).await.unwrap().unwrap();
    assert_eq!(user.email.as_str(), "anna@example.com");
    assert_eq!(user.role, Role::Admin);

    let (by_email, hash) = users::credentials_by_email(db.pool(), "anna@example.com")
        .await
        .unwrap()
        .unwrap();
    assert_eq!(by_email.id, id);
    assert_eq!(hash, "$argon2id$dummy");
}

#[sqlx::test]
async fn create_user_audits_email_and_role_but_never_the_hash(pool: PgPool) {
    let db = Db::from_pool(pool);
    let mut tx = db.pool().begin().await.unwrap();
    let id = users::create_user(
        &mut tx,
        AuditActor::System,
        &new_user("anna@example.com", Role::Editor),
    )
    .await
    .unwrap();
    tx.commit().await.unwrap();

    let history = audit::history_for(db.pool(), "user", id.to_uuid())
        .await
        .unwrap();
    assert_eq!(history.len(), 1);
    assert_eq!(history[0].action, AuditAction::Created);
    let mut fields: Vec<&str> = history[0]
        .changes
        .iter()
        .map(|c| c.field.as_str())
        .collect();
    fields.sort_unstable();
    assert_eq!(fields, vec!["email", "role"]);
}

#[sqlx::test]
async fn missing_email_returns_none(pool: PgPool) {
    let db = Db::from_pool(pool);
    assert!(
        users::credentials_by_email(db.pool(), "nobody@example.com")
            .await
            .unwrap()
            .is_none()
    );
}

#[sqlx::test]
async fn list_users_is_ordered_by_email(pool: PgPool) {
    let db = Db::from_pool(pool);
    let mut tx = db.pool().begin().await.unwrap();
    users::create_user(
        &mut tx,
        AuditActor::System,
        &new_user("zoe@example.com", Role::Editor),
    )
    .await
    .unwrap();
    users::create_user(
        &mut tx,
        AuditActor::System,
        &new_user("amy@example.com", Role::Admin),
    )
    .await
    .unwrap();
    tx.commit().await.unwrap();

    let users = users::list_users(db.pool()).await.unwrap();
    let emails: Vec<&str> = users.iter().map(|u| u.email.as_str()).collect();
    assert_eq!(emails, vec!["amy@example.com", "zoe@example.com"]);
}

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

    let mut tx = db.pool().begin().await.unwrap();
    users::create_user(
        &mut tx,
        AuditActor::System,
        &new_user("anna@example.com", Role::Admin),
    )
    .await
    .unwrap();
    // Same normalized email again — the lower(email) unique index must reject it.
    let err = users::create_user(
        &mut tx,
        AuditActor::System,
        &new_user("anna@example.com", Role::Editor),
    )
    .await
    .unwrap_err();
    assert!(
        matches!(err, sqlx::Error::Database(_)),
        "expected a unique-violation database error, got {err:?}"
    );
}
