//! Gas metering for contract execution.
//!
//! Gas provides predictable execution costs and prevents infinite loops.
//! Each operation has an associated gas cost.

use std::sync::atomic::{AtomicU64, Ordering};

/// Gas unit type.
pub type Gas = u64;

/// Gas configuration for different operations.
#[derive(Clone, Debug)]
pub struct GasConfig {
    /// Base cost per instruction.
    pub instruction_base: Gas,
    /// Cost per byte of memory allocation.
    pub memory_byte: Gas,
    /// Cost per byte of storage read.
    pub storage_read_byte: Gas,
    /// Cost per byte of storage write.
    pub storage_write_byte: Gas,
    /// Cost per byte of log data.
    pub log_byte: Gas,
    /// Cost per log topic.
    pub log_topic: Gas,
    /// Cost for contract call.
    pub call_base: Gas,
    /// Cost per byte of call data.
    pub call_data_byte: Gas,
    /// Cost for contract creation.
    pub create_base: Gas,
    /// Cost per byte of contract code.
    pub create_byte: Gas,
    /// Cost for hash operations.
    pub hash_base: Gas,
    /// Cost per byte hashed.
    pub hash_byte: Gas,
    /// Cost for signature verification.
    pub signature_verify: Gas,
    /// Cost for address computation.
    pub address_compute: Gas,
    /// Refund for storage deletion.
    pub storage_delete_refund: Gas,
}

impl Default for GasConfig {
    fn default() -> Self {
        GasConfig {
            instruction_base: 1,
            memory_byte: 3,
            storage_read_byte: 200,
            storage_write_byte: 5000,
            log_byte: 8,
            log_topic: 375,
            call_base: 700,
            call_data_byte: 3,
            create_base: 32000,
            create_byte: 200,
            hash_base: 30,
            hash_byte: 6,
            signature_verify: 3000,
            address_compute: 50,
            storage_delete_refund: 15000,
        }
    }
}

impl GasConfig {
    /// Creates a config for testing (cheaper operations).
    pub fn testing() -> Self {
        GasConfig {
            instruction_base: 1,
            memory_byte: 1,
            storage_read_byte: 10,
            storage_write_byte: 100,
            log_byte: 1,
            log_topic: 10,
            call_base: 100,
            call_data_byte: 1,
            create_base: 1000,
            create_byte: 10,
            hash_base: 10,
            hash_byte: 1,
            signature_verify: 100,
            address_compute: 10,
            storage_delete_refund: 500,
        }
    }

    /// Calculates gas for memory allocation.
    pub fn memory_gas(&self, bytes: usize) -> Gas {
        self.memory_byte * bytes as Gas
    }

    /// Calculates gas for storage read.
    pub fn storage_read_gas(&self, bytes: usize) -> Gas {
        self.storage_read_byte * bytes as Gas
    }

    /// Calculates gas for storage write.
    pub fn storage_write_gas(&self, bytes: usize) -> Gas {
        self.storage_write_byte * bytes as Gas
    }

    /// Calculates gas for hashing.
    pub fn hash_gas(&self, bytes: usize) -> Gas {
        self.hash_base + self.hash_byte * bytes as Gas
    }

    /// Calculates gas for logging.
    pub fn log_gas(&self, data_bytes: usize, topics: usize) -> Gas {
        self.log_byte * data_bytes as Gas + self.log_topic * topics as Gas
    }

    /// Calculates gas for contract call.
    pub fn call_gas(&self, data_bytes: usize) -> Gas {
        self.call_base + self.call_data_byte * data_bytes as Gas
    }

    /// Calculates gas for contract creation.
    pub fn create_gas(&self, code_bytes: usize) -> Gas {
        self.create_base + self.create_byte * code_bytes as Gas
    }
}

/// Gas meter for tracking consumption.
#[derive(Debug)]
pub struct GasMeter {
    /// Gas limit for this execution.
    limit: Gas,
    /// Gas consumed so far.
    consumed: AtomicU64,
    /// Refund accumulated.
    refund: AtomicU64,
    /// Gas configuration.
    config: GasConfig,
}

