//! Inventory and booking performance benchmarks.
//!
//! Run with: cargo bench -p rustledger-core

#![allow(missing_docs)]

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

use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use rustledger_core::NaiveDate;
use rustledger_core::{Amount, BookingMethod, Cost, CostSpec, Inventory, Position};

#[allow(clippy::missing_const_for_fn)]
fn date(year: i32, month: u32, day: u32) -> NaiveDate {
    rustledger_core::naive_date(year, month, day).unwrap()
}

/// Generate an inventory with N positions.
fn generate_inventory(num_positions: usize) -> Inventory {
    let mut inv = Inventory::new();

    for i in 0..num_positions {
        let cost = Cost::new(dec!(100.00) + Decimal::from(i as i32), "USD").with_date(date(
            2024,
            1,
            1 + (i % 28) as u32,
        ));

        inv.add(Position::with_cost(Amount::new(dec!(10), "STOCK"), cost));
    }

    inv
}

fn bench_inventory_add(c: &mut Criterion) {
    let mut group = c.benchmark_group("inventory_add");

    // Include 0/1/2/3 to capture the small-N regime where storage choice
    // (heap Vec vs inline SmallVec) makes the largest relative difference.
    for size in [0, 1, 2, 3, 10, 100, 1000] {
        if size > 0 {
            group.throughput(Throughput::Elements(size as u64));
        }

        group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, &size| {
            b.iter(|| {
                let mut inv = Inventory::new();
                for i in 0..size {
                    let cost = Cost::new(dec!(100.00) + Decimal::from(i), "USD");
                    inv.add(Position::with_cost(Amount::new(dec!(10), "STOCK"), cost));
                }
                std::hint::black_box(inv)
            });
        });
    }

    group.finish();
}

/// Clone benchmark — important because Inventory is cloned in many code
/// paths (BQL aggregator snapshots, plugin pre-state, validation). For
/// small-N inventories backed by `Vec<Position>`, every clone allocates;
/// the `SmallVec` switch makes 1-position clones allocation-free.
fn bench_inventory_clone(c: &mut Criterion) {
    let mut group = c.benchmark_group("inventory_clone");

    for size in [0, 1, 2, 3, 10] {
        let inv = generate_inventory(size);

        group.bench_with_input(BenchmarkId::from_parameter(size), &inv, |b, inv| {
            b.iter(|| std::hint::black_box(inv.clone()));
        });
    }

    group.finish();
}

fn bench_inventory_units(c: &mut Criterion) {
    let mut group = c.benchmark_group("inventory_units");

    for size in [10, 100, 1000] {
        let inv = generate_inventory(size);

        group.bench_with_input(BenchmarkId::from_parameter(size), &inv, |b, inv| {
            b.iter(|| std::hint::black_box(inv.units("STOCK")));
        });
    }

    group.finish();
}

fn bench_inventory_book_value(c: &mut Criterion) {
    let mut group = c.benchmark_group("inventory_book_value");

    for size in [10, 100, 1000] {
        let inv = generate_inventory(size);

        group.bench_with_input(BenchmarkId::from_parameter(size), &inv, |b, inv| {
            b.iter(|| std::hint::black_box(inv.book_value("STOCK")));
        });
    }

    group.finish();
}

fn bench_inventory_at_cost(c: &mut Criterion) {
    let mut group = c.benchmark_group("inventory_at_cost");

    for size in [10, 100, 1000] {
        let inv = generate_inventory(size);

        group.bench_with_input(BenchmarkId::from_parameter(size), &inv, |b, inv| {
            b.iter(|| std::hint::black_box(inv.at_cost()));
        });
    }

    group.finish();
}

fn bench_reduce_fifo(c: &mut Criterion) {
    let mut group = c.benchmark_group("reduce_fifo");

    for size in [10, 100, 500] {
        group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, &size| {
            b.iter_batched(
                || generate_inventory(size),
                |mut inv| {
                    // Reduce half the positions
                    for _ in 0..size / 2 {
                        let _ =
                            inv.reduce(&Amount::new(dec!(-10), "STOCK"), None, BookingMethod::Fifo);
                    }
                    std::hint::black_box(inv)
                },
                criterion::BatchSize::SmallInput,
            );
        });
    }

    group.finish();
}

fn bench_reduce_lifo(c: &mut Criterion) {
    let mut group = c.benchmark_group("reduce_lifo");

    for size in [10, 100, 500] {
        group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, &size| {
            b.iter_batched(
                || generate_inventory(size),
                |mut inv| {
                    // Reduce half the positions
                    for _ in 0..size / 2 {
                        let _ =
                            inv.reduce(&Amount::new(dec!(-10), "STOCK"), None, BookingMethod::Lifo);
                    }
                    std::hint::black_box(inv)
                },
                criterion::BatchSize::SmallInput,
            );
        });
    }

    group.finish();
}

fn bench_reduce_strict(c: &mut Criterion) {
    let mut group = c.benchmark_group("reduce_strict");

    for size in [10, 100, 500] {
        group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, &size| {
            b.iter_batched(
                || generate_inventory(size),
                |mut inv| {
                    // Reduce with specific cost spec
                    let spec = CostSpec::empty().with_date(date(2024, 1, 1));
                    let _ = inv.reduce(
                        &Amount::new(dec!(-10), "STOCK"),
                        Some(&spec),
                        BookingMethod::Strict,
                    );
                    std::hint::black_box(inv)
                },
                criterion::BatchSize::SmallInput,
            );
        });
    }

    group.finish();
}

fn bench_inventory_merge(c: &mut Criterion) {
    let mut group = c.benchmark_group("inventory_merge");

    for size in [10, 100, 500] {
        let inv1 = generate_inventory(size);
        let inv2 = generate_inventory(size);

        group.bench_with_input(
            BenchmarkId::from_parameter(size),
            &(inv1, inv2),
            |b, (inv1, inv2)| {
                b.iter_batched(
                    || inv1.clone(),
                    |mut inv| {
                        inv.merge(inv2);
                        std::hint::black_box(inv)
                    },
                    criterion::BatchSize::SmallInput,
                );
            },
        );
    }

    group.finish();
}

criterion_group!(
    benches,
    bench_inventory_add,
    bench_inventory_clone,
    bench_inventory_units,
    bench_inventory_book_value,
    bench_inventory_at_cost,
    bench_reduce_fifo,
    bench_reduce_lifo,
    bench_reduce_strict,
    bench_inventory_merge,
);
criterion_main!(benches);
