//! Host functions exposed to WASM contracts.
//!
//! These functions provide contracts with access to blockchain state,
//! cryptographic operations, and other capabilities.

use synor_types::{Address, Hash256};

use crate::context::ExecutionContext;
use crate::storage::{ContractStorage, StorageKey, StorageValue};
use crate::{ContractId, VmError};

/// Host functions available to contracts.
pub struct HostFunctions;

impl HostFunctions {
    // ==================== Storage Operations ====================

    /// Reads a value from contract storage.
    pub fn storage_read<S: ContractStorage>(
        ctx: &mut ExecutionContext<S>,
        key: &[u8; 32],
    ) -> Result<Option<Vec<u8>>, VmError> {
        let storage_key = StorageKey::new(*key);

        // Charge gas
        ctx.gas
            .charge_storage_read(32)
            .map_err(|_| VmError::OutOfGas {
                used: ctx.gas.consumed(),
                limit: ctx.gas.limit(),
            })?;

        Ok(ctx
            .storage
            .get(&ctx.call.contract, &storage_key)
            .map(|v| v.0))
    }

    /// Writes a value to contract storage.
    pub fn storage_write<S: ContractStorage>(
        ctx: &mut ExecutionContext<S>,
        key: &[u8; 32],
        value: &[u8],
    ) -> Result<(), VmError> {
        if ctx.is_static() {
            return Err(VmError::ExecutionError(
                "Cannot write storage in static call".into(),
            ));
        }

        let storage_key = StorageKey::new(*key);

        // Charge gas based on value size
        ctx.gas
            .charge_storage_write(value.len())
            .map_err(|_| VmError::OutOfGas {
                used: ctx.gas.consumed(),
                limit: ctx.gas.limit(),
            })?;

        ctx.storage.set(
            &ctx.call.contract,
            storage_key,
            StorageValue::new(value.to_vec()),
        );

        Ok(())
    }

    /// Deletes a value from contract storage.
    pub fn storage_delete<S: ContractStorage>(
        ctx: &mut ExecutionContext<S>,
        key: &[u8; 32],
    ) -> Result<bool, VmError> {
        if ctx.is_static() {
            return Err(VmError::ExecutionError(
                "Cannot delete storage in static call".into(),
            ));
        }

        let storage_key = StorageKey::new(*key);

        // Add refund for deletion
        ctx.gas.add_refund(ctx.gas.config().storage_delete_refund);

        let existed = ctx
            .storage
            .delete(&ctx.call.contract, &storage_key)
            .is_some();
        Ok(existed)
    }

    // ==================== Context Information ====================

    /// Returns the caller address.
    pub fn get_caller<S: ContractStorage>(ctx: &ExecutionContext<S>) -> Address {
        ctx.caller()
    }

    /// Returns the contract's own address.
    pub fn get_address<S: ContractStorage>(ctx: &ExecutionContext<S>) -> Address {
        ctx.address()
    }

    /// Returns the transaction origin.
    pub fn get_origin<S: ContractStorage>(ctx: &ExecutionContext<S>) -> Address {
        ctx.origin()
    }

    /// Returns the value sent with the call.
    pub fn get_value<S: ContractStorage>(ctx: &ExecutionContext<S>) -> u64 {
        ctx.value()
    }

    /// Returns the call data.
    pub fn get_calldata<S: ContractStorage>(ctx: &ExecutionContext<S>) -> &[u8] {
        ctx.calldata()
    }

    /// Returns the block timestamp.
    pub fn get_timestamp<S: ContractStorage>(ctx: &ExecutionContext<S>) -> u64 {
        ctx.timestamp()
    }

    /// Returns the block height.
    pub fn get_block_height<S: ContractStorage>(ctx: &ExecutionContext<S>) -> u64 {
        ctx.block_height()
    }

    /// Returns the block hash.
    pub fn get_block_hash<S: ContractStorage>(ctx: &ExecutionContext<S>) -> Hash256 {
        ctx.block_hash()
    }

    /// Returns remaining gas.
    pub fn get_gas_remaining<S: ContractStorage>(ctx: &ExecutionContext<S>) -> u64 {
        ctx.gas_remaining()
    }

    /// Returns the chain ID.
    pub fn get_chain_id<S: ContractStorage>(ctx: &ExecutionContext<S>) -> u64 {
        ctx.chain_id
    }

    // ==================== Cryptographic Operations ====================

    /// Computes SHA3-256 hash.
    pub fn sha3<S: ContractStorage>(
        ctx: &mut ExecutionContext<S>,
        data: &[u8],
    ) -> Result<[u8; 32], VmError> {
        ctx.gas
            .charge_hash(data.len())
            .map_err(|_| VmError::OutOfGas {
                used: ctx.gas.consumed(),
                limit: ctx.gas.limit(),
            })?;

        use sha3::{Digest, Sha3_256};
        let result = Sha3_256::digest(data);
        Ok(result.into())
    }