impl GasMeter {
    /// Creates a new gas meter with the given limit.
    pub fn new(limit: Gas) -> Self {
        GasMeter {
            limit,
            consumed: AtomicU64::new(0),
            refund: AtomicU64::new(0),
            config: GasConfig::default(),
        }
    }

    /// Creates with custom configuration.
    pub fn with_config(limit: Gas, config: GasConfig) -> Self {
        GasMeter {
            limit,
            consumed: AtomicU64::new(0),
            refund: AtomicU64::new(0),
            config,
        }
    }

    /// Returns the gas limit.
    pub fn limit(&self) -> Gas {
        self.limit
    }

    /// Returns gas consumed so far.
    pub fn consumed(&self) -> Gas {
        self.consumed.load(Ordering::Relaxed)
    }

    /// Returns remaining gas.
    pub fn remaining(&self) -> Gas {
        self.limit.saturating_sub(self.consumed())
    }

    /// Returns accumulated refund.
    pub fn refund(&self) -> Gas {
        self.refund.load(Ordering::Relaxed)
    }

    /// Consumes gas, returning error if out of gas.
    pub fn consume(&self, amount: Gas) -> Result<(), GasError> {
        let current = self.consumed.fetch_add(amount, Ordering::Relaxed);
        let new_total = current + amount;

        if new_total > self.limit {
            // Rollback
            self.consumed.fetch_sub(amount, Ordering::Relaxed);
            return Err(GasError::OutOfGas {
                required: amount,
                remaining: self.limit.saturating_sub(current),
            });
        }

        Ok(())
    }

    /// Consumes gas without checking limit (for internal use).
    pub fn consume_unchecked(&self, amount: Gas) {
        self.consumed.fetch_add(amount, Ordering::Relaxed);
    }

    /// Adds a refund.
    pub fn add_refund(&self, amount: Gas) {
        self.refund.fetch_add(amount, Ordering::Relaxed);
    }

    /// Checks if there's enough gas without consuming.
    pub fn has_gas(&self, amount: Gas) -> bool {
        self.remaining() >= amount
    }

    /// Charges for memory allocation.
    pub fn charge_memory(&self, bytes: usize) -> Result<(), GasError> {
        self.consume(self.config.memory_gas(bytes))
    }

    /// Charges for storage read.
    pub fn charge_storage_read(&self, bytes: usize) -> Result<(), GasError> {
        self.consume(self.config.storage_read_gas(bytes))
    }

    /// Charges for storage write.
    pub fn charge_storage_write(&self, bytes: usize) -> Result<(), GasError> {
        self.consume(self.config.storage_write_gas(bytes))
    }

    /// Charges for hashing.
    pub fn charge_hash(&self, bytes: usize) -> Result<(), GasError> {
        self.consume(self.config.hash_gas(bytes))
    }

    /// Charges for logging.
    pub fn charge_log(&self, data_bytes: usize, topics: usize) -> Result<(), GasError> {
        self.consume(self.config.log_gas(data_bytes, topics))
    }

    /// Charges for contract call.
    pub fn charge_call(&self, data_bytes: usize) -> Result<(), GasError> {
        self.consume(self.config.call_gas(data_bytes))
    }

    /// Charges for contract creation.
    pub fn charge_create(&self, code_bytes: usize) -> Result<(), GasError> {
        self.consume(self.config.create_gas(code_bytes))
    }

    /// Charges for signature verification.
    pub fn charge_signature(&self) -> Result<(), GasError> {
        self.consume(self.config.signature_verify)
    }

    /// Applies refunds and returns final gas used.
    pub fn finalize(&self) -> Gas {
        let consumed = self.consumed();
        let refund = self.refund();

        // Cap refund at 50% of consumed
        let max_refund = consumed / 2;
        let actual_refund = std::cmp::min(refund, max_refund);

        consumed.saturating_sub(actual_refund)
    }

    /// Returns gas configuration.
    pub fn config(&self) -> &GasConfig {
        &self.config
    }
}

