//! Mining benchmarks for Synor blockchain.
//!
//! Benchmarks:
//! - kHeavyHash hashing throughput
//! - Matrix initialization
//! - Nonce search rate
//! - Block template creation
//! - Mining coordination
//!
//! Run with: cargo bench -p synor-mining --bench mining_bench

use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use synor_mining::{
    kheavyhash::{KHeavyHash, ParallelMiner},
    matrix::{GfTables, HeavyMatrix, OptimizedMatrix},
    miner::{BlockMiner, MinerConfig, ParallelBlockMiner},
    template::{
        BlockTemplate, BlockTemplateBuilder, CoinbaseBuilder, CoinbaseData, TemplateTransaction,
    },
    Target,
};
use synor_types::{Address, Hash256, Network};

// ==================== Test Data Helpers ====================

/// Creates a test address.
fn test_address() -> Address {
    Address::from_ed25519_pubkey(Network::Mainnet, &[0x42; 32])
}

/// Creates a deterministic hash.
fn test_hash(n: u8) -> Hash256 {
    let mut bytes = [0u8; 32];
    bytes[0] = n;
    bytes[31] = n;
    Hash256::from_bytes(bytes)
}

/// Creates a test coinbase.
fn test_coinbase(height: u64) -> CoinbaseData {
    CoinbaseBuilder::new(test_address(), height)
        .extra_data(b"Synor Benchmark Pool".to_vec())
        .reward(500_00000000)
        .fees(1_00000000)
        .build()
}

/// Creates a template transaction.
fn test_template_tx(n: u64) -> TemplateTransaction {
    TemplateTransaction {
        txid: test_hash((n % 256) as u8),
        data: vec![0x42u8; 250], // ~250 byte transaction
        fee: 1000 + (n * 100),
        mass: 1000 + (n * 50),
    }
}

/// Creates a block template with the specified number of transactions.
fn create_test_template(tx_count: usize) -> BlockTemplate {
    let mut builder = BlockTemplateBuilder::new()
        .version(1)
        .add_parent(test_hash(1))
        .add_parent(test_hash(2))
        .selected_parent(test_hash(1))
        .timestamp(1234567890000)
        .bits(0x207fffff) // Easy target for benchmarking
        .blue_score(1000)
        .coinbase(test_coinbase(1000))
        .reward(500_00000000);

    for i in 0..tx_count {
        builder = builder.add_transaction(test_template_tx(i as u64));
    }

    builder.build(1).unwrap()
}

// ==================== kHeavyHash Throughput ====================

fn kheavyhash_throughput_single(c: &mut Criterion) {
    let hasher = KHeavyHash::new();
    let header = [0x42u8; 80];
    let pre_hash = hasher.pre_hash(&header);

    let mut group = c.benchmark_group("kheavyhash_throughput");
    group.throughput(Throughput::Elements(1));

    group.bench_function("single_hash", |b| {
        let mut nonce = 0u64;
        b.iter(|| {
            let result = hasher.finalize(&pre_hash, nonce);
            nonce = nonce.wrapping_add(1);
            black_box(result)
        })
    });

    // Batch throughput
    for batch_size in [100, 1000, 10000] {
        group.throughput(Throughput::Elements(batch_size));
        group.bench_with_input(
            BenchmarkId::new("batch", batch_size),
            &batch_size,
            |b, &size| {
                b.iter(|| {
                    for nonce in 0..size {
                        black_box(hasher.finalize(&pre_hash, nonce));
                    }
                })
            },
        );
    }

    group.finish();
}

fn kheavyhash_sustained_rate(c: &mut Criterion) {
    let hasher = KHeavyHash::new();
    let header = [0xAB; 80];
    let pre_hash = hasher.pre_hash(&header);

    c.bench_function("kheavyhash_sustained_10k", |b| {
        b.iter(|| {
            let mut count = 0u64;
            for nonce in 0..10000 {
                let pow = hasher.finalize(&pre_hash, nonce);
                // Simulate target check
                if pow.hash.as_bytes()[0] == 0 {
                    count += 1;
                }
            }
            black_box(count)
        })
    });
}

