//! Tests for `.gated_by_state(|s| ...)` — state-based step gating.
//!
//! Spec §D3: branching expression should support both upstream-output
//! gating (`.gated_by`) and state-based gating (`.gated_by_state`).
//!
//! Gated on `DATABASE_URL`; skips silently when unset.

#![allow(dead_code)]

use mire::{Aggregate, EventData};
use mire_sagas::{
    Emit, PgSagaRunner, Req, Saga, SagaBuilder, SagaInstanceStatus, SagaStepStatus, Start, State,
    task,
};
use serde::{Deserialize, Serialize};

mod common;

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

#[derive(Serialize, Deserialize, Clone, EventData)]
#[mire(event_type = "src.requested")]
struct SourceRequested {
    id: String,
    amount: i64,
}

impl Aggregate for Source {
    type Event = SourceRequested;
    fn stream_category() -> &'static str {
        "source"
    }
    fn apply(&mut self, _: &Self::Event) {}
}

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

// --- step: auto-approve ---

#[derive(Debug, Clone, Serialize, Deserialize, EventData)]
#[mire(event_type = "auto.req")]
struct AutoApproveReq;

#[derive(Debug, Clone, Serialize, Deserialize, EventData)]
#[mire(event_type = "auto.ok")]
struct AutoOk;

#[derive(Debug, Clone, Serialize, Deserialize)]
struct AutoDone;

fn auto_step(State(_): State<St>) -> Emit {
    Emit::event(AutoApproveReq)
}

async fn handle_auto(Req(_): Req<AutoApproveReq>) -> AutoOk {
    AutoOk
}

// --- step: manual review (only when amount > 10_000) ---

#[derive(Debug, Clone, Serialize, Deserialize, EventData)]
#[mire(event_type = "review.req")]
struct ReviewReq;

#[derive(Debug, Clone, Serialize, Deserialize, EventData)]
#[mire(event_type = "review.ok")]
struct ReviewOk;

#[derive(Debug, Clone, Serialize, Deserialize)]
struct Reviewed;

fn review_step(State(_): State<St>) -> Emit {
    Emit::event(ReviewReq)
}

async fn handle_review(Req(_): Req<ReviewReq>) -> ReviewOk {
    ReviewOk
}

// --- terminal step ---

#[derive(Debug, Clone, Serialize, Deserialize)]
struct Done;

fn finalise(State(_): State<St>) -> Done {
    Done
}

// =====================================================================
// Saga: review step only runs for amount > 10_000.
// =====================================================================

struct BranchSaga;
impl Saga for BranchSaga {
    type State = St;
    fn name() -> &'static str {
        "branch-saga"
    }
    fn build(b: SagaBuilder<Self>) -> SagaBuilder<Self> {
        b.start_on::<Source, _>(|e: &SourceRequested| {
            Some(Start {
                instance_id: e.id.clone(),
                state: St {
                    id: e.id.clone(),
                    amount: e.amount,
                },
            })
        })
        .step("auto", task(auto_step).on_response(|_: AutoOk| AutoDone))
        .step(
            "review",
            task(review_step)
                .after("auto")
                .gated_by_state(|s: &St| s.amount > 10_000),
        )
        .step(
            "finalise",
            task(finalise)
                .after_any_of(["review", "auto"])
                .terminal_success(),
        )
        .effect(handle_auto)
        .effect(handle_review)
    }
}

#[tokio::test]
async fn state_gate_true_runs_step() {
    let Some(store) = common::store().await else {
        return;
    };
    let topology = BranchSaga::build(SagaBuilder::<BranchSaga>::for_saga());
    let runner = PgSagaRunner::new(topology, store);
    let id = runner
        .trigger::<Source>(&SourceRequested {
            id: common::unique("big"),
            amount: 50_000,
        })
        .await
        .unwrap()
        .unwrap();

    assert_eq!(
        runner.step_status(&id, "review").await.unwrap(),
        SagaStepStatus::Completed,
        "amount=50k → review should run"
    );
    assert_eq!(
        runner.status(&id).await.unwrap(),
        Some(SagaInstanceStatus::SucceededTerminal)
    );
}

#[tokio::test]
async fn state_gate_false_skips_step() {
    let Some(store) = common::store().await else {
        return;
    };
    let topology = BranchSaga::build(SagaBuilder::<BranchSaga>::for_saga());
    let runner = PgSagaRunner::new(topology, store);
    let id = runner
        .trigger::<Source>(&SourceRequested {
            id: common::unique("small"),
            amount: 100,
        })
        .await
        .unwrap()
        .unwrap();

    assert_eq!(
        runner.step_status(&id, "review").await.unwrap(),
        SagaStepStatus::Skipped,
        "amount=100 → review should skip"
    );
    // The terminal still runs because finalise depends on any_of([review, auto]).
    assert_eq!(
        runner.status(&id).await.unwrap(),
        Some(SagaInstanceStatus::SucceededTerminal)
    );
}

#[tokio::test]
async fn state_gate_does_not_trigger_compensation_on_skip() {
    // Skipped steps don't get compensated — proved by no events
    // referencing review.
    let Some(store) = common::store().await else {
        return;
    };
    let topology = BranchSaga::build(SagaBuilder::<BranchSaga>::for_saga());
    let runner = PgSagaRunner::new(topology, store);
    let id = runner
        .trigger::<Source>(&SourceRequested {
            id: common::unique("skip-comp"),
            amount: 1,
        })
        .await
        .unwrap()
        .unwrap();

    // No review-related event_type appears in the log.
    let saw_review_emit = runner
        .emitted(&id)
        .await
        .unwrap()
        .iter()
        .any(|e| e.event_type == "review.req");
    assert!(!saw_review_emit, "skipped step must not emit");
}
