use betex::{
    book::BookEvent,
    book::protocol::command::{Command, CommandKind, MarketState, Persistence, Side, TimeInForce},
    types::{
        AccountId, CorrelationId, MarketId, MarketKind, MarketModel, MarketPhase, Money,
        OddsX10000, Quantity, RunnerId,
    },
};

#[test]
fn command_json_roundtrip_preserves_correlation_and_payload() {
    let cmd = Command {
        correlation_id: Some(CorrelationId(1.to_string())),
        metadata: None,
        market_id: MarketId(7),
        kind: CommandKind::SetMarketState {
            state: MarketState::Open,
        },
    };

    let value = serde_json::to_value(&cmd).expect("serialize command");
    let decoded = serde_json::from_value::<Command>(value).expect("decode command");

    assert_eq!(decoded.correlation_id, Some(CorrelationId(1.to_string())));
    assert_eq!(decoded.market_id, MarketId(7));
    assert_eq!(
        decoded.kind,
        CommandKind::SetMarketState {
            state: MarketState::Open
        }
    );
}

#[test]
fn command_kind_json_roundtrip_smoke() {
    let kinds = vec![
        CommandKind::CreateMarket {
            name: "x".to_string(),
            market_model: MarketModel::ExchangeOdds,
            market_kind: MarketKind::InPlayCapable,
            market_phase: MarketPhase::Pre,
            runner_ids: vec![RunnerId(1), RunnerId(2)],
            runner_labels: vec!["A".to_string(), "B".to_string()],
        },
        CommandKind::PlaceOrder {
            runner_id: RunnerId(1),
            account_id: AccountId::from(1),
            client_order_id: None,
            side: Side::Yes,
            odds: OddsX10000(20_000),
            stake: Money(100),
            persistence: Persistence::Persist,
            time_in_force: TimeInForce::Gtc,
        },
        CommandKind::PlaceBinaryOrder {
            account_id: AccountId::from(1),
            client_order_id: None,
            side: Side::No,
            price_ticks: 5_000,
            qty_shares: 10,
            time_in_force: TimeInForce::FillOrKill {
                min_fill: Some(Quantity(3)),
            },
        },
        CommandKind::ContinueCloseMarket,
    ];

    for kind in kinds {
        let value = serde_json::to_value(&kind).expect("serialize kind");
        let decoded = serde_json::from_value::<CommandKind>(value).expect("decode kind");
        assert_eq!(decoded, kind);
    }
}

#[test]
fn order_accepted_carries_correlation_id() {
    let event = BookEvent::OrderAccepted {
        correlation_id: Some(CorrelationId(1.to_string())),
        order_id: betex::types::OrderId(1),
        account_id: AccountId::from(1),
        runner_id: RunnerId(1),
        runner_label: "runner-1".to_string(),
        side: Side::Yes,
        price: OddsX10000(20_000),
        stake: Money(100),
        persistence: Persistence::Persist,
        time_in_force: TimeInForce::Gtc,
    };

    let value = serde_json::to_value(event).expect("serialize event");
    let decoded = serde_json::from_value::<BookEvent>(value).expect("event should decode");
    let BookEvent::OrderAccepted { correlation_id, .. } = decoded else {
        panic!("expected OrderAccepted")
    };
    assert_eq!(correlation_id, Some(CorrelationId(1.to_string())));
}

#[test]
fn command_kind_json_roundtrip_preserves_string_account_id() {
    let kind = CommandKind::PlaceOrder {
        runner_id: RunnerId(1),
        account_id: AccountId::from("acct-alpha"),
        client_order_id: None,
        side: Side::Yes,
        odds: OddsX10000(20_000),
        stake: Money(100),
        persistence: Persistence::Persist,
        time_in_force: TimeInForce::Gtc,
    };

    let value = serde_json::to_value(&kind).expect("serialize kind");
    assert_eq!(value["PLACE_ORDER"]["account_id"], "acct-alpha");

    let decoded = serde_json::from_value::<CommandKind>(value).expect("decode kind");
    assert_eq!(decoded, kind);
}
