//! Gas estimation for smart contract execution.
//!
//! Provides tools to estimate gas usage before executing transactions,
//! helping users set appropriate gas limits.

use crate::context::{BlockInfo, CallContext, ExecutionContext, TransactionInfo};
use crate::engine::{ContractModule, VmEngine};
use crate::storage::MemoryStorage;
use crate::VmError;
use synor_types::Address;

/// Gas estimation result.
#[derive(Clone, Debug)]
pub struct GasEstimate {
    /// Estimated gas usage.
    pub gas_used: u64,
    /// Gas used with safety margin (1.2x).
    pub gas_limit_recommended: u64,
    /// Whether the execution succeeded.
    pub success: bool,
    /// Error message if failed.
    pub error: Option<String>,
    /// Return data from the call.
    pub return_data: Vec<u8>,
}

impl GasEstimate {
    /// Creates a successful estimate.
    pub fn success(gas_used: u64, return_data: Vec<u8>) -> Self {
        let recommended = (gas_used as f64 * 1.2).ceil() as u64;
        GasEstimate {
            gas_used,
            gas_limit_recommended: recommended,
            success: true,
            error: None,
            return_data,
        }
    }

    /// Creates a failed estimate.
    pub fn failure(gas_used: u64, error: String) -> Self {
        GasEstimate {
            gas_used,
            gas_limit_recommended: 0,
            success: false,
            error: Some(error),
            return_data: Vec::new(),
        }
    }
}

/// Gas estimator for contract calls.
pub struct GasEstimator {
    /// VM engine.
    engine: VmEngine,
    /// Maximum gas for estimation.
    max_gas: u64,
}

impl GasEstimator {
    /// Creates a new gas estimator.
    pub fn new() -> Result<Self, VmError> {
        Ok(GasEstimator {
            engine: VmEngine::new()?,
            max_gas: 10_000_000, // 10M gas limit for estimation
        })
    }

    /// Creates with custom max gas.
    pub fn with_max_gas(max_gas: u64) -> Result<Self, VmError> {
        Ok(GasEstimator {
            engine: VmEngine::new()?,
            max_gas,
        })
    }

    /// Estimates gas for deploying a contract.
    pub fn estimate_deploy(
        &self,
        bytecode: &[u8],
        init_params: &[u8],
        deployer: Address,
    ) -> GasEstimate {
        // Compile the contract
        let module = match self.engine.compile(bytecode.to_vec()) {
            Ok(m) => m,
            Err(e) => return GasEstimate::failure(0, e.to_string()),
        };

        // Create execution context
        let contract_id = module.id;
        let call = CallContext::new(contract_id, deployer.clone(), 0, init_params.to_vec());
        let context = ExecutionContext::<MemoryStorage>::new(
            BlockInfo::default(),
            TransactionInfo::default(),
            call,
            self.max_gas,
            MemoryStorage::new(),
            1, // chain_id
        );

        // Execute init
        match self
            .engine
            .execute(&module, "__synor_init", init_params, context, self.max_gas)
        {
            Ok(result) => GasEstimate::success(result.gas_used, result.return_data),
            Err(e) => {
                // Try to extract gas used from error
                let gas = match &e {
                    VmError::OutOfGas { used, .. } => *used,
                    _ => 0,
                };
                GasEstimate::failure(gas, e.to_string())
            }
        }
    }

    /// Estimates gas for calling a contract method.
    pub fn estimate_call(
        &self,
        module: &ContractModule,
        method: &str,
        params: &[u8],
        caller: Address,
        value: u64,
        storage: MemoryStorage,
    ) -> GasEstimate {
        // Build call data: 4-byte selector + params
        let selector = synor_sdk_method_selector(method);
        let mut call_data = Vec::with_capacity(4 + params.len());
        call_data.extend_from_slice(&selector);
        call_data.extend_from_slice(params);

        // Create execution context
        let call = CallContext::new(module.id, caller.clone(), value, call_data.clone());
        let context = ExecutionContext::<MemoryStorage>::new(
            BlockInfo::default(),
            TransactionInfo::default(),
            call,
            self.max_gas,
            storage,
            1, // chain_id
        );

        // Execute call
        match self
            .engine
            .execute(module, "__synor_call", &call_data, context, self.max_gas)
        {
            Ok(result) => GasEstimate::success(result.gas_used, result.return_data),
            Err(e) => {
                let gas = match &e {
                    VmError::OutOfGas { used, .. } => *used,
                    _ => 0,
                };
                GasEstimate::failure(gas, e.to_string())
            }
        }
    }

    /// Estimates gas using binary search for more accuracy.
    ///
    /// This runs the call multiple times with different gas limits
    /// to find the minimum required gas.
    pub fn estimate_call_precise(
        &self,
        module: &ContractModule,
        method: &str,
        params: &[u8],
        caller: Address,
        value: u64,
        storage: MemoryStorage,
    ) -> GasEstimate {
        // First, try with max gas to see if it succeeds
        let initial = self.estimate_call(
            module,
            method,
            params,
            caller.clone(),
            value,
            storage.clone(),
        );

        if !initial.success {
            return initial;
        }

        let gas_used = initial.gas_used;

        // Binary search between gas_used and gas_used * 2 to find minimum
        let mut low = gas_used;
        let mut high = gas_used.saturating_mul(2).min(self.max_gas);

        while low < high {
            let mid = (low + high) / 2;

            let selector = synor_sdk_method_selector(method);
            let mut call_data = Vec::with_capacity(4 + params.len());
            call_data.extend_from_slice(&selector);
            call_data.extend_from_slice(params);

            let call = CallContext::new(module.id, caller.clone(), value, call_data.clone());
            let context = ExecutionContext::<MemoryStorage>::new(
                BlockInfo::default(),
                TransactionInfo::default(),
                call,
                mid,
                storage.clone(),
                1, // chain_id
            );

            match self
                .engine
                .execute(module, "__synor_call", &call_data, context, mid)
            {
                Ok(_) => high = mid,
                Err(_) => low = mid + 1,
            }
        }

        GasEstimate::success(low, initial.return_data)
    }

