//! Execution context for contract calls.
//!
//! The context provides contracts with information about the current
//! execution environment (caller, value, block info, etc.).

use synor_types::{Address, Hash256, Network};

use crate::gas::GasMeter;
use crate::storage::{ContractStorage, MemoryStorage};
use crate::ContractId;

/// Block information available to contracts.
#[derive(Clone, Debug)]
pub struct BlockInfo {
    /// Block height.
    pub height: u64,
    /// Block timestamp (Unix milliseconds).
    pub timestamp: u64,
    /// Block hash (of selected parent).
    pub hash: Hash256,
    /// Blue score.
    pub blue_score: u64,
    /// DAA score (for difficulty).
    pub daa_score: u64,
    /// Coinbase address.
    pub coinbase: Address,
}

impl Default for BlockInfo {
    fn default() -> Self {
        BlockInfo {
            height: 0,
            timestamp: 0,
            hash: Hash256::default(),
            blue_score: 0,
            daa_score: 0,
            coinbase: Address::from_ed25519_pubkey(Network::Mainnet, &[0; 32]),
        }
    }
}

/// Transaction information.
#[derive(Clone, Debug)]
pub struct TransactionInfo {
    /// Transaction hash.
    pub hash: Hash256,
    /// Transaction origin (original sender).
    pub origin: Address,
    /// Gas price.
    pub gas_price: u64,
    /// Gas limit.
    pub gas_limit: u64,
}

impl Default for TransactionInfo {
    fn default() -> Self {
        TransactionInfo {
            hash: Hash256::default(),
            origin: Address::from_ed25519_pubkey(Network::Mainnet, &[0; 32]),
            gas_price: 1,
            gas_limit: 10_000_000,
        }
    }
}

/// Call-specific context.
#[derive(Clone, Debug)]
pub struct CallContext {
    /// The contract being called.
    pub contract: ContractId,
    /// The caller (previous contract or EOA).
    pub caller: Address,
    /// Value sent with the call.
    pub value: u64,
    /// Input data (method + args).
    pub input: Vec<u8>,
    /// Whether this is a delegate call.
    pub is_delegate: bool,
    /// Whether this is a static call (read-only).
    pub is_static: bool,
    /// Call depth.
    pub depth: u32,
}

impl CallContext {
    /// Creates a new call context.
    pub fn new(contract: ContractId, caller: Address, value: u64, input: Vec<u8>) -> Self {
        CallContext {
            contract,
            caller,
            value,
            input,
            is_delegate: false,
            is_static: false,
            depth: 0,
        }
    }

    /// Creates a static (read-only) call context.
    pub fn static_call(contract: ContractId, caller: Address, input: Vec<u8>) -> Self {
        CallContext {
            contract,
            caller,
            value: 0,
            input,
            is_delegate: false,
            is_static: true,
            depth: 0,
        }
    }

    /// Creates a delegate call context.
    pub fn delegate_call(
        contract: ContractId,
        caller: Address,
        value: u64,
        input: Vec<u8>,
    ) -> Self {
        CallContext {
            contract,
            caller,
            value,
            input,
            is_delegate: true,
            is_static: false,
            depth: 0,
        }
    }

    /// Increments call depth for nested calls.
    pub fn nested(&self, contract: ContractId, value: u64, input: Vec<u8>) -> Self {
        CallContext {
            contract,
            // Current contract becomes caller - use contract hash as "address"
            caller: Address::from_ed25519_pubkey(Network::Mainnet, self.contract.as_bytes()),
            value,
            input,
            is_delegate: false,
            is_static: self.is_static, // Inherit static flag
            depth: self.depth + 1,
        }
    }
}

/// Full execution context.
pub struct ExecutionContext<S: ContractStorage = MemoryStorage> {
    /// Block information.
    pub block: BlockInfo,
    /// Transaction information.
    pub tx: TransactionInfo,
    /// Current call context.
    pub call: CallContext,
    /// Gas meter.
    pub gas: GasMeter,
    /// Contract storage.
    pub storage: S,
    /// Return data from last call.
    pub return_data: Vec<u8>,
    /// Logs accumulated.
    pub logs: Vec<crate::ContractLog>,
    /// Chain ID.
    pub chain_id: u64,
}

