use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
use std::hint::black_box;

use betex::{
    book::Book,
    book::protocol::command::{BinarySide, BinaryTimeInForce, Command, CommandKind},
    types::*,
};

const MARKET_ID: MarketId = MarketId(1);

fn account_id() -> AccountId {
    AccountId::from(1000)
}

fn place_binary_cmd(
    order_id: OrderId,
    side: BinarySide,
    price_ticks: u16,
    qty_shares: u64,
) -> Command {
    Command {
        correlation_id: None,
        metadata: None,
        market_id: MARKET_ID,
        kind: CommandKind::PlaceBinaryOrder {
            account_id: account_id(),
            client_order_id: Some(ClientOrderId(order_id.0)),
            side,
            price_ticks,
            qty_shares,
            time_in_force: BinaryTimeInForce::Gtc,
        },
    }
}

fn place_binary_ioc_cmd(
    order_id: OrderId,
    side: BinarySide,
    price_ticks: u16,
    qty_shares: u64,
) -> Command {
    Command {
        correlation_id: None,
        metadata: None,
        market_id: MARKET_ID,
        kind: CommandKind::PlaceBinaryOrder {
            account_id: account_id(),
            client_order_id: Some(ClientOrderId(order_id.0)),
            side,
            price_ticks,
            qty_shares,
            time_in_force: BinaryTimeInForce::FillOrKill {
                min_fill: Some(Quantity(0)),
            },
        },
    }
}

fn replace_binary_cmd(order_id: OrderId, new_price_ticks: u16, new_qty_shares: u64) -> Command {
    Command {
        correlation_id: None,
        metadata: None,
        market_id: MARKET_ID,
        kind: CommandKind::ReplaceBinaryOrder {
            account_id: account_id(),
            order_id,
            new_price_ticks: Some(new_price_ticks),
            new_qty_shares: Some(new_qty_shares),
        },
    }
}

fn apply_cmd_and_events(book: &mut Book, cmd: &Command) {
    let (events, _) = black_box(book.handle(black_box(cmd))).unwrap();
    book.apply_all_events(&black_box(events));
}

fn create_binary_yes_book(max_price_ticks: u16) -> Book {
    Book::new_binary_yes_with_kind_and_capacity(
        MARKET_ID,
        MarketKind::InPlayCapable,
        RunnerId(1),
        RunnerId(2),
        max_price_ticks,
        20_000,
    )
}

fn bench_binary_insert_no_match(c: &mut Criterion) {
    let mut group = c.benchmark_group("binary_yes/insert_no_match");

    let max_price_ticks = 100u16; // $0.01 ticks
    for n in [100usize, 500, 2_000, 10_000] {
        group.throughput(Throughput::Elements(n as u64));
        group.bench_with_input(BenchmarkId::new("buy_only", n), &n, |b, n| {
            b.iter(|| {
                let mut book = create_binary_yes_book(max_price_ticks);
                for i in 0..*n {
                    let oid = OrderId(i as u64 + 1);
                    let tick = 10 + (i % 40) as u16; // always within 1..max-1
                    let cmd = place_binary_cmd(oid, BinarySide::Buy, tick, 10);
                    apply_cmd_and_events(&mut book, &cmd);
                }
            });
        });
    }
    group.finish();
}

fn bench_binary_simple_full_match(c: &mut Criterion) {
    let mut group = c.benchmark_group("binary_yes/simple_full_match");
    group.throughput(Throughput::Elements(1));

    let max_price_ticks = 100u16;
    group.bench_function("cross_at_one_level", |b| {
        b.iter(|| {
            let mut book = create_binary_yes_book(max_price_ticks);
            let maker = place_binary_cmd(OrderId(1), BinarySide::Sell, 60, 1_000);
            apply_cmd_and_events(&mut book, &maker);

            let taker = place_binary_ioc_cmd(OrderId(2), BinarySide::Buy, 60, 1_000);
            apply_cmd_and_events(&mut book, &taker);
        });
    });

    group.finish();
}

fn bench_binary_match_through_levels(c: &mut Criterion) {
    let mut group = c.benchmark_group("binary_yes/match_through_levels");
    group.throughput(Throughput::Elements(1));

    let max_price_ticks = 100u16;
    group.bench_function("sweep_asks", |b| {
        b.iter(|| {
            let mut book = create_binary_yes_book(max_price_ticks);

            // Populate ask side levels 51..=70
            let mut oid = 1u64;
            for px in 51u16..=70u16 {
                let cmd = place_binary_cmd(OrderId(oid), BinarySide::Sell, px, 50);
                apply_cmd_and_events(&mut book, &cmd);
                oid += 1;
            }

            // Sweep with a single aggressive buy.
            let sweep = place_binary_ioc_cmd(OrderId(10_000), BinarySide::Buy, 70, 1_000);
            apply_cmd_and_events(&mut book, &sweep);
        });
    });

    group.finish();
}

fn bench_binary_replace(c: &mut Criterion) {
    let mut group = c.benchmark_group("binary_yes/replace");

    let max_price_ticks = 100u16;
    for n in [50usize, 200, 1_000] {
        group.throughput(Throughput::Elements(n as u64));
        group.bench_with_input(BenchmarkId::new("replace_chain", n), &n, |b, n| {
            b.iter(|| {
                let mut book = create_binary_yes_book(max_price_ticks);
                apply_cmd_and_events(
                    &mut book,
                    &place_binary_cmd(OrderId(1), BinarySide::Buy, 40, 10),
                );

                let mut cur = 1u64;
                for i in 0..*n {
                    let new = cur + 1;
                    let px = (30 + (i % 60) as u16).clamp(1, max_price_ticks - 1);
                    let cmd = replace_binary_cmd(OrderId(cur), px, 10);
                    apply_cmd_and_events(&mut book, &cmd);
                    cur = new;
                }
            });
        });
    }
    group.finish();
}

fn bench_binary_depth_query(c: &mut Criterion) {
    let mut group = c.benchmark_group("binary_yes/depth_query");
    group.throughput(Throughput::Elements(1));

    let max_price_ticks = 100u16;
    group.bench_function("depth_20", |b| {
        b.iter(|| {
            let mut book = create_binary_yes_book(max_price_ticks);

            // Populate both sides at many levels to exercise scan.
            let mut oid = 1u64;
            for px in 1u16..=(max_price_ticks - 1) {
                if px % 2 == 0 {
                    let cmd = place_binary_cmd(OrderId(oid), BinarySide::Buy, px, 10);
                    apply_cmd_and_events(&mut book, &cmd);
                } else {
                    let cmd = place_binary_cmd(OrderId(oid), BinarySide::Sell, px, 10);
                    apply_cmd_and_events(&mut book, &cmd);
                }
                oid += 1;
            }

            let d = black_box(book.binary_depth(20));
            black_box(d);
        });
    });
    group.finish();
}

criterion_group!(
    binary_yes_benchmarks,
    bench_binary_insert_no_match,
    bench_binary_simple_full_match,
    bench_binary_match_through_levels,
    bench_binary_replace,
    bench_binary_depth_query,
);
criterion_main!(binary_yes_benchmarks);
