use betex::{
    book::protocol::command::{Command, CommandKind, MarketState, Persistence, Side, TimeInForce},
    book::{BookEvent, BookMarketState},
    config::Config,
    engine::{
        root::EngineRoot,
        state::{EngineState, Market},
    },
    types::*,
};
mod common;
use common::{apply_outcome, two_runner_market_config};

#[test]
fn settle_market_emits_market_settled_and_sets_terminal_state() {
    let cfg = Config::default();
    let mut state = EngineState::new(cfg);
    let _ts = unix_epoch();

    let market_id = MarketId(1);
    state.markets.insert(
        market_id,
        Market::from_config_with_cfg(&state.cfg, two_runner_market_config(market_id)),
    );
    let mut root = EngineRoot::new_with(state, 0);

    // Create one matched trade.
    for (correlation_id, side, account_id) in [
        (1u64, Side::No, AccountId::from(10)),
        (2u64, Side::Yes, AccountId::from(20)),
    ] {
        let cmd = Command {
            correlation_id: Some(CorrelationId(correlation_id.to_string())),
            metadata: None,
            market_id,
            kind: CommandKind::PlaceOrder {
                runner_id: RunnerId(1),
                account_id,
                client_order_id: None,
                side,
                odds: OddsX10000(20000),
                stake: Money(100),
                persistence: Persistence::Persist,
                time_in_force: TimeInForce::Gtc,
            },
        };
        root.handle(cmd).expect("command handler succeeds");
        let events = root.take_changes();
        apply_outcome(&mut root, events);
    }

    // Close market before settlement.
    let close = Command {
        correlation_id: Some(CorrelationId(3.to_string())),
        metadata: None,
        market_id,
        kind: CommandKind::SetMarketState {
            state: MarketState::Closed,
        },
    };
    root.handle(close).expect("command handler succeeds");
    let events = root.take_changes();
    apply_outcome(&mut root, events);

    let settle = Command {
        correlation_id: Some(CorrelationId(4.to_string())),
        metadata: None,
        market_id,
        kind: CommandKind::SettleMarket {
            runner_results: vec![
                (RunnerId(1), betex::book::RunnerResult::Winner),
                (RunnerId(2), betex::book::RunnerResult::Loser),
            ],
            dead_heat_divisor: None,
        },
    };
    root.handle(settle).expect("command handler succeeds");
    let events = root.take_changes();
    assert!(
        events
            .iter()
            .any(|e| matches!(e.event, BookEvent::MarketSettled { .. })),
        "expected MarketSettled event"
    );
    apply_outcome(&mut root, events);

    let market = root.state().markets.get(&market_id).unwrap();
    assert_eq!(market.book.market_state(), BookMarketState::Settled);
}

#[test]
fn void_market_voids_trades_and_sets_terminal_state() {
    let cfg = Config::default();
    let mut state = EngineState::new(cfg);
    let _ts = unix_epoch();

    let market_id = MarketId(1);
    state.markets.insert(
        market_id,
        Market::from_config_with_cfg(&state.cfg, two_runner_market_config(market_id)),
    );
    let mut root = EngineRoot::new_with(state, 0);

    // Create one matched trade.
    for (correlation_id, side, account_id) in [
        (1u64, Side::No, AccountId::from(10)),
        (2u64, Side::Yes, AccountId::from(20)),
    ] {
        let cmd = Command {
            correlation_id: Some(CorrelationId(correlation_id.to_string())),
            metadata: None,
            market_id,
            kind: CommandKind::PlaceOrder {
                runner_id: RunnerId(1),
                account_id,
                client_order_id: None,
                side,
                odds: OddsX10000(20000),
                stake: Money(100),
                persistence: Persistence::Persist,
                time_in_force: TimeInForce::Gtc,
            },
        };
        root.handle(cmd).expect("command handler succeeds");
        let events = root.take_changes();
        apply_outcome(&mut root, events);
    }

    let void_cmd = Command {
        correlation_id: Some(CorrelationId(3.to_string())),
        metadata: None,
        market_id,
        kind: CommandKind::VoidMarket {
            reason: "TEST_VOID".to_string(),
        },
    };
    root.handle(void_cmd).expect("command handler succeeds");
    let events = root.take_changes();
    assert!(
        events.iter().any(|e| {
            matches!(
                e.event,
                BookEvent::MarketStateChanged {
                    to: BookMarketState::Voided,
                    ..
                }
            )
        }),
        "expected MarketStateChanged -> Voided event"
    );
    assert!(
        events
            .iter()
            .all(|e| !matches!(e.event, BookEvent::VoidTradesFromTime { .. })),
        "did not expect trade-void marker on market void"
    );

    apply_outcome(&mut root, events);

    let market = root.state().markets.get(&market_id).unwrap();
    assert_eq!(market.book.market_state(), BookMarketState::Voided);
}
