//! Tests for `.default_step_timeout(d)`.
//!
//! Three behaviours, run as sequential phases of ONE test: timeouts are
//! swept per saga name, so independent `#[tokio::test]`s would race each
//! other's `tick_timeouts` calls against instances that must stay Active.
//!
//!   1. Without default + without per-step timeout: step waits forever.
//!   2. With default: steps without an explicit `.timeout()` use the default.
//!   3. Per-step `.timeout(d)` still overrides the default.
//!
//! Gated on `DATABASE_URL`; skips silently when unset.

#![allow(dead_code)]

use std::time::Duration;

use mire_sagas::{PgSagaRunner, Saga, SagaBuilder, SagaInstanceStatus, SagaStepStatus};

mod common;

#[allow(unused)]
#[path = "../examples/travel_booking.rs"]
mod travel_booking;

use travel_booking::{Booking, BookingEvent, BookingId, CustomerId, FlightId, HotelId, Money};

fn make_request(id: &str) -> BookingEvent {
    BookingEvent::Requested {
        booking_id: BookingId::new(id),
        customer_id: CustomerId::new("alice"),
        flight_id: FlightId::new("LH-100"),
        hotel_id: HotelId::new("HILTON-OSL"),
        amount: Money::from_minor(150_000),
    }
}

#[tokio::test]
async fn default_step_timeout_behaviours() {
    let Some(store) = common::store().await else {
        return;
    };

    // ------------------------------------------------------------------
    // Phase 1 — no default timeout, no per-step timeout: the flight
    // step's await never expires when the handler is missing.
    // ------------------------------------------------------------------
    let mut topology = travel_booking::TravelBookingSaga::build(SagaBuilder::<
        travel_booking::TravelBookingSaga,
    >::for_saga());
    topology.remove_request_handler("saga.flight.hold-requested");
    let runner = PgSagaRunner::new(topology, store.clone());

    let id_forever = runner
        .trigger::<Booking>(&make_request(&common::unique("no-timeout")))
        .await
        .unwrap()
        .unwrap();

    // Tick way past any reasonable timeout — flight still Active (its
    // await row has deadline_at = NULL).
    runner
        .tick_timeouts(chrono::Utc::now() + chrono::Duration::hours(24))
        .await
        .unwrap();
    assert_eq!(
        runner.step_status(&id_forever, "flight").await.unwrap(),
        SagaStepStatus::Active,
        "no timeout means no expiry"
    );

    // ------------------------------------------------------------------
    // Phase 2 — with a default timeout, the flight step (no explicit
    // .timeout in its handler) expires after the default.
    // ------------------------------------------------------------------
    let mut topology = travel_booking::TravelBookingSaga::build(SagaBuilder::<
        travel_booking::TravelBookingSaga,
    >::for_saga())
    .default_step_timeout(Duration::from_secs(60));
    topology.remove_request_handler("saga.flight.hold-requested");
    let runner = PgSagaRunner::new(topology, store.clone());

    let id_default = runner
        .trigger::<Booking>(&make_request(&common::unique("default-timeout")))
        .await
        .unwrap()
        .unwrap();

    // Just under the default — still Active.
    runner
        .tick_timeouts(chrono::Utc::now() + chrono::Duration::seconds(50))
        .await
        .unwrap();
    assert_eq!(
        runner.step_status(&id_default, "flight").await.unwrap(),
        SagaStepStatus::Active
    );

    // Past it — expires; flight is the first failing step so the saga
    // transitions to FailedTerminal with no compensable upstreams.
    runner
        .tick_timeouts(chrono::Utc::now() + chrono::Duration::seconds(70))
        .await
        .unwrap();
    assert_eq!(
        runner.status(&id_default).await.unwrap(),
        Some(SagaInstanceStatus::FailedTerminal)
    );
    assert_eq!(
        runner.step_status(&id_default, "flight").await.unwrap(),
        SagaStepStatus::Failed
    );

    // ------------------------------------------------------------------
    // Phase 3 — the payment step declares its own 10-minute
    // .timeout(deadline.0); the saga default is much shorter. The
    // per-step timeout wins.
    // ------------------------------------------------------------------
    let mut topology = travel_booking::TravelBookingSaga::build(SagaBuilder::<
        travel_booking::TravelBookingSaga,
    >::for_saga())
    .default_step_timeout(Duration::from_secs(1));
    topology.remove_request_handler("saga.payment.capture-requested");
    let runner = PgSagaRunner::new(topology, store.clone());

    let id_per_step = runner
        .trigger::<Booking>(&make_request(&common::unique("per-step")))
        .await
        .unwrap()
        .unwrap();

    // Flight + hotel handlers ARE registered, so they completed
    // synchronously. Payment is awaiting with its EXPLICIT 10-minute
    // deadline, not the 1s default.
    assert_eq!(
        runner.step_status(&id_per_step, "payment").await.unwrap(),
        SagaStepStatus::Active
    );

    // Seconds past the saga default — still Active because the per-step
    // .timeout(10min) wins.
    runner
        .tick_timeouts(chrono::Utc::now() + chrono::Duration::seconds(5))
        .await
        .unwrap();
    assert_eq!(
        runner.step_status(&id_per_step, "payment").await.unwrap(),
        SagaStepStatus::Active
    );

    // Past 10 minutes — now it expires.
    runner
        .tick_timeouts(chrono::Utc::now() + chrono::Duration::seconds(10 * 60 + 5))
        .await
        .unwrap();
    assert_eq!(
        runner.status(&id_per_step).await.unwrap(),
        Some(SagaInstanceStatus::FailedTerminal)
    );
}