// ==================== Matrix Initialization ====================

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

    group.bench_function("heavy_matrix_new", |b| {
        b.iter(|| black_box(HeavyMatrix::new()))
    });

    group.bench_function("optimized_matrix_new", |b| {
        b.iter(|| black_box(OptimizedMatrix::new()))
    });

    group.bench_function("gf_tables_new", |b| b.iter(|| black_box(GfTables::new())));

    // Custom seed initialization
    for seed_idx in [0u8, 42u8, 255u8] {
        let seed = [seed_idx; 32];
        group.bench_with_input(
            BenchmarkId::new("matrix_from_seed", seed_idx),
            &seed,
            |b, s| b.iter(|| black_box(HeavyMatrix::from_seed(s))),
        );
    }

    group.finish();
}

fn matrix_memory_patterns(c: &mut Criterion) {
    let optimized = OptimizedMatrix::new();

    let mut group = c.benchmark_group("matrix_patterns");

    // Sequential access pattern
    group.bench_function("sequential_input", |b| {
        let inputs: Vec<[u8; 32]> = (0..100)
            .map(|i| {
                let mut arr = [0u8; 32];
                for (j, byte) in arr.iter_mut().enumerate() {
                    *byte = ((i + j) & 0xFF) as u8;
                }
                arr
            })
            .collect();

        b.iter(|| {
            for input in &inputs {
                black_box(optimized.multiply(input));
            }
        })
    });

    // Random access pattern
    group.bench_function("random_input", |b| {
        // Pre-generated "random" inputs
        let inputs: Vec<[u8; 32]> = (0..100)
            .map(|i| {
                let mut arr = [0u8; 32];
                let mut state = i as u8;
                for byte in arr.iter_mut() {
                    state = state.wrapping_mul(17).wrapping_add(31);
                    *byte = state;
                }
                arr
            })
            .collect();

        b.iter(|| {
            for input in &inputs {
                black_box(optimized.multiply(input));
            }
        })
    });

    group.finish();
}

// ==================== Nonce Search Rate ====================

fn nonce_search_rate(c: &mut Criterion) {
    let hasher = KHeavyHash::new();
    let header = [0x42u8; 80];

    let mut group = c.benchmark_group("nonce_search");

    // Search with very easy target (always finds quickly)
    group.bench_function("easy_target_search", |b| {
        let target = Target::max();
        b.iter(|| black_box(hasher.mine(&header, &target, 0, 1000)))
    });

    // Search through fixed ranges
    for range in [1000u64, 5000, 10000] {
        let target = Target::from_bytes([0x00; 32]); // Impossible target
        group.throughput(Throughput::Elements(range));
        group.bench_with_input(BenchmarkId::new("range_search", range), &range, |b, &r| {
            b.iter(|| black_box(hasher.mine(&header, &target, 0, r)))
        });
    }

    // Search with progress callback
    group.bench_function("search_with_callback", |b| {
        let target = Target::max();
        b.iter(|| hasher.mine_with_callback(&header, &target, 0, 5000, |_tried, _nonce| true))
    });

    group.finish();
}

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

    let header = [0x42u8; 80];
    let target = Target::max();

    for threads in [1, 2, 4] {
        group.bench_with_input(BenchmarkId::new("threads", threads), &threads, |b, &t| {
            let miner = ParallelMiner::new(t);
            b.iter(|| black_box(miner.mine(&header, &target, 0)))
        });
    }

    group.finish();
}