impl<S: ContractStorage> ExecutionContext<S> {
    /// Creates a new execution context.
    pub fn new(
        block: BlockInfo,
        tx: TransactionInfo,
        call: CallContext,
        gas_limit: u64,
        storage: S,
        chain_id: u64,
    ) -> Self {
        ExecutionContext {
            block,
            tx,
            call,
            gas: GasMeter::new(gas_limit),
            storage,
            return_data: Vec::new(),
            logs: Vec::new(),
            chain_id,
        }
    }

    /// Creates a context for testing.
    pub fn testing(call: CallContext) -> ExecutionContext<MemoryStorage> {
        ExecutionContext {
            block: BlockInfo::default(),
            tx: TransactionInfo::default(),
            call,
            gas: GasMeter::new(10_000_000),
            storage: MemoryStorage::new(),
            return_data: Vec::new(),
            logs: Vec::new(),
            chain_id: 1,
        }
    }

    /// Gets the current contract address as Address.
    pub fn address(&self) -> Address {
        // Contract address derived from contract ID hash
        Address::from_ed25519_pubkey(Network::Mainnet, self.call.contract.as_bytes())
    }

    /// Gets the caller address.
    pub fn caller(&self) -> Address {
        self.call.caller.clone()
    }

    /// Gets the transaction origin.
    pub fn origin(&self) -> Address {
        self.tx.origin.clone()
    }

    /// Gets the call value.
    pub fn value(&self) -> u64 {
        self.call.value
    }

    /// Gets the call input data.
    pub fn calldata(&self) -> &[u8] {
        &self.call.input
    }

    /// Gets the call data size.
    pub fn calldatasize(&self) -> usize {
        self.call.input.len()
    }

    /// Gets remaining gas.
    pub fn gas_remaining(&self) -> u64 {
        self.gas.remaining()
    }

    /// Gets the block timestamp.
    pub fn timestamp(&self) -> u64 {
        self.block.timestamp
    }

    /// Gets the block height.
    pub fn block_height(&self) -> u64 {
        self.block.height
    }

    /// Gets the block hash.
    pub fn block_hash(&self) -> Hash256 {
        self.block.hash
    }

    /// Checks if this is a static call.
    pub fn is_static(&self) -> bool {
        self.call.is_static
    }

    /// Checks if state modifications are allowed.
    pub fn can_modify_state(&self) -> bool {
        !self.call.is_static
    }

    /// Sets the return data.
    pub fn set_return_data(&mut self, data: Vec<u8>) {
        self.return_data = data;
    }

    /// Gets the last return data.
    pub fn get_return_data(&self) -> &[u8] {
        &self.return_data
    }

    /// Emits a log.
    pub fn emit_log(&mut self, topics: Vec<Hash256>, data: Vec<u8>) -> Result<(), crate::VmError> {
        if self.is_static() {
            return Err(crate::VmError::ExecutionError(
                "Cannot emit logs in static call".into(),
            ));
        }

        // Charge gas
        self.gas
            .charge_log(data.len(), topics.len())
            .map_err(|_e| crate::VmError::OutOfGas {
                used: self.gas.consumed(),
                limit: self.gas.limit(),
            })?;

        self.logs.push(crate::ContractLog {
            contract: self.call.contract,
            topics,
            data,
        });

        Ok(())
    }

    /// Creates a nested context for a sub-call.
    pub fn nested_call(
        &self,
        target: ContractId,
        value: u64,
        input: Vec<u8>,
        gas_limit: u64,
    ) -> Result<ExecutionContext<MemoryStorage>, crate::VmError> {
        if self.call.depth >= crate::MAX_CALL_DEPTH {
            return Err(crate::VmError::CallDepthExceeded(self.call.depth + 1));
        }

        let nested_call = self.call.nested(target, value, input);

        Ok(ExecutionContext {
            block: self.block.clone(),
            tx: self.tx.clone(),
            call: nested_call,
            gas: GasMeter::new(gas_limit),
            storage: MemoryStorage::new(), // Nested calls get fresh storage view
            return_data: Vec::new(),
            logs: Vec::new(),
            chain_id: self.chain_id,
        })
    }
}

