#![allow(
    clippy::unwrap_used,
    clippy::cast_precision_loss,
    clippy::cast_possible_truncation
)]

//! Criterion benchmarks for metadata-based sort modes (m6p3).
//!
//! Validates the m6p3 performance acceptance criterion:
//! - Metadata-based sorts (alphabetical, duration) complete in < 50ms at 500 items.
//!
//! Sort modes exercised:
//! - `alphabetical_asc`: packs 8-byte title prefix into u64 for ordering
//! - `alphabetical_desc`: inverse of alphabetical_asc
//! - `shortest`: reads "duration" metadata, orders ascending
//! - `longest`: reads "duration" metadata, orders descending
//!
//! Dataset: 500 items, each with a unique title and duration. No signals.
//! The benchmark uses a shared `LazyLock<TidalDb>` to amortize setup.

use std::sync::LazyLock;
use std::time::Duration;

use criterion::{Criterion, black_box, criterion_group, criterion_main};
use tidaldb::TidalDb;
use tidaldb::query::retrieve::Retrieve;
use tidaldb::schema::{DecaySpec, EntityId, EntityKind, SchemaBuilder, Window};

const N_ITEMS: u64 = 500;

/// Minimal schema: no text fields needed; metadata-only sorts don't use Tantivy.
fn sort_schema() -> tidaldb::schema::Schema {
    let mut builder = SchemaBuilder::new();
    let _ = builder
        .signal(
            "view",
            EntityKind::Item,
            DecaySpec::Exponential {
                half_life: Duration::from_secs(7 * 24 * 3600),
            },
        )
        .windows(&[Window::SevenDays])
        .velocity(false)
        .add();
    builder.build().unwrap()
}

/// Build the shared 500-item database. Called exactly once per benchmark run.
///
/// Items are written with:
/// - `"title"`: alphabetically varied titles ("Item 000" through "Item 499")
/// - `"duration"`: varied integer seconds (1..=500)
fn build_sort_db() -> TidalDb {
    let db = TidalDb::builder()
        .ephemeral()
        .with_schema(sort_schema())
        .open()
        .unwrap();

    for i in 0..N_ITEMS {
        let item_id = EntityId::new(i + 1);
        let mut meta = std::collections::HashMap::new();
        // Vary the title so alphabetical sort has real ordering work to do.
        meta.insert("title".to_string(), format!("Item {:03}", i));
        // Duration cycles 1..=500 seconds.
        meta.insert("duration".to_string(), (i + 1).to_string());
        db.write_item_with_metadata(item_id, &meta).unwrap();
    }

    db
}

/// Shared 500-item DB: built once, reused by all sort benchmarks.
static SORT_DB: LazyLock<TidalDb> = LazyLock::new(build_sort_db);

// ── Benchmarks ────────────────────────────────────────────────────────────────

/// Alphabetical A-Z sort over 500 items (< 50ms AC).
fn bench_alphabetical_asc(c: &mut Criterion) {
    let db: &TidalDb = &SORT_DB;

    let query = Retrieve::builder()
        .profile("alphabetical_asc")
        .limit(N_ITEMS as usize)
        .build()
        .unwrap();

    let mut group = c.benchmark_group("sort_500");
    group.bench_function("alphabetical_asc", |b| {
        b.iter(|| db.retrieve(black_box(&query)).unwrap());
    });
    group.finish();
}

/// Alphabetical Z-A sort over 500 items (< 50ms AC).
fn bench_alphabetical_desc(c: &mut Criterion) {
    let db: &TidalDb = &SORT_DB;

    let query = Retrieve::builder()
        .profile("alphabetical_desc")
        .limit(N_ITEMS as usize)
        .build()
        .unwrap();

    let mut group = c.benchmark_group("sort_500");
    group.bench_function("alphabetical_desc", |b| {
        b.iter(|| db.retrieve(black_box(&query)).unwrap());
    });
    group.finish();
}

/// Shortest-first sort over 500 items (< 50ms AC).
fn bench_shortest(c: &mut Criterion) {
    let db: &TidalDb = &SORT_DB;

    let query = Retrieve::builder()
        .profile("shortest")
        .limit(N_ITEMS as usize)
        .build()
        .unwrap();

    let mut group = c.benchmark_group("sort_500");
    group.bench_function("shortest", |b| {
        b.iter(|| db.retrieve(black_box(&query)).unwrap());
    });
    group.finish();
}

/// Longest-first sort over 500 items (< 50ms AC).
fn bench_longest(c: &mut Criterion) {
    let db: &TidalDb = &SORT_DB;

    let query = Retrieve::builder()
        .profile("longest")
        .limit(N_ITEMS as usize)
        .build()
        .unwrap();

    let mut group = c.benchmark_group("sort_500");
    group.bench_function("longest", |b| {
        b.iter(|| db.retrieve(black_box(&query)).unwrap());
    });
    group.finish();
}

criterion_group!(
    benches,
    bench_alphabetical_asc,
    bench_alphabetical_desc,
    bench_shortest,
    bench_longest,
);
criterion_main!(benches);
