//! Benchmarks for the multi-file graph storage engine (GraphStorageManager + Graph4D).
//!
//! Run with: cargo bench --bench storage_benchmarks

use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use geographdb_core::algorithms::four_d::{GraphNode4D, TemporalEdge};
use geographdb_core::storage::{load_graph4d, save_graph4d, GraphStorageManager, NodeRec};
use std::collections::BTreeMap;
use tempfile::tempdir;

fn make_node(id: u64) -> NodeRec {
    NodeRec {
        id,
        morton_code: id,
        x: (id as f32 * 0.1) % 1000.0,
        y: (id as f32 * 0.2) % 1000.0,
        z: (id as f32 * 0.3) % 1000.0,
        edge_off: 0,
        edge_len: 0,
        flags: 0,
        begin_ts: id * 10,
        end_ts: id * 10 + 100,
        tx_id: 0,
        visibility: 1,
        _padding: [0u8; 7],
    }
}

fn bench_node_write(c: &mut Criterion) {
    let mut group = c.benchmark_group("storage_node_write");
    for count in [1_000, 10_000, 100_000].iter() {
        group.throughput(Throughput::Elements(*count as u64));
        group.bench_with_input(BenchmarkId::from_parameter(count), count, |b, &count| {
            b.iter_with_setup(
                || {
                    let dir = tempdir().unwrap();
                    let sm = GraphStorageManager::open(dir.path(), 4096, 4096, 4096, 4096).unwrap();
                    (dir, sm)
                },
                |(_dir, sm)| {
                    for i in 0..count {
                        let node = make_node(i as u64);
                        sm.write_node_at(black_box(&node)).unwrap();
                    }
                },
            );
        });
    }
    group.finish();
}

fn bench_edge_append(c: &mut Criterion) {
    let mut group = c.benchmark_group("storage_edge_append");
    for count in [1_000, 10_000, 100_000].iter() {
        group.throughput(Throughput::Elements(*count as u64));
        group.bench_with_input(BenchmarkId::from_parameter(count), count, |b, &count| {
            b.iter_with_setup(
                || {
                    let dir = tempdir().unwrap();
                    let sm = GraphStorageManager::open(dir.path(), 4096, 4096, 4096, 4096).unwrap();
                    (dir, sm)
                },
                |(_dir, sm)| {
                    for i in 0..count {
                        let edge = geographdb_core::storage::EdgeRec {
                            src: i as u64,
                            dst: (i as u64 + 1) % 1000,
                            w: 1.0,
                            flags: 0,
                            begin_ts: 0,
                            end_ts: 0,
                            tx_id: 0,
                            visibility: 1,
                            _padding: [0u8; 7],
                        };
                        sm.append_edge_record(black_box(&edge)).unwrap();
                    }
                },
            );
        });
    }
    group.finish();
}

fn bench_wal_append(c: &mut Criterion) {
    use geographdb_core::storage::WalEntry;
    let mut group = c.benchmark_group("storage_wal_append");
    for count in [1_000, 10_000].iter() {
        group.throughput(Throughput::Elements(*count as u64));
        group.bench_with_input(BenchmarkId::from_parameter(count), count, |b, &count| {
            b.iter_with_setup(
                || {
                    let dir = tempdir().unwrap();
                    let sm = GraphStorageManager::open(dir.path(), 4096, 4096, 4096, 4096).unwrap();
                    (dir, sm)
                },
                |(_dir, sm)| {
                    for i in 0..count {
                        let entry = WalEntry {
                            timestamp: i as u64,
                            node_id: i as u64,
                            edge_dst: 0,
                            x: 0.0,
                            y: 0.0,
                            z: 0.0,
                            edge_w: 0.0,
                            entry_type: 1,
                            _padding: [0u8; 7],
                            tx_id: 0,
                            lsn: i as u64 + 1,
                        };
                        sm.append_wal_entry(black_box(&entry)).unwrap();
                    }
                },
            );
        });
    }
    group.finish();
}