/// Builder for execution contexts.
pub struct ContextBuilder<S: ContractStorage = MemoryStorage> {
    block: BlockInfo,
    tx: TransactionInfo,
    call: Option<CallContext>,
    gas_limit: u64,
    storage: Option<S>,
    chain_id: u64,
}

impl<S: ContractStorage> ContextBuilder<S> {
    /// Creates a new context builder.
    pub fn new() -> Self {
        ContextBuilder {
            block: BlockInfo::default(),
            tx: TransactionInfo::default(),
            call: None,
            gas_limit: 10_000_000,
            storage: None,
            chain_id: 1,
        }
    }

    /// Sets block info.
    pub fn block(mut self, block: BlockInfo) -> Self {
        self.block = block;
        self
    }

    /// Sets transaction info.
    pub fn transaction(mut self, tx: TransactionInfo) -> Self {
        self.tx = tx;
        self
    }

    /// Sets call context.
    pub fn call(mut self, call: CallContext) -> Self {
        self.call = Some(call);
        self
    }

    /// Sets gas limit.
    pub fn gas_limit(mut self, limit: u64) -> Self {
        self.gas_limit = limit;
        self
    }

    /// Sets storage.
    pub fn storage(mut self, storage: S) -> Self {
        self.storage = Some(storage);
        self
    }

    /// Sets chain ID.
    pub fn chain_id(mut self, chain_id: u64) -> Self {
        self.chain_id = chain_id;
        self
    }

    /// Builds the context.
    pub fn build(self) -> Result<ExecutionContext<S>, &'static str> {
        let call = self.call.ok_or("Call context is required")?;
        let storage = self.storage.ok_or("Storage is required")?;

        Ok(ExecutionContext::new(
            self.block,
            self.tx,
            call,
            self.gas_limit,
            storage,
            self.chain_id,
        ))
    }
}

impl Default for ContextBuilder<MemoryStorage> {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::storage::MemoryStorage;
    use synor_types::Network;

    fn test_contract_id() -> ContractId {
        ContractId::from_bytes([0x42; 32])
    }

    fn test_address() -> Address {
        Address::from_ed25519_pubkey(Network::Mainnet, &[0x11; 32])
    }

    #[test]
    fn test_call_context() {
        let contract = test_contract_id();
        let caller = test_address();

        let ctx = CallContext::new(contract, caller, 1000, vec![1, 2, 3]);

        assert_eq!(ctx.value, 1000);
        assert_eq!(ctx.depth, 0);
        assert!(!ctx.is_static);
    }

    #[test]
    fn test_nested_call() {
        let contract = test_contract_id();
        let caller = test_address();

        let ctx = CallContext::new(contract, caller, 1000, vec![]);
        let nested = ctx.nested(ContractId::from_bytes([0x99; 32]), 500, vec![4, 5, 6]);

        assert_eq!(nested.depth, 1);
        assert_eq!(nested.value, 500);
    }

    #[test]
    fn test_execution_context() {
        let contract = test_contract_id();
        let caller = test_address();
        let call = CallContext::new(contract, caller, 1000, vec![1, 2, 3]);

        let ctx = ExecutionContext::<MemoryStorage>::testing(call);

        assert_eq!(ctx.value(), 1000);
        assert_eq!(ctx.calldatasize(), 3);
        assert!(ctx.gas_remaining() > 0);
    }

    #[test]
    fn test_static_call() {
        let contract = test_contract_id();
        let caller = test_address();
        let call = CallContext::static_call(contract, caller, vec![]);

        let ctx = ExecutionContext::<MemoryStorage>::testing(call);

        assert!(ctx.is_static());
        assert!(!ctx.can_modify_state());
    }
}
