use chainfire_client::ChainFireClient;
use chainfire_server::config::{ClusterConfig, NetworkConfig, NodeConfig, RaftConfig, ServerConfig, StorageConfig};
use chainfire_server::node::Node;
use chainfire_types::RaftRole;
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use std::time::Duration;
use tempfile::TempDir;
use tokio::runtime::Runtime;

const VALUE_SIZE: usize = 1024; // 1KB
const NUM_KEYS_THROUGHPUT: usize = 10_000; // 10K for throughput tests
const NUM_KEYS_LATENCY: usize = 100; // 100 for latency tests

fn create_test_node(temp_dir: &TempDir) -> (Node, Runtime) {
    let rt = tokio::runtime::Builder::new_multi_thread()
        .worker_threads(4)
        .enable_all()
        .build()
        .unwrap();

    let config = ServerConfig {
        node: NodeConfig {
            id: 1,
            name: "benchmark-node".to_string(),
            role: "control_plane".to_string(),
        },
        cluster: ClusterConfig {
            id: 1,
            bootstrap: true,
            initial_members: vec![],
        },
        network: NetworkConfig {
            api_addr: "127.0.0.1:2379".parse().unwrap(),
            raft_addr: "127.0.0.1:2380".parse().unwrap(),
            gossip_addr: "127.0.0.1:2381".parse().unwrap(),
            tls: None,
        },
        storage: StorageConfig {
            data_dir: temp_dir.path().to_path_buf(),
        },
        raft: RaftConfig {
            role: RaftRole::Voter,
            tick_interval_ms: 100,
            election_timeout_ticks: 10,
            heartbeat_interval_ticks: 3,
            snapshot_interval_secs: 3600,
            max_applied_log_to_keep: 1000,
        },
    };

    let node = rt.block_on(async { Node::new(config).await.unwrap() });

    (node, rt)
}

fn bench_put_throughput(c: &mut Criterion) {
    let temp_dir = TempDir::new().unwrap();
    let (node, rt) = create_test_node(&temp_dir);

    // Start server
    let server_handle = rt.spawn(async move {
        node.run().await.unwrap();
    });

    // Give server time to start
    std::thread::sleep(Duration::from_millis(500));

    // Create client
    let mut client = rt.block_on(async {
        ChainFireClient::connect("http://127.0.0.1:2379")
            .await
            .unwrap()
    });

    let value = vec![b'x'; VALUE_SIZE];

    let mut group = c.benchmark_group("put_throughput");
    group.throughput(Throughput::Elements(NUM_KEYS_THROUGHPUT as u64));
    group.sample_size(10);
    group.measurement_time(Duration::from_secs(30));

    group.bench_function(BenchmarkId::from_parameter(NUM_KEYS_THROUGHPUT), |b| {
        b.iter(|| {
            rt.block_on(async {
                for i in 0..NUM_KEYS_THROUGHPUT {
                    let key = format!("bench_key_{}", i);
                    client.put(black_box(&key), black_box(&value)).await.unwrap();
                }
            })
        });
    });

    group.finish();

    // Cleanup
    server_handle.abort();
    drop(rt);
}

fn bench_get_throughput(c: &mut Criterion) {
    let temp_dir = TempDir::new().unwrap();
    let (node, rt) = create_test_node(&temp_dir);

    // Start server
    let server_handle = rt.spawn(async move {
        node.run().await.unwrap();
    });

    // Give server time to start
    std::thread::sleep(Duration::from_millis(500));

    // Create client and populate data
    let mut client = rt.block_on(async {
        ChainFireClient::connect("http://127.0.0.1:2379")
            .await
            .unwrap()
    });

    let value = vec![b'x'; VALUE_SIZE];

    // Pre-populate keys
    rt.block_on(async {
        for i in 0..NUM_KEYS_THROUGHPUT {
            let key = format!("bench_key_{}", i);
            client.put(&key, &value).await.unwrap();
        }
    });

    let mut group = c.benchmark_group("get_throughput");
    group.throughput(Throughput::Elements(NUM_KEYS_THROUGHPUT as u64));
    group.sample_size(10);
    group.measurement_time(Duration::from_secs(30));

    group.bench_function(BenchmarkId::from_parameter(NUM_KEYS_THROUGHPUT), |b| {
        b.iter(|| {
            rt.block_on(async {
                for i in 0..NUM_KEYS_THROUGHPUT {
                    let key = format!("bench_key_{}", i);
                    let _ = client.get(black_box(&key)).await.unwrap();
                }
            })
        });
    });

    group.finish();

    // Cleanup
    server_handle.abort();
    drop(rt);
}

fn bench_put_latency(c: &mut Criterion) {
    let temp_dir = TempDir::new().unwrap();
    let (node, rt) = create_test_node(&temp_dir);

    // Start server
    let server_handle = rt.spawn(async move {
        node.run().await.unwrap();
    });

    // Give server time to start
    std::thread::sleep(Duration::from_millis(500));

    // Create client
    let mut client = rt.block_on(async {
        ChainFireClient::connect("http://127.0.0.1:2379")
            .await
            .unwrap()
    });

    let value = vec![b'x'; VALUE_SIZE];

    let mut group = c.benchmark_group("put_latency");
    group.sample_size(1000); // Larger sample for better p99/p999 estimates
    group.measurement_time(Duration::from_secs(60));

    group.bench_function("single_put", |b| {
        let mut key_counter = 0;
        b.iter(|| {
            let key = format!("latency_key_{}", key_counter);
            key_counter += 1;
            rt.block_on(async {
                client.put(black_box(&key), black_box(&value)).await.unwrap();
            })
        });
    });

    group.finish();

    // Cleanup
    server_handle.abort();
    drop(rt);
}

criterion_group!(benches, bench_put_throughput, bench_get_throughput, bench_put_latency);
criterion_main!(benches);