// ==================== Block Template Creation ====================

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

    for tx_count in [0, 10, 50, 100, 500] {
        group.bench_with_input(
            BenchmarkId::new("tx_count", tx_count),
            &tx_count,
            |b, &count| {
                b.iter_batched(
                    || {
                        // Setup: create transactions
                        let txs: Vec<TemplateTransaction> =
                            (0..count).map(|i| test_template_tx(i as u64)).collect();
                        txs
                    },
                    |txs| {
                        // Build template
                        let mut builder = BlockTemplateBuilder::new()
                            .version(1)
                            .add_parent(test_hash(1))
                            .timestamp(1234567890000)
                            .bits(0x1d00ffff)
                            .blue_score(1000)
                            .coinbase(test_coinbase(1000))
                            .reward(500_00000000);

                        for tx in txs {
                            builder = builder.add_transaction(tx);
                        }

                        black_box(builder.build(1))
                    },
                    criterion::BatchSize::SmallInput,
                )
            },
        );
    }

    group.finish();
}

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

    for tx_count in [0, 50, 200] {
        let template = create_test_template(tx_count);

        group.bench_with_input(
            BenchmarkId::new("header_for_mining", tx_count),
            &template,
            |b, tmpl| b.iter(|| black_box(tmpl.header_for_mining())),
        );

        group.bench_with_input(
            BenchmarkId::new("header_with_extra_nonce", tx_count),
            &template,
            |b, tmpl| b.iter(|| black_box(tmpl.header_with_extra_nonce(12345))),
        );
    }

    group.finish();
}

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

    for tx_count in [0, 50, 200] {
        let template = create_test_template(tx_count);

        group.bench_with_input(
            BenchmarkId::new("validate", tx_count),
            &template,
            |b, tmpl| b.iter(|| black_box(tmpl.validate())),
        );

        group.bench_with_input(
            BenchmarkId::new("get_target", tx_count),
            &template,
            |b, tmpl| b.iter(|| black_box(tmpl.get_target())),
        );
    }

    group.finish();
}

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

    group.bench_function("coinbase_simple", |b| {
        b.iter(|| black_box(CoinbaseBuilder::new(test_address(), 1000).build()))
    });

    group.bench_function("coinbase_with_data", |b| {
        b.iter(|| {
            black_box(
                CoinbaseBuilder::new(test_address(), 1000)
                    .extra_data(b"Synor Mining Pool v1.0.0".to_vec())
                    .reward(500_00000000)
                    .fees(1_00000000)
                    .build(),
            )
        })
    });

    // Varying extra data sizes
    for data_size in [0, 32, 100, 256] {
        let extra_data = vec![0x42u8; data_size];
        group.bench_with_input(
            BenchmarkId::new("extra_data_size", data_size),
            &extra_data,
            |b, data| {
                b.iter(|| {
                    black_box(
                        CoinbaseBuilder::new(test_address(), 1000)
                            .extra_data(data.clone())
                            .reward(500_00000000)
                            .build(),
                    )
                })
            },
        );
    }

    group.finish();
}

// ==================== Miner Configuration ====================

fn miner_config_creation(c: &mut Criterion) {
    c.bench_function("miner_config_default", |b| {
        b.iter(|| black_box(MinerConfig::default()))
    });

    c.bench_function("miner_config_solo", |b| {
        b.iter(|| black_box(MinerConfig::solo(test_address(), 4)))
    });

    c.bench_function("miner_config_pool", |b| {
        b.iter(|| black_box(MinerConfig::pool(test_address(), 4, 12345)))
    });
}

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

    for threads in [1, 2, 4, 8] {
        group.bench_with_input(
            BenchmarkId::new("block_miner", threads),
            &threads,
            |b, &t| {
                let config = MinerConfig::solo(test_address(), t);
                b.iter(|| black_box(BlockMiner::new(config.clone())))
            },
        );
    }

    group.finish();
}

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

    for threads in [1, 2, 4, 8] {
        group.bench_with_input(
            BenchmarkId::new("parallel_block_miner", threads),
            &threads,
            |b, &t| {
                let config = MinerConfig::solo(test_address(), t);
                b.iter(|| black_box(ParallelBlockMiner::new(config.clone())))
            },
        );
    }

    group.finish();
}