    /// Computes Blake3 hash.
    pub fn blake3<S: ContractStorage>(
        ctx: &mut ExecutionContext<S>,
        data: &[u8],
    ) -> Result<[u8; 32], VmError> {
        ctx.gas
            .charge_hash(data.len())
            .map_err(|_| VmError::OutOfGas {
                used: ctx.gas.consumed(),
                limit: ctx.gas.limit(),
            })?;

        Ok(blake3::hash(data).into())
    }

    /// Verifies an Ed25519 signature.
    pub fn verify_signature<S: ContractStorage>(
        ctx: &mut ExecutionContext<S>,
        _message: &[u8],
        _signature: &[u8; 64],
        _public_key: &[u8; 32],
    ) -> Result<bool, VmError> {
        ctx.gas.charge_signature().map_err(|_| VmError::OutOfGas {
            used: ctx.gas.consumed(),
            limit: ctx.gas.limit(),
        })?;

        // Use synor_crypto for verification
        // Simplified for now
        Ok(true) // Placeholder
    }

    // ==================== Contract Calls ====================

    /// Calls another contract.
    pub fn call_contract<S: ContractStorage>(
        ctx: &mut ExecutionContext<S>,
        _target: ContractId,
        value: u64,
        input: &[u8],
        _gas_limit: u64,
    ) -> Result<Vec<u8>, VmError> {
        if ctx.is_static() && value > 0 {
            return Err(VmError::ExecutionError(
                "Cannot send value in static call".into(),
            ));
        }

        // Charge gas for call
        ctx.gas
            .charge_call(input.len())
            .map_err(|_| VmError::OutOfGas {
                used: ctx.gas.consumed(),
                limit: ctx.gas.limit(),
            })?;

        // This would trigger actual contract execution
        // For now, return empty success
        Ok(Vec::new())
    }

    /// Makes a static (read-only) call to another contract.
    pub fn static_call<S: ContractStorage>(
        ctx: &mut ExecutionContext<S>,
        _target: ContractId,
        input: &[u8],
        _gas_limit: u64,
    ) -> Result<Vec<u8>, VmError> {
        ctx.gas
            .charge_call(input.len())
            .map_err(|_| VmError::OutOfGas {
                used: ctx.gas.consumed(),
                limit: ctx.gas.limit(),
            })?;

        Ok(Vec::new())
    }

    /// Makes a delegate call (code runs in caller's context).
    pub fn delegate_call<S: ContractStorage>(
        ctx: &mut ExecutionContext<S>,
        _target: ContractId,
        input: &[u8],
        _gas_limit: u64,
    ) -> Result<Vec<u8>, VmError> {
        if ctx.is_static() {
            return Err(VmError::ExecutionError(
                "Cannot delegate call in static call".into(),
            ));
        }

        ctx.gas
            .charge_call(input.len())
            .map_err(|_| VmError::OutOfGas {
                used: ctx.gas.consumed(),
                limit: ctx.gas.limit(),
            })?;

        Ok(Vec::new())
    }

    // ==================== Events ====================

    /// Emits a log event.
    pub fn emit_log<S: ContractStorage>(
        ctx: &mut ExecutionContext<S>,
        topics: Vec<[u8; 32]>,
        data: Vec<u8>,
    ) -> Result<(), VmError> {
        let topics: Vec<Hash256> = topics.into_iter().map(Hash256::from_bytes).collect();
        ctx.emit_log(topics, data)
    }

    // ==================== Control Flow ====================

    /// Reverts execution with a message.
    pub fn revert(message: &str) -> VmError {
        VmError::Revert(message.to_string())
    }

    /// Returns successfully with data.
    pub fn return_data<S: ContractStorage>(ctx: &mut ExecutionContext<S>, data: Vec<u8>) {
        ctx.set_return_data(data);
    }

    // ==================== Balance Operations ====================

    /// Gets the balance of an address.
    pub fn get_balance<S: ContractStorage>(
        _ctx: &ExecutionContext<S>,
        _address: &Address,
    ) -> Result<u64, VmError> {
        // Would query UTXO set
        Ok(0)
    }

    /// Transfers value to an address.
    pub fn transfer<S: ContractStorage>(
        ctx: &mut ExecutionContext<S>,
        _to: &Address,
        _amount: u64,
    ) -> Result<(), VmError> {
        if ctx.is_static() {
            return Err(VmError::ExecutionError(
                "Cannot transfer in static call".into(),
            ));
        }

        // Would create transfer output
        Ok(())
    }
}

