#![allow(clippy::unwrap_used)]

use std::time::Duration;

use criterion::{Criterion, black_box, criterion_group, criterion_main};
use tidaldb::schema::{DecaySpec, EntityId, EntityKind, SchemaBuilder, Timestamp, Window};
use tidaldb::signals::{NoopWalWriter, SignalLedger, SignalTypeId};

fn view_ledger() -> (SignalLedger, SignalTypeId) {
    let mut builder = SchemaBuilder::new();
    let _ = builder
        .signal(
            "view",
            EntityKind::Item,
            DecaySpec::Exponential {
                half_life: Duration::from_secs(7 * 24 * 3600),
            },
        )
        .windows(&[Window::OneHour, Window::TwentyFourHours, Window::SevenDays])
        .velocity(false)
        .add();
    let schema = builder.build().unwrap();
    let type_id = {
        // Alphabetical sort assigns "view" → id 0 (only signal).
        SignalTypeId::new(0)
    };
    let ledger = SignalLedger::new(schema, Box::new(NoopWalWriter));
    (ledger, type_id)
}

// Pre-computed lambda for a 7-day half-life exponential decay.
const LAMBDA_7D: f64 = std::f64::consts::LN_2 / (7.0 * 24.0 * 3600.0);

/// Benchmark: single signal write (excluding WAL — `NoopWalWriter`).
/// Target: < 100ns.
fn bench_single_signal_write(c: &mut Criterion) {
    let (ledger, _type_id) = view_ledger();
    let entity_id = EntityId::new(1);

    // Fixed timestamp avoids SystemTime::now() overhead per iteration.
    let fixed_ns = Timestamp::now().as_nanos();
    let ts = Timestamp::from_nanos(fixed_ns);

    c.bench_function("signal_write_single", |b| {
        b.iter(|| {
            ledger
                .record_signal(
                    black_box("view"),
                    black_box(entity_id),
                    black_box(1.0_f64),
                    black_box(ts),
                )
                .unwrap();
        });
    });
}

/// Benchmark: decay score read for a single entity.
/// Setup: 100 pre-written signals to ensure a non-trivial hot state.
/// Target: < 100ns.
fn bench_decay_score_read(c: &mut Criterion) {
    let (ledger, _type_id) = view_ledger();
    let entity_id = EntityId::new(42);

    // Pre-warm: 100 signals spread over the past 7 days.
    let base_ns = Timestamp::now().as_nanos();
    let seven_days_ns: u64 = 7 * 24 * 3600 * 1_000_000_000;
    for i in 0u64..100 {
        let ts = Timestamp::from_nanos(
            base_ns.saturating_sub(seven_days_ns) + i * (seven_days_ns / 100),
        );
        ledger.record_signal("view", entity_id, 1.0, ts).unwrap();
    }

    c.bench_function("signal_decay_score_read", |b| {
        b.iter(|| {
            ledger
                .read_decay_score(black_box(entity_id), black_box("view"), black_box(0))
                .unwrap()
        });
    });
}

/// Benchmark: 200-entity scoring pass using direct `DashMap` access to isolate
/// the hot-path read from schema lookup overhead.
/// Setup: 200 entities, each with 50 pre-written signals.
/// Target: < 5µs total.
fn bench_200_entity_scoring_pass(c: &mut Criterion) {
    let (ledger, type_id) = view_ledger();

    // Pre-warm: 200 entities × 50 signals each.
    let base_ns = Timestamp::now().as_nanos();
    let entity_ids: Vec<EntityId> = (0u64..200).map(EntityId::new).collect();
    for &entity_id in &entity_ids {
        for j in 0u64..50 {
            let ts = Timestamp::from_nanos(
                base_ns.saturating_sub(3_600_000_000_000) + j * 72_000_000_000,
            );
            ledger.record_signal("view", entity_id, 1.0, ts).unwrap();
        }
    }

    let now_ns = Timestamp::now().as_nanos();

    c.bench_function("signal_200_entity_scoring_pass", |b| {
        b.iter(|| {
            let mut sum = 0.0_f64;
            for &entity_id in black_box(&entity_ids) {
                if let Some(entry) = ledger.entries().get(&(entity_id, type_id)) {
                    sum += entry.hot.current_score(
                        black_box(0),
                        black_box(now_ns),
                        black_box(LAMBDA_7D),
                    );
                }
            }
            black_box(sum)
        });
    });
}

criterion_group!(
    benches,
    bench_single_signal_write,
    bench_decay_score_read,
    bench_200_entity_scoring_pass,
);
criterion_main!(benches);