// ==================== Target Operations ====================

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

    // Target from bits
    for bits in [0x1d00ffff, 0x1a0fffff, 0x170fffff] {
        group.bench_with_input(
            BenchmarkId::new("from_bits", format!("{:08x}", bits)),
            &bits,
            |b, &bit| b.iter(|| black_box(Target::from_bits(bit))),
        );
    }

    // Target comparison
    let target = Target::from_bits(0x1d00ffff);
    let easy_hash = Hash256::from_bytes([0x00; 32]);
    let hard_hash = Hash256::from_bytes([0xFF; 32]);

    group.bench_function("is_met_by_pass", |b| {
        b.iter(|| black_box(target.is_met_by(&easy_hash)))
    });

    group.bench_function("is_met_by_fail", |b| {
        b.iter(|| black_box(target.is_met_by(&hard_hash)))
    });

    // Difficulty calculation
    group.bench_function("to_difficulty", |b| {
        b.iter(|| black_box(target.to_difficulty()))
    });

    group.finish();
}

// ==================== Mining Statistics ====================

fn mining_stats_operations(c: &mut Criterion) {
    use synor_mining::MiningStats;

    let mut group = c.benchmark_group("mining_stats");

    group.bench_function("stats_default", |b| {
        b.iter(|| black_box(MiningStats::default()))
    });

    group.bench_function("update_hashrate", |b| {
        b.iter_batched(
            MiningStats::default,
            |mut stats| {
                stats.update_hashrate(1000000, 1000);
                black_box(stats)
            },
            criterion::BatchSize::SmallInput,
        )
    });

    group.bench_function("record_block", |b| {
        b.iter_batched(
            || (MiningStats::default(), 1000u64),
            |(mut stats, timestamp)| {
                stats.record_block(timestamp);
                black_box(stats)
            },
            criterion::BatchSize::SmallInput,
        )
    });

    group.bench_function("formatted_hashrate", |b| {
        let mut stats = MiningStats::default();
        stats.update_hashrate(1_000_000_000, 1000); // 1 GH/s
        b.iter(|| black_box(stats.formatted_hashrate()))
    });

    group.finish();
}

// ==================== End-to-End Mining ====================

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

    // Full mining cycle: template -> hash -> check target
    group.bench_function("full_cycle", |b| {
        let template = create_test_template(50);
        let hasher = KHeavyHash::new();
        let target = template.get_target();

        b.iter(|| {
            let header = template.header_for_mining();
            let pre_hash = hasher.pre_hash(&header);
            let pow = hasher.finalize(&pre_hash, 12345);
            black_box(target.is_met_by(&pow.hash))
        })
    });

    // Mining with template rotation (simulating new templates arriving)
    group.bench_function("template_rotation_100_hashes", |b| {
        let templates: Vec<BlockTemplate> = (0..10)
            .map(|i| {
                let mut t = create_test_template(10);
                t.id = i;
                t
            })
            .collect();
        let hasher = KHeavyHash::new();

        b.iter(|| {
            let mut total_hashes = 0u64;
            for template in &templates {
                let header = template.header_for_mining();
                let pre_hash = hasher.pre_hash(&header);
                for nonce in 0..10 {
                    let _ = hasher.finalize(&pre_hash, nonce);
                    total_hashes += 1;
                }
            }
            black_box(total_hashes)
        })
    });

    group.finish();
}

// ==================== Criterion Groups ====================

criterion_group!(
    throughput_benches,
    kheavyhash_throughput_single,
    kheavyhash_sustained_rate,
);

criterion_group!(
    matrix_benches,
    matrix_initialization,
    matrix_memory_patterns,
);

criterion_group!(nonce_benches, nonce_search_rate, parallel_nonce_search,);

criterion_group!(
    template_benches,
    block_template_creation,
    template_header_creation,
    template_validation,
    coinbase_building,
);

criterion_group!(
    miner_benches,
    miner_config_creation,
    block_miner_creation,
    parallel_block_miner_creation,
);

criterion_group!(misc_benches, target_operations, mining_stats_operations,);

criterion_group!(e2e_benches, end_to_end_mining,);

criterion_main!(
    throughput_benches,
    matrix_benches,
    nonce_benches,
    template_benches,
    miner_benches,
    misc_benches,
    e2e_benches
);
