//! Tests for `#[derive(IntoResponse)]` on enums of events.
//!
//! The derive handles the "all variants delegate" case — every variant
//! is a tuple wrapping an event type, and the generated impl just calls
//! `inner.into_response()`. For variants needing custom is_error or
//! per-variant error_reason, users write the impl by hand (see
//! `multi_outcome.rs`).

use mire::{EventData, EventTypeStatic, IntoResponse};
use serde::{Deserialize, Serialize};

mod common;

#[derive(Debug, Clone, Serialize, Deserialize, EventData)]
#[mire(event_type = "test.flight-held")]
struct FlightHeld {
    hold_id: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, EventData)]
#[mire(event_type = "test.flight-backordered")]
struct FlightBackordered {
    queue_position: u32,
}

#[derive(Debug, IntoResponse)]
enum FlightOutcome {
    Held(FlightHeld),
    Backordered(FlightBackordered),
}

#[test]
fn derive_routes_to_held_variant() {
    let outcome = FlightOutcome::Held(FlightHeld {
        hold_id: "fh-1".into(),
    });
    let r = outcome.into_response();
    assert_eq!(r.event_type, <FlightHeld as EventTypeStatic>::EVENT_TYPE);
    assert!(!r.is_error);
    assert_eq!(r.error_reason, None);
}

#[test]
fn derive_routes_to_backordered_variant() {
    let outcome = FlightOutcome::Backordered(FlightBackordered { queue_position: 5 });
    let r = outcome.into_response();
    assert_eq!(
        r.event_type,
        <FlightBackordered as EventTypeStatic>::EVENT_TYPE
    );
    assert!(!r.is_error);
}

#[test]
fn derive_preserves_payload() {
    let outcome = FlightOutcome::Held(FlightHeld {
        hold_id: "fh-42".into(),
    });
    let r = outcome.into_response();
    let decoded: FlightHeld = serde_json::from_value(r.payload).unwrap();
    assert_eq!(decoded.hold_id, "fh-42");
}

// =====================================================================
// End-to-end: derive used as a real handler return type in a running
// saga. Saga awaits `FlightHeld`; the handler returns
// `FlightOutcome::Held(FlightHeld { ... })`; the runner correctly
// identifies the inner event_type from the derive's delegation and
// wakes the awaiting step.
// =====================================================================

mod e2e {
    use super::*;
    use mire::Aggregate;
    use mire_sagas::{
        Emit, PgSagaRunner, Req, Saga, SagaBuilder, SagaInstanceStatus, SagaStepStatus, Start,
        State, task,
    };

    use crate::common;

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

    #[derive(Serialize, Deserialize, Clone, EventData)]
    #[mire(event_type = "booking.req")]
    struct BookingReq {
        id: String,
    }

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

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

    #[derive(Debug, Clone, Serialize, Deserialize, EventData)]
    #[mire(event_type = "test.flight-request")]
    struct RequestFlight {
        id: String,
    }

    #[derive(Debug, Clone, Serialize, Deserialize)]
    struct FlightReserved {
        hold_id: String,
    }

    async fn reserve_flight(State(s): State<St>) -> Emit {
        Emit::event(RequestFlight { id: s.id.clone() })
    }

    // The handler returns the user-defined ENUM via the derive — the
    // runner correctly extracts the inner FlightHeld event_type.
    async fn handle_flight(Req(r): Req<RequestFlight>) -> FlightOutcome {
        FlightOutcome::Held(FlightHeld {
            hold_id: format!("fh-{}", r.id),
        })
    }

    struct DeriveSaga;
    impl Saga for DeriveSaga {
        type State = St;
        fn name() -> &'static str {
            "derive-saga"
        }
        fn build(b: SagaBuilder<Self>) -> SagaBuilder<Self> {
            b.start_on::<Booking, _>(|e: &BookingReq| {
                Some(Start {
                    instance_id: e.id.clone(),
                    state: St { id: e.id.clone() },
                })
            })
            .step(
                "flight",
                task(reserve_flight)
                    .terminal_success()
                    .on_response(|h: FlightHeld| FlightReserved { hold_id: h.hold_id }),
            )
            .handle(handle_flight)
        }
    }

    #[tokio::test]
    async fn derived_into_response_drives_saga_to_terminal() {
        let Some(store) = common::store().await else {
            return;
        };
        let topology = DeriveSaga::build(SagaBuilder::<DeriveSaga>::for_saga());
        let runner = PgSagaRunner::new(topology, store);
        let instance = common::unique("b-derive");
        let id = runner
            .trigger::<Booking>(&BookingReq {
                id: instance.clone(),
            })
            .await
            .unwrap()
            .unwrap();

        let env = runner.load(&id).await.unwrap().unwrap();
        assert_eq!(env.status, SagaInstanceStatus::SucceededTerminal);
        assert_eq!(
            runner.step_status(&id, "flight").await.unwrap(),
            SagaStepStatus::Completed
        );

        // The step's typed output reflects the hold_id minted by the
        // handler — confirming the derive routed correctly all the way
        // through the runner's await + map.
        let output = env.outputs.get("flight").unwrap().clone();
        let reserved: FlightReserved = serde_json::from_value(output).unwrap();
        assert_eq!(reserved.hold_id, format!("fh-{instance}"));
    }
}

mod fallible_serialisation {
    use std::collections::HashMap;

    use mire::{EventData, IntoResponse};
    use serde::{Deserialize, Serialize};

    /// serde_json refuses maps with non-string keys at serialise time —
    /// the canonical way to make a perfectly innocent-looking response
    /// type fail. Pre-fix the derive `.expect("response serialises")`-ed,
    /// crashing the host process from inside a request handler; now the
    /// failure becomes an error *response* and routes through the saga's
    /// step-failure/compensation path.
    #[derive(Debug, Clone, Serialize, Deserialize, EventData)]
    #[mire(event_type = "derive.unserialisable")]
    struct Unserialisable {
        bad: HashMap<(u8, u8), String>,
    }

    #[test]
    fn serialise_failure_becomes_error_response_not_panic() {
        let mut bad = HashMap::new();
        bad.insert((1, 2), "boom".to_string());
        let response = Unserialisable { bad }.into_response();
        assert!(response.is_error, "must surface as an error response");
        assert!(
            response
                .error_reason
                .as_deref()
                .unwrap_or_default()
                .contains("failed to serialise"),
            "reason should explain the serialisation failure: {:?}",
            response.error_reason
        );
        assert_eq!(response.event_type, "derive.unserialisable");
    }

    #[test]
    fn normal_responses_are_unaffected() {
        #[derive(Debug, Clone, Serialize, Deserialize, EventData)]
        #[mire(event_type = "derive.fine")]
        struct Fine {
            ok: bool,
        }
        let response = Fine { ok: true }.into_response();
        assert!(!response.is_error);
        assert_eq!(response.event_type, "derive.fine");
        assert_eq!(response.payload["ok"], serde_json::json!(true));
    }
}
