//! E2E tests for branching primitives: `.gated_by` and `.after_any_of`.
//!
//! Scenario: a small approval saga. After an `amount_check` step decides
//! whether the booking is auto-approvable or needs manual approval, two
//! mutually-exclusive branches lead to a single `finalise` step.
//!
//! - Auto branch: `auto_path` activates when `amount_check` says AutoOk;
//!   `manual_path` is skipped.
//! - Manual branch: `manual_path` activates when amount_check says Manual;
//!   `auto_path` is skipped.
//! - The `finalise` step uses `.after_any_of` so whichever branch ran
//!   triggers it.
//!
//! Both branches must reach `Confirmed`.
//!
//! 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,
    Trigger, task,
};
use serde::{Deserialize, Serialize};

mod common;

// =====================================================================
// Trigger aggregate + event
// =====================================================================

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

#[derive(Serialize, Deserialize, Clone, EventData)]
#[mire(event_type = "account.review-requested")]
struct ReviewRequested {
    account_id: String,
    amount: i64,
}

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

// =====================================================================
// Saga state + outputs
// =====================================================================

#[derive(Debug, Default, Clone, Serialize, Deserialize)]
struct ReviewState {
    account_id: String,
    amount: i64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
enum AmountDecision {
    AutoOk,
    NeedsManual,
}

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

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

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

// =====================================================================
// Request / response events
// =====================================================================

#[derive(Debug, Clone, Serialize, Deserialize, EventData)]
#[mire(event_type = "review.auto-approve")]
struct DoAutoApprove {
    account_id: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, EventData)]
#[mire(event_type = "review.auto-approved")]
struct AutoApprovedEvent {
    account_id: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, EventData)]
#[mire(event_type = "review.manual-approve")]
struct DoManualApprove {
    account_id: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, EventData)]
#[mire(event_type = "review.manually-approved")]
struct ManuallyApprovedEvent {
    account_id: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, EventData)]
#[mire(event_type = "review.finalise")]
struct DoFinalise {
    account_id: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, EventData)]
#[mire(event_type = "review.finalised")]
struct FinalisedEvent {
    account_id: String,
}

// =====================================================================
// Step handlers
// =====================================================================

async fn check_amount(
    Trigger(_req): Trigger<ReviewRequested>,
    State(s): State<ReviewState>,
) -> AmountDecision {
    if s.amount > 10_000 {
        AmountDecision::NeedsManual
    } else {
        AmountDecision::AutoOk
    }
}

async fn auto_approve(State(s): State<ReviewState>) -> Emit {
    Emit::event(DoAutoApprove {
        account_id: s.account_id.clone(),
    })
}

async fn manual_approve(State(s): State<ReviewState>) -> Emit {
    Emit::event(DoManualApprove {
        account_id: s.account_id.clone(),
    })
}

async fn finalise(State(s): State<ReviewState>) -> Emit {
    Emit::event(DoFinalise {
        account_id: s.account_id.clone(),
    })
}

// =====================================================================
// Request handlers (in-process workers).
// =====================================================================

async fn handle_auto_approve(Req(r): Req<DoAutoApprove>) -> AutoApprovedEvent {
    AutoApprovedEvent {
        account_id: r.account_id,
    }
}

async fn handle_manual_approve(Req(r): Req<DoManualApprove>) -> ManuallyApprovedEvent {
    ManuallyApprovedEvent {
        account_id: r.account_id,
    }
}

async fn handle_finalise(Req(r): Req<DoFinalise>) -> FinalisedEvent {
    FinalisedEvent {
        account_id: r.account_id,
    }
}

// =====================================================================
// Saga definition
// =====================================================================

struct ReviewSaga;