/// Gas-related errors.
#[derive(Debug, Clone, thiserror::Error)]
pub enum GasError {
    /// Out of gas.
    #[error("Out of gas: required {required}, remaining {remaining}")]
    OutOfGas { required: Gas, remaining: Gas },
}

/// Gas estimation result.
#[derive(Clone, Debug)]
pub struct GasEstimate {
    /// Estimated gas for successful execution.
    pub gas_used: Gas,
    /// Estimated refund.
    pub refund: Gas,
    /// Whether estimation succeeded.
    pub success: bool,
    /// Error message if failed.
    pub error: Option<String>,
}

impl GasEstimate {
    /// Creates a successful estimate.
    pub fn success(gas_used: Gas, refund: Gas) -> Self {
        GasEstimate {
            gas_used,
            refund,
            success: true,
            error: None,
        }
    }

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

    /// Net gas after refunds.
    pub fn net_gas(&self) -> Gas {
        self.gas_used.saturating_sub(self.refund / 2)
    }
}

/// Tracks gas across nested calls.
#[derive(Debug)]
pub struct GasStack {
    /// Stack of gas meters.
    meters: Vec<GasMeter>,
}

impl GasStack {
    /// Creates a new gas stack with initial gas.
    pub fn new(initial_gas: Gas) -> Self {
        GasStack {
            meters: vec![GasMeter::new(initial_gas)],
        }
    }

    /// Pushes a new gas context for a nested call.
    pub fn push(&mut self, gas_limit: Gas) {
        self.meters.push(GasMeter::new(gas_limit));
    }

    /// Pops a gas context after a nested call.
    pub fn pop(&mut self) -> Option<Gas> {
        if self.meters.len() > 1 {
            self.meters.pop().map(|m| m.consumed())
        } else {
            None
        }
    }

    /// Gets the current gas meter.
    pub fn current(&self) -> &GasMeter {
        self.meters.last().expect("Gas stack is empty")
    }

    /// Consumes gas from current context.
    pub fn consume(&self, amount: Gas) -> Result<(), GasError> {
        self.current().consume(amount)
    }

    /// Total gas consumed across all contexts.
    pub fn total_consumed(&self) -> Gas {
        self.meters.iter().map(|m| m.consumed()).sum()
    }

    /// Depth of the call stack.
    pub fn depth(&self) -> usize {
        self.meters.len()
    }
}

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

    #[test]
    fn test_gas_meter() {
        let meter = GasMeter::new(1000);

        assert_eq!(meter.limit(), 1000);
        assert_eq!(meter.consumed(), 0);
        assert_eq!(meter.remaining(), 1000);

        assert!(meter.consume(500).is_ok());
        assert_eq!(meter.consumed(), 500);
        assert_eq!(meter.remaining(), 500);

        assert!(meter.consume(500).is_ok());
        assert_eq!(meter.remaining(), 0);

        // Out of gas
        assert!(meter.consume(1).is_err());
    }

    #[test]
    fn test_gas_refund() {
        let meter = GasMeter::new(1000);
        meter.consume(600).unwrap();
        meter.add_refund(500);

        // Refund capped at 50% of consumed
        let final_gas = meter.finalize();
        assert_eq!(final_gas, 300); // 600 - min(500, 300)
    }

    #[test]
    fn test_gas_config() {
        let config = GasConfig::default();

        assert!(config.memory_gas(100) > 0);
        assert!(config.storage_write_gas(32) > config.storage_read_gas(32));
        assert!(config.create_gas(1000) > config.call_gas(1000));
    }

    #[test]
    fn test_gas_stack() {
        let mut stack = GasStack::new(10000);

        stack.current().consume(1000).unwrap();
        assert_eq!(stack.depth(), 1);

        stack.push(5000);
        stack.current().consume(2000).unwrap();
        assert_eq!(stack.depth(), 2);

        let consumed = stack.pop();
        assert_eq!(consumed, Some(2000));
        assert_eq!(stack.depth(), 1);
    }
}
