//! The pg runner hands its in-process handlers a populated `IdempotencyKey`
//! (D1) — `(instance_id, step, attempt_seq)` — so handlers can dedupe under
//! at-least-once retries. Gated on `DATABASE_URL`.

#![allow(dead_code)]

use std::sync::Mutex;

use mire::{Aggregate, EventStore};
use mire_sagas::{Emit, IdempotencyKey, PgSagaRunner, Req, Saga, SagaBuilder, Start, State, task};
use serde::{Deserialize, Serialize};
use sqlx::PgPool;

static CAPTURED: Mutex<Option<IdempotencyKey>> = Mutex::new(None);

#[derive(Default, Clone)]
struct Booking;

#[derive(Debug, Clone, Serialize, Deserialize, mire::EventData)]
#[mire(event_type = "idem-pg.booking-requested")]
struct BookingRequested {
    id: String,
}

impl Aggregate for Booking {
    type Event = BookingRequested;
    fn stream_category() -> &'static str {
        "idem-pg-booking"
    }
    fn apply(&mut self, _: &Self::Event) {}
}

#[derive(Default, Clone, Serialize, Deserialize)]
struct St {
    id: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, mire::EventData)]
#[mire(event_type = "idem-pg.charge-req")]
struct ChargeReq {
    id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, mire::EventData)]
#[mire(event_type = "idem-pg.charge-ok")]
struct ChargeOk;
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Charged;

fn charge_step(State(s): State<St>) -> Emit {
    Emit::event(ChargeReq { id: s.id.clone() })
}

async fn handle_charge(Req(_): Req<ChargeReq>, key: IdempotencyKey) -> ChargeOk {
    *CAPTURED.lock().unwrap() = Some(key);
    ChargeOk
}

struct IdemPgSaga;
impl Saga for IdemPgSaga {
    type State = St;
    fn name() -> &'static str {
        "idem-pg-saga"
    }
    fn build(b: SagaBuilder<Self>) -> SagaBuilder<Self> {
        b.start_on::<Booking, _>(|e: &BookingRequested| {
            Some(Start {
                instance_id: e.id.clone(),
                state: St { id: e.id.clone() },
            })
        })
        .step(
            "charge",
            task(charge_step)
                .terminal_success()
                .on_response(|_: ChargeOk| Charged),
        )
        .effect(handle_charge)
    }
}

async fn maybe_store() -> Option<EventStore> {
    let url = std::env::var("DATABASE_URL")
        .ok()
        .filter(|s| !s.is_empty())?;
    let pool = PgPool::connect(&url).await.expect("connect");
    let store = EventStore::new(pool);
    store.migrate().await.expect("mire migrate");
    mire_sagas::migrate(&store).await.expect("saga migrate");
    Some(store)
}

#[tokio::test]
async fn pg_request_handler_receives_a_populated_idempotency_key() {
    let Some(store) = maybe_store().await else {
        eprintln!("skipping: DATABASE_URL not set");
        return;
    };
    *CAPTURED.lock().unwrap() = None;
    let runner = PgSagaRunner::new(
        IdemPgSaga::build(SagaBuilder::<IdemPgSaga>::for_saga()),
        store,
    );

    let id = format!("idem-{}", uuid::Uuid::new_v4());
    runner
        .trigger::<Booking>(&BookingRequested { id: id.clone() })
        .await
        .expect("trigger");

    let key = CAPTURED
        .lock()
        .unwrap()
        .clone()
        .expect("handler ran and captured the key");
    assert_eq!(key.instance_id, id, "key carries the bare instance id");
    assert_eq!(key.step, "charge", "key carries the source step");
    assert_eq!(key.attempt_seq, 0);
}