    /// Gets the underlying VM engine.
    pub fn engine(&self) -> &VmEngine {
        &self.engine
    }

    /// Compiles a contract module.
    pub fn compile(&self, bytecode: Vec<u8>) -> Result<ContractModule, VmError> {
        self.engine.compile(bytecode)
    }
}

impl Default for GasEstimator {
    fn default() -> Self {
        Self::new().expect("Failed to create gas estimator")
    }
}

/// Computes method selector (first 4 bytes of blake3 hash).
fn synor_sdk_method_selector(method: &str) -> [u8; 4] {
    let hash = blake3::hash(method.as_bytes());
    let bytes = hash.as_bytes();
    [bytes[0], bytes[1], bytes[2], bytes[3]]
}

/// Gas costs for common operations.
pub mod costs {
    /// Base cost for any transaction.
    pub const BASE_TX: u64 = 21_000;

    /// Cost per byte of call data.
    pub const CALLDATA_BYTE: u64 = 16;

    /// Cost per zero byte of call data.
    pub const CALLDATA_ZERO_BYTE: u64 = 4;

    /// Cost for contract deployment base.
    pub const CREATE_BASE: u64 = 32_000;

    /// Cost per byte of deployed code.
    pub const CREATE_BYTE: u64 = 200;

    /// Cost for storage write (new value).
    pub const STORAGE_SET: u64 = 20_000;

    /// Cost for storage write (update).
    pub const STORAGE_UPDATE: u64 = 5_000;

    /// Cost for storage read.
    pub const STORAGE_GET: u64 = 200;

    /// Cost for storage delete (refund).
    pub const STORAGE_REFUND: u64 = 15_000;

    /// Cost for SHA3/Keccak256.
    pub const SHA3_BASE: u64 = 30;

    /// Cost per word for SHA3.
    pub const SHA3_WORD: u64 = 6;

    /// Cost for Blake3 hash.
    pub const BLAKE3_BASE: u64 = 20;

    /// Cost per word for Blake3.
    pub const BLAKE3_WORD: u64 = 4;

    /// Cost for Ed25519 signature verification.
    pub const ED25519_VERIFY: u64 = 3_000;

    /// Cost for Dilithium signature verification.
    pub const DILITHIUM_VERIFY: u64 = 5_000;

    /// Cost for memory expansion per page (64KB).
    pub const MEMORY_PAGE: u64 = 512;

    /// Cost for event emission base.
    pub const LOG_BASE: u64 = 375;

    /// Cost per topic for event emission.
    pub const LOG_TOPIC: u64 = 375;

    /// Cost per byte for event data.
    pub const LOG_DATA_BYTE: u64 = 8;

    /// Cost for cross-contract call base.
    pub const CALL_BASE: u64 = 700;

    /// Cost for delegatecall base.
    pub const DELEGATECALL_BASE: u64 = 700;

    /// Estimates cost for calldata.
    pub fn calldata_cost(data: &[u8]) -> u64 {
        let zero_bytes = data.iter().filter(|&&b| b == 0).count() as u64;
        let non_zero_bytes = data.len() as u64 - zero_bytes;
        zero_bytes * CALLDATA_ZERO_BYTE + non_zero_bytes * CALLDATA_BYTE
    }

    /// Estimates cost for contract deployment.
    pub fn deploy_cost(bytecode: &[u8]) -> u64 {
        CREATE_BASE + (bytecode.len() as u64 * CREATE_BYTE) + calldata_cost(bytecode)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_gas_estimate_success() {
        let estimate = GasEstimate::success(50_000, vec![1, 2, 3]);
        assert!(estimate.success);
        assert_eq!(estimate.gas_used, 50_000);
        assert_eq!(estimate.gas_limit_recommended, 60_000); // 1.2x
        assert!(estimate.error.is_none());
    }

    #[test]
    fn test_gas_estimate_failure() {
        let estimate = GasEstimate::failure(10_000, "out of gas".to_string());
        assert!(!estimate.success);
        assert_eq!(estimate.gas_used, 10_000);
        assert_eq!(estimate.gas_limit_recommended, 0);
        assert!(estimate.error.is_some());
    }

    #[test]
    fn test_calldata_cost() {
        let data = [0u8, 1, 0, 2, 0, 3]; // 3 zeros, 3 non-zeros
        let cost = costs::calldata_cost(&data);
        assert_eq!(
            cost,
            3 * costs::CALLDATA_ZERO_BYTE + 3 * costs::CALLDATA_BYTE
        );
    }

    #[test]
    fn test_method_selector() {
        let sel1 = synor_sdk_method_selector("transfer");
        let sel2 = synor_sdk_method_selector("transfer");
        let sel3 = synor_sdk_method_selector("mint");

        assert_eq!(sel1, sel2);
        assert_ne!(sel1, sel3);
    }
}