/// WASM import definitions for host functions.
/// These map to the actual wasmtime host function implementations.
pub mod wasm_imports {
    /// Storage read signature: (key_ptr: i32, value_ptr: i32) -> i32 (length or -1)
    pub const STORAGE_READ: &str = "synor_storage_read";

    /// Storage write signature: (key_ptr: i32, value_ptr: i32, value_len: i32) -> i32
    pub const STORAGE_WRITE: &str = "synor_storage_write";

    /// Storage delete signature: (key_ptr: i32) -> i32 (existed: 0/1)
    pub const STORAGE_DELETE: &str = "synor_storage_delete";

    /// Get caller: (out_ptr: i32) -> ()
    pub const GET_CALLER: &str = "synor_get_caller";

    /// Get address: (out_ptr: i32) -> ()
    pub const GET_ADDRESS: &str = "synor_get_address";

    /// Get value: () -> i64
    pub const GET_VALUE: &str = "synor_get_value";

    /// Get calldata size: () -> i32
    pub const GET_CALLDATA_SIZE: &str = "synor_get_calldata_size";

    /// Get calldata: (out_ptr: i32, offset: i32, length: i32) -> ()
    pub const GET_CALLDATA: &str = "synor_get_calldata";

    /// Get timestamp: () -> i64
    pub const GET_TIMESTAMP: &str = "synor_get_timestamp";

    /// Get block height: () -> i64
    pub const GET_BLOCK_HEIGHT: &str = "synor_get_block_height";

    /// SHA3 hash: (data_ptr: i32, data_len: i32, out_ptr: i32) -> ()
    pub const SHA3: &str = "synor_sha3";

    /// Blake3 hash: (data_ptr: i32, data_len: i32, out_ptr: i32) -> ()
    pub const BLAKE3: &str = "synor_blake3";

    /// Emit log: (topics_ptr: i32, topics_count: i32, data_ptr: i32, data_len: i32) -> i32
    pub const EMIT_LOG: &str = "synor_emit_log";

    /// Call contract: (addr_ptr: i32, value: i64, data_ptr: i32, data_len: i32, gas: i64) -> i32
    pub const CALL: &str = "synor_call";

    /// Revert: (msg_ptr: i32, msg_len: i32) -> ()
    pub const REVERT: &str = "synor_revert";

    /// Return: (data_ptr: i32, data_len: i32) -> ()
    pub const RETURN: &str = "synor_return";
}

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

    fn test_context() -> ExecutionContext<MemoryStorage> {
        let contract = ContractId::from_bytes([0x42; 32]);
        let caller = Address::from_ed25519_pubkey(Network::Mainnet, &[0x11; 32]);
        let call = CallContext::new(contract, caller, 1000, vec![1, 2, 3]);
        ExecutionContext::<MemoryStorage>::testing(call)
    }

    #[test]
    fn test_storage_operations() {
        let mut ctx = test_context();
        let key = [0x01; 32];
        let value = vec![1, 2, 3, 4];

        // Write
        HostFunctions::storage_write(&mut ctx, &key, &value).unwrap();
        ctx.storage.commit();

        // Read
        let read = HostFunctions::storage_read(&mut ctx, &key).unwrap();
        assert_eq!(read, Some(value.clone()));

        // Delete
        let existed = HostFunctions::storage_delete(&mut ctx, &key).unwrap();
        assert!(existed);
        ctx.storage.commit();

        let read = HostFunctions::storage_read(&mut ctx, &key).unwrap();
        assert!(read.is_none());
    }

    #[test]
    fn test_context_getters() {
        let ctx = test_context();

        assert_eq!(HostFunctions::get_value(&ctx), 1000);
        assert_eq!(HostFunctions::get_calldata(&ctx), &[1, 2, 3]);
        assert!(HostFunctions::get_gas_remaining(&ctx) > 0);
    }

    #[test]
    fn test_hash_functions() {
        let mut ctx = test_context();
        let data = b"hello world";

        let sha3_result = HostFunctions::sha3(&mut ctx, data).unwrap();
        let blake3_result = HostFunctions::blake3(&mut ctx, data).unwrap();

        assert_ne!(sha3_result, blake3_result);
        assert_ne!(sha3_result, [0u8; 32]);
    }

    #[test]
    fn test_static_call_restrictions() {
        let contract = ContractId::from_bytes([0x42; 32]);
        let caller = Address::from_ed25519_pubkey(Network::Mainnet, &[0x11; 32]);
        let call = CallContext::static_call(contract, caller, vec![]);
        let mut ctx = ExecutionContext::<MemoryStorage>::testing(call);

        // Storage write should fail
        let result = HostFunctions::storage_write(&mut ctx, &[0x01; 32], &[1, 2, 3]);
        assert!(result.is_err());

        // Emit log should fail
        let result = ctx.emit_log(vec![], vec![]);
        assert!(result.is_err());
    }
}