fn bench_graph4d_save_load(c: &mut Criterion) {
    let mut group = c.benchmark_group("storage_graph4d_save_load");
    for node_count in [1_000, 10_000].iter() {
        group.throughput(Throughput::Elements(*node_count as u64));

        // Save benchmark
        group.bench_with_input(
            BenchmarkId::new("save", node_count),
            node_count,
            |b, &count| {
                b.iter_with_setup(
                    || {
                        let nodes: Vec<GraphNode4D> = (0..count)
                            .map(|i| {
                                let mut node = GraphNode4D {
                                    id: i as u64,
                                    x: (i as f32 * 0.1) % 1000.0,
                                    y: (i as f32 * 0.2) % 1000.0,
                                    z: (i as f32 * 0.3) % 1000.0,
                                    begin_ts: i as u64 * 10,
                                    end_ts: i as u64 * 10 + 100,
                                    properties: BTreeMap::new(),
                                    successors: vec![TemporalEdge {
                                        dst: ((i + 1) % count) as u64,
                                        weight: 1.0,
                                        begin_ts: 0,
                                        end_ts: 100,
                                    }],
                                };
                                if i % 10 == 0 {
                                    node.properties.insert(
                                        "kind".to_string(),
                                        serde_json::Value::String("hub".to_string()),
                                    );
                                }
                                node
                            })
                            .collect();
                        let dir = tempdir().unwrap();
                        (dir, nodes)
                    },
                    |(dir, nodes)| {
                        save_graph4d(black_box(&nodes), black_box(dir.path())).unwrap();
                    },
                );
            },
        );

        // Roundtrip benchmark (save then load — realistic workload)
        group.bench_with_input(
            BenchmarkId::new("roundtrip", node_count),
            node_count,
            |b, &count| {
                // Pre-build nodes once (not measured)
                let nodes: Vec<GraphNode4D> = (0..count)
                    .map(|i| {
                        let mut node = GraphNode4D {
                            id: i as u64,
                            x: (i as f32 * 0.1) % 1000.0,
                            y: (i as f32 * 0.2) % 1000.0,
                            z: (i as f32 * 0.3) % 1000.0,
                            begin_ts: i as u64 * 10,
                            end_ts: i as u64 * 10 + 100,
                            properties: BTreeMap::new(),
                            successors: vec![TemporalEdge {
                                dst: ((i + 1) % count) as u64,
                                weight: 1.0,
                                begin_ts: 0,
                                end_ts: 100,
                            }],
                        };
                        if i % 10 == 0 {
                            node.properties.insert(
                                "kind".to_string(),
                                serde_json::Value::String("hub".to_string()),
                            );
                        }
                        node
                    })
                    .collect();

                b.iter(|| {
                    let dir = tempdir().unwrap();
                    save_graph4d(black_box(&nodes), black_box(dir.path())).unwrap();
                    std::thread::sleep(std::time::Duration::from_millis(5));
                    let _loaded = load_graph4d(black_box(dir.path())).unwrap();
                    drop(dir); // cleanup after load
                });
            },
        );
    }
    group.finish();
}

fn bench_random_node_read(c: &mut Criterion) {
    let mut group = c.benchmark_group("storage_random_node_read");
    for count in [1_000, 10_000, 100_000].iter() {
        group.bench_with_input(BenchmarkId::from_parameter(count), count, |b, &count| {
            let dir = tempdir().unwrap();
            let sm = GraphStorageManager::open(dir.path(), 4096, 4096, 4096, 4096).unwrap();
            for i in 0..count {
                sm.write_node_at(&make_node(i)).unwrap();
            }

            b.iter(|| {
                // deterministic "random" access for reproducible benchmarking
                let idx = black_box((count / 2 + 1) % count) as u64;
                let _ = sm.get_node_record(idx);
            });
        });
    }
    group.finish();
}

criterion_group!(
    benches,
    bench_node_write,
    bench_edge_append,
    bench_wal_append,
    bench_graph4d_save_load,
    bench_random_node_read
);
criterion_main!(benches);