impl Saga for ReviewSaga {
    type State = ReviewState;
    fn name() -> &'static str {
        "review-saga"
    }

    fn build(b: SagaBuilder<Self>) -> SagaBuilder<Self> {
        b.start_on::<Account, _>(|e: &ReviewRequested| {
            Some(Start {
                instance_id: e.account_id.clone(),
                state: ReviewState {
                    account_id: e.account_id.clone(),
                    amount: e.amount,
                },
            })
        })
        .step("amount_check", task(check_amount))
        // Two sibling branches gated by the same upstream's decision.
        .step(
            "auto_path",
            task(auto_approve)
                .after("amount_check")
                .gated_by("amount_check", |d: &AmountDecision| {
                    matches!(d, AmountDecision::AutoOk)
                })
                .on_response(|_: AutoApprovedEvent| AutoApproved),
        )
        .step(
            "manual_path",
            task(manual_approve)
                .after("amount_check")
                .gated_by("amount_check", |d: &AmountDecision| {
                    matches!(d, AmountDecision::NeedsManual)
                })
                .on_response(|_: ManuallyApprovedEvent| ManuallyApproved),
        )
        // Merge: whichever branch ran kicks off finalise.
        .step(
            "finalise",
            task(finalise)
                .after_any_of(["auto_path", "manual_path"])
                .terminal_success()
                .on_response(|_: FinalisedEvent| Finalised),
        )
        .handle(handle_auto_approve)
        .handle(handle_manual_approve)
        .handle(handle_finalise)
    }
}

// =====================================================================
// Tests
// =====================================================================

fn build_runner(store: &mire::EventStore) -> PgSagaRunner<ReviewSaga> {
    let topology = ReviewSaga::build(SagaBuilder::<ReviewSaga>::for_saga());
    PgSagaRunner::new(topology, store.clone())
}

#[tokio::test]
async fn auto_branch_skips_manual_path() {
    let Some(store) = common::store().await else {
        return;
    };
    let runner = build_runner(&store);
    let trigger = ReviewRequested {
        account_id: common::unique("a-1"),
        amount: 5_000, // under threshold → AutoOk
    };
    let instance_id = runner.trigger::<Account>(&trigger).await.unwrap().unwrap();

    assert_eq!(
        runner.status(&instance_id).await.unwrap(),
        Some(SagaInstanceStatus::SucceededTerminal)
    );
    assert_eq!(
        runner.step_status(&instance_id, "auto_path").await.unwrap(),
        SagaStepStatus::Completed
    );
    assert_eq!(
        runner
            .step_status(&instance_id, "manual_path")
            .await
            .unwrap(),
        SagaStepStatus::Skipped
    );
    assert_eq!(
        runner.step_status(&instance_id, "finalise").await.unwrap(),
        SagaStepStatus::Completed
    );
}

#[tokio::test]
async fn manual_branch_skips_auto_path() {
    let Some(store) = common::store().await else {
        return;
    };
    let runner = build_runner(&store);
    let trigger = ReviewRequested {
        account_id: common::unique("a-2"),
        amount: 50_000, // above threshold → NeedsManual
    };
    let instance_id = runner.trigger::<Account>(&trigger).await.unwrap().unwrap();

    assert_eq!(
        runner.status(&instance_id).await.unwrap(),
        Some(SagaInstanceStatus::SucceededTerminal)
    );
    assert_eq!(
        runner.step_status(&instance_id, "auto_path").await.unwrap(),
        SagaStepStatus::Skipped
    );
    assert_eq!(
        runner
            .step_status(&instance_id, "manual_path")
            .await
            .unwrap(),
        SagaStepStatus::Completed
    );
    assert_eq!(
        runner.step_status(&instance_id, "finalise").await.unwrap(),
        SagaStepStatus::Completed
    );
}

#[tokio::test]
async fn finalise_picks_whichever_branch_completed() {
    // Same as auto_branch but checks finalise's emit lists only ONE
    // typed request — finalise consumed any-of the predecessors.
    let Some(store) = common::store().await else {
        return;
    };
    let runner = build_runner(&store);
    let instance_id = runner
        .trigger::<Account>(&ReviewRequested {
            account_id: common::unique("a-3"),
            amount: 100,
        })
        .await
        .unwrap()
        .unwrap();

    let emitted = runner.emitted(&instance_id).await.unwrap();
    let finalise_emits: Vec<_> = emitted
        .iter()
        .filter(|e| e.event_type == "review.finalise")
        .collect();
    assert_eq!(finalise_emits.len(), 1, "finalise should run exactly once");
}
