//! Verifies the cron reaper's core query/release logic by reproducing it
//! directly against the wallet primitives (the reaper itself lives in
//! apps/cron, which can't easily be imported as a lib from here -- this
//! test exercises the same "find expired holds, release them" contract
//! the cron task relies on).

use db::models::ai_credits::AiCreditsRepository;
use sqlx::PgPool;
use uuid::Uuid;

async fn pool() -> PgPool {
    let url = std::env::var("TEST_DATABASE_URL")
        .expect("set TEST_DATABASE_URL to run this test");
    sqlx::postgres::PgPoolOptions::new()
        .max_connections(5)
        .connect(&url)
        .await
        .expect("connect to test db")
}

#[tokio::test]
async fn expired_hold_is_swept_and_credits_return_to_available() {
    let pool = pool().await;
    let user_id = Uuid::parse_str("33333333-3333-3333-3333-333333333333").unwrap();

    // user_ai_subscriptions.user_id has a FK to users(id) - this fixed UUID
    // isn't a real seeded user in every environment this test might run
    // against (e.g. a fresh CI database), so provision it here rather than
    // assuming it pre-exists. Matches ai_credits.rs's make_user() pattern.
    sqlx::query("INSERT INTO users (id, email, password_hash) VALUES ($1, $2, 'x') ON CONFLICT (id) DO NOTHING")
        .bind(user_id)
        .bind(format!("{user_id}@test.local"))
        .execute(&pool)
        .await
        .unwrap();

    AiCreditsRepository::ensure_wallet(&pool, user_id).await.unwrap();

    let hold = AiCreditsRepository::try_reserve_credits(&pool, user_id, "help_answer", 4, None, None)
        .await
        .expect("reserve");

    let wallet = AiCreditsRepository::get_wallet(&pool, user_id).await.unwrap().unwrap();
    assert_eq!(wallet.reserved_credits, 4, "credits should be reserved before the sweep");

    // Force the hold into the past, exactly as an abandoned request would
    // eventually end up (the migration default is NOW() + 5 minutes).
    sqlx::query("UPDATE ai_reservation_holds SET expires_at = NOW() - INTERVAL '1 minute' WHERE id = $1")
        .bind(hold.id)
        .execute(&pool)
        .await
        .unwrap();

    // Reproduce the cron task's sweep query + release call.
    let expired: Vec<Uuid> = sqlx::query_scalar(
        "SELECT id FROM ai_reservation_holds WHERE status = 'held' AND expires_at < NOW()",
    )
    .fetch_all(&pool)
    .await
    .unwrap();
    assert_eq!(expired, vec![hold.id]);

    for id in expired {
        let released = AiCreditsRepository::try_release_reservation(&pool, id).await.unwrap();
        assert!(released);
    }

    let wallet = AiCreditsRepository::get_wallet(&pool, user_id).await.unwrap().unwrap();
    assert_eq!(wallet.reserved_credits, 0, "reaper must release the reservation back to available");
    assert_eq!(wallet.available_credits(), 10, "released credits must be fully spendable again");

    let hold_row: (String,) = sqlx::query_as("SELECT status FROM ai_reservation_holds WHERE id = $1")
        .bind(hold.id)
        .fetch_one(&pool)
        .await
        .unwrap();
    assert_eq!(hold_row.0, "released");
}
