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

use ocg::graph::{GraphBackend, PropertyGraph, NetworKitRustBackend};

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

    for size in [100, 1_000, 10_000] {
        group.throughput(Throughput::Elements(size as u64));

        group.bench_with_input(BenchmarkId::new("PropertyGraph", size), &size, |b, &size| {
            b.iter(|| {
                let mut graph = PropertyGraph::new();
                let mut props = IndexMap::new();
                props.insert("name".to_string(), "TestNode".into());
                for _ in 0..size {
                    black_box(graph.create_node(vec!["Node"], props.clone()));
                }
                graph
            });
        });

        group.bench_with_input(BenchmarkId::new("NetworKitRust", size), &size, |b, &size| {
            b.iter(|| {
                let mut graph = NetworKitRustBackend::new();
                let mut props = IndexMap::new();
                props.insert("name".to_string(), "TestNode".into());
                for _ in 0..size {
                    black_box(graph.create_node(vec!["Node"], props.clone()));
                }
                graph
            });
        });
    }

    group.finish();
}

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

    for size in [100, 1_000, 10_000] {
        group.throughput(Throughput::Elements(size as u64));

        group.bench_with_input(BenchmarkId::new("PropertyGraph", size), &size, |b, &size| {
            b.iter(|| {
                let mut graph = PropertyGraph::new();
                // Create a chain of nodes (returns IDs)
                let nodes: Vec<u64> = (0..size).map(|_| graph.create_node(vec!["Node"], IndexMap::new()).id).collect();
                // Create edges
                for i in 0..size - 1 {
                    black_box(graph.create_relationship(nodes[i], nodes[i + 1], "NEXT", IndexMap::new())).unwrap();
                }
                graph
            });
        });

        group.bench_with_input(BenchmarkId::new("NetworKitRust", size), &size, |b, &size| {
            b.iter(|| {
                let mut graph = NetworKitRustBackend::new();
                let nodes: Vec<u64> = (0..size).map(|_| graph.create_node(vec!["Node"], IndexMap::new())).collect();
                for i in 0..size - 1 {
                    black_box(graph.create_relationship(nodes[i], nodes[i + 1], "NEXT", IndexMap::new())).unwrap();
                }
                graph
            });
        });
    }

    group.finish();
}

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

    for size in [100, 1_000, 10_000] {
        group.throughput(Throughput::Elements(size as u64));

        // Setup PropertyGraph
        let mut pg_graph = PropertyGraph::new();
        let pg_nodes: Vec<u64> = (0..size).map(|_| pg_graph.create_node(vec!["Node"], IndexMap::new()).id).collect();
        for i in 0..size - 1 {
            pg_graph.create_relationship(pg_nodes[i], pg_nodes[i + 1], "NEXT", IndexMap::new()).unwrap();
        }

        // Setup NetworKitRust
        let mut nk_graph = NetworKitRustBackend::new();
        let nk_nodes: Vec<u64> = (0..size).map(|_| nk_graph.create_node(vec!["Node"], IndexMap::new())).collect();
        for i in 0..size - 1 {
            nk_graph.create_relationship(nk_nodes[i], nk_nodes[i + 1], "NEXT", IndexMap::new()).unwrap();
        }

        group.bench_with_input(BenchmarkId::new("PropertyGraph", size), &size, |b, _| {
            b.iter(|| {
                let mut count = 0;
                for &node_id in &pg_nodes {
                    count += black_box(pg_graph.outgoing_edges(node_id).len());
                }
                count
            });
        });

        group.bench_with_input(BenchmarkId::new("NetworKitRust", size), &size, |b, _| {
            b.iter(|| {
                let mut count = 0;
                for &node_id in &nk_nodes {
                    count += black_box(nk_graph.outgoing_edges(node_id).len());
                }
                count
            });
        });
    }

    group.finish();
}

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

    for size in [100, 1_000, 10_000] {
        group.throughput(Throughput::Elements(size as u64));

        // Setup PropertyGraph
        let mut pg_graph = PropertyGraph::new();
        for i in 0..size {
            let label = if i % 3 == 0 { "Person" } else if i % 3 == 1 { "Company" } else { "Product" };
            pg_graph.create_node(vec![label], IndexMap::new());
        }

        // Setup NetworKitRust
        let mut nk_graph = NetworKitRustBackend::new();
        for i in 0..size {
            let label = if i % 3 == 0 { "Person" } else if i % 3 == 1 { "Company" } else { "Product" };
            nk_graph.create_node(vec![label], IndexMap::new());
        }

        group.bench_with_input(BenchmarkId::new("PropertyGraph", size), &size, |b, _| {
            b.iter(|| {
                black_box(pg_graph.nodes_with_label("Person").len())
            });
        });

        group.bench_with_input(BenchmarkId::new("NetworKitRust", size), &size, |b, _| {
            b.iter(|| {
                black_box(nk_graph.nodes_with_label("Person").len())
            });
        });
    }

    group.finish();
}

criterion_group!(
    benches,
    bench_node_creation,
    bench_edge_creation,
    bench_neighbor_traversal,
    bench_label_lookup
);
criterion_main!(benches);
