// Shared utilities for gas benchmarks across all contract packages.

use fuels::prelude::*;
use regex::Regex;
use serde::Serialize;
use std::io::Write;

// Fuel mainnet (Ignition) consensus parameters — version 7
// Source: https://github.com/FuelLabs/chain-configuration/blob/49065098b50aceaecdd11eeabacb3ef94f60dc64/upgradelog/ignition/consensus_parameters/7.json
//
// NOTE: Gas costs use the SDK default (V4) rather than mainnet's V6 because
// fuel-tx v0.56.0 predates V6. Gas measurements are therefore approximations.
// The structural parameters (gas limits, sizes, fees) match mainnet exactly.

// tx_params
pub const MAINNET_MAX_GAS_PER_TX: u64 = 30_000_000;
pub const MAINNET_MAX_TX_SIZE: u64 = 112_640;

// contract_params
pub const MAINNET_CONTRACT_MAX_SIZE: u64 = 112_640;
pub const MAINNET_MAX_STORAGE_SLOTS: u64 = 1_760;

// fee_params
pub const MAINNET_GAS_PRICE_FACTOR: u64 = 1_150_000;
pub const MAINNET_GAS_PER_BYTE: u64 = 1;

// predicate_params
pub const MAINNET_MAX_PREDICATE_LENGTH: u64 = 24_576;
pub const MAINNET_MAX_PREDICATE_DATA_LENGTH: u64 = 24_576;
pub const MAINNET_MAX_MESSAGE_DATA_LENGTH: u64 = 102_400;
pub const MAINNET_MAX_GAS_PER_PREDICATE: u64 = 1_000_000;

// block limits
pub const MAINNET_BLOCK_GAS_LIMIT: u64 = 42_000_000;

/// True if benchmarks should only be listed and not executed.
/// Configured via environment variable `BENCH_LIST_ONLY`:
///   `BENCH_LIST_ONLY=1`
pub fn bench_is_list_only() -> bool {
    std::env::var("BENCH_LIST_ONLY").as_deref() == Ok("1")
}

/// Regex for filtering benchmarks to execute.
/// Configured via environment variable `BENCH_FILTER`.
pub fn bench_filter() -> Option<Regex> {
    std::env::var("BENCH_FILTER")
        .ok()
        .map(|s| Regex::new(&s).expect("BENCH_FILTER: invalid regex"))
}

/// Runs a single named benchmark, gated by the `BENCH_LIST_ONLY` and
/// `BENCH_FILTER` environment variables. No caller-scope variables are
/// needed — the macro reads the env vars itself on every invocation
/// (negligible cost compared to actual benchmark execution).
#[macro_export]
macro_rules! run_bench {
    ($desc:expr, $body:block) => {{
        let desc: &str = $desc;
        if $crate::bench::bench_is_list_only() {
            println!("{desc}");
        } else if $crate::bench::bench_filter()
            .as_ref()
            .map_or(true, |re| re.is_match(desc))
        {
            println!("Running {desc}...");
            let __t0 = std::time::Instant::now();
            $body
            println!("Duration {:.2?}", __t0.elapsed());
        }
    }};
}

/// A single benchmark result.
#[derive(Serialize, Clone, Debug)]
pub struct BenchmarkEntry {
    pub name: String,
    pub unit: String,
    pub value: f64,
}

/// Returns the total gas consumed by a contract call response.
pub fn extract_gas_used<T>(
    response: &fuels::programs::responses::CallResponse<T>,
) -> u64 {
    response.tx_status.total_gas
}

/// Applies Fuel mainnet (Ignition v7) consensus parameters to `chain_config`
/// in-place. Call this inside `setup_wallets_mainnet` before launching the
/// provider so benchmarks reflect real on-chain constraints.
pub fn apply_mainnet_consensus_params(chain_config: &mut ChainConfig) {
    let cp = &mut chain_config.consensus_parameters;

    let mut tx_params = cp.tx_params().with_max_gas_per_tx(MAINNET_MAX_GAS_PER_TX);
    tx_params.set_max_size(MAINNET_MAX_TX_SIZE);
    cp.set_tx_params(tx_params);

    let contract_params = cp
        .contract_params()
        .with_contract_max_size(MAINNET_CONTRACT_MAX_SIZE)
        .with_max_storage_slots(MAINNET_MAX_STORAGE_SLOTS);
    cp.set_contract_params(contract_params);

    let fee_params = cp
        .fee_params()
        .with_gas_price_factor(MAINNET_GAS_PRICE_FACTOR)
        .with_gas_per_byte(MAINNET_GAS_PER_BYTE);
    cp.set_fee_params(fee_params);

    let predicate_params = cp
        .predicate_params()
        .with_max_predicate_length(MAINNET_MAX_PREDICATE_LENGTH)
        .with_max_predicate_data_length(MAINNET_MAX_PREDICATE_DATA_LENGTH)
        .with_max_message_data_length(MAINNET_MAX_MESSAGE_DATA_LENGTH)
        .with_max_gas_per_predicate(MAINNET_MAX_GAS_PER_PREDICATE);
    cp.set_predicate_params(predicate_params);

    cp.set_block_gas_limit(MAINNET_BLOCK_GAS_LIMIT);
}

/// Writes benchmark results to the JSON output files specified by the
/// `BENCH_OUTPUT_GAS` and `BENCH_OUTPUT_MATCHES` environment variables
/// (defaulting to `benchmark-results-{gas|matches}.json` in the current
/// directory).
///
/// Pass all smaller-is-better results (gas, gas/match) as `gas` and all
/// bigger-is-better results (match counts) as `matches`. Either slice may
/// be empty.
pub fn write_bench_results(gas: &[BenchmarkEntry], matches: &[BenchmarkEntry]) {
    let gas_path = std::env::var("BENCH_OUTPUT_GAS")
        .unwrap_or_else(|_| "benchmark-results-gas.json".to_string());
    let mut f = std::fs::File::create(&gas_path).unwrap();
    f.write_all(serde_json::to_string_pretty(gas).unwrap().as_bytes())
        .unwrap();

    let matches_path = std::env::var("BENCH_OUTPUT_MATCHES")
        .unwrap_or_else(|_| "benchmark-results-matches.json".to_string());
    let mut f = std::fs::File::create(&matches_path).unwrap();
    f.write_all(serde_json::to_string_pretty(matches).unwrap().as_bytes())
        .unwrap();

    println!("\nGas results written to {gas_path}");
    println!("Matches results written to {matches_path}");
}
