//! Contract execution service.
//!
//! Provides smart contract deployment and execution using the Synor VM.

use std::sync::Arc;

use tokio::sync::RwLock;
use tracing::{debug, info, warn};

use synor_storage::{ContractStateStore, ContractStore, Database, StoredContract};
use synor_types::{Address, Hash256};
use synor_vm::{
    storage::MemoryStorage, CallContext, ContractId, ContractModule, ContractStorage,
    ExecutionContext, StorageKey, StorageValue, VmEngine,
};

/// Contract deployment result.
#[derive(Clone, Debug)]
pub struct DeployResult {
    /// Contract ID (code hash).
    pub contract_id: [u8; 32],
    /// Gas used.
    pub gas_used: u64,
    /// Deployment address (for reference).
    pub address: Vec<u8>,
}

/// Contract call result.
#[derive(Clone, Debug)]
pub struct CallResult {
    /// Return data.
    pub data: Vec<u8>,
    /// Gas used.
    pub gas_used: u64,
    /// Success status.
    pub success: bool,
    /// Logs emitted.
    pub logs: Vec<LogEntry>,
}

/// Log entry from contract execution.
#[derive(Clone, Debug)]
pub struct LogEntry {
    /// Contract that emitted the log.
    pub contract_id: [u8; 32],
    /// Indexed topics.
    pub topics: Vec<[u8; 32]>,
    /// Log data.
    pub data: Vec<u8>,
}

/// Contract service manages smart contract execution.
pub struct ContractService {
    /// VM engine for WASM execution.
    engine: RwLock<Option<VmEngine>>,
    /// Contract bytecode store.
    contract_store: RwLock<Option<ContractStore>>,
    /// Contract state store.
    state_store: RwLock<Option<ContractStateStore>>,
    /// Is running.
    running: RwLock<bool>,
    /// Default gas limit for calls.
    default_gas_limit: u64,
    /// Chain ID.
    chain_id: u64,
}

impl ContractService {
    /// Creates a new contract service.
    pub fn new(chain_id: u64) -> Self {
        ContractService {
            engine: RwLock::new(None),
            contract_store: RwLock::new(None),
            state_store: RwLock::new(None),
            running: RwLock::new(false),
            default_gas_limit: 10_000_000,
            chain_id,
        }
    }

    /// Starts the contract service.
    pub async fn start(&self, db: Arc<Database>) -> anyhow::Result<()> {
        info!("Starting contract service");

        // Initialize VM engine
        let engine =
            VmEngine::new().map_err(|e| anyhow::anyhow!("Failed to create VM engine: {}", e))?;

        *self.engine.write().await = Some(engine);

        // Initialize stores
        *self.contract_store.write().await = Some(ContractStore::new(Arc::clone(&db)));
        *self.state_store.write().await = Some(ContractStateStore::new(db));

        *self.running.write().await = true;

        info!("Contract service started");
        Ok(())
    }

    /// Stops the contract service.
    pub async fn stop(&self) -> anyhow::Result<()> {
        info!("Stopping contract service");

        *self.engine.write().await = None;
        *self.contract_store.write().await = None;
        *self.state_store.write().await = None;
        *self.running.write().await = false;

        info!("Contract service stopped");
        Ok(())
    }

    /// Checks if service is running.
    pub async fn is_running(&self) -> bool {
        *self.running.read().await
    }

    /// Deploys a new contract.
    #[allow(clippy::too_many_arguments)]
    pub async fn deploy(
        &self,
        bytecode: Vec<u8>,
        init_args: Vec<u8>,
        deployer: &Address,
        gas_limit: Option<u64>,
        block_height: u64,
        timestamp: u64,
    ) -> anyhow::Result<DeployResult> {
        let engine = self.engine.read().await;
        let engine = engine
            .as_ref()
            .ok_or_else(|| anyhow::anyhow!("Contract service not started"))?;

        let gas_limit = gas_limit.unwrap_or(self.default_gas_limit);

        // Compile the contract
        debug!(size = bytecode.len(), "Compiling contract");
        let module = engine
            .compile(bytecode.clone())
            .map_err(|e| anyhow::anyhow!("Compilation failed: {}", e))?;

        let contract_id = *module.id.as_bytes();

        // Check if contract already exists
        {
            let store = self.contract_store.read().await;
            if let Some(store) = store.as_ref() {
                if store.exists(&contract_id)? {
                    return Err(anyhow::anyhow!("Contract already deployed"));
                }
            }
        }

        // Create execution context for initialization
        let call_context = CallContext::new(module.id, deployer.clone(), 0, init_args.clone());

        let storage = MemoryStorage::new();

        // Load existing state into memory (none for new contract)
        let context = ExecutionContext::new(
            synor_vm::context::BlockInfo {
                height: block_height,
                timestamp,
                hash: Hash256::default(),
                blue_score: block_height,
                daa_score: block_height,
                coinbase: deployer.clone(),
            },
            synor_vm::context::TransactionInfo::default(),
            call_context,
            gas_limit,
            storage,
            self.chain_id,
        );

        // Execute initialization
        debug!(contract = %module.id, "Executing contract init");
        let result = engine
            .execute(&module, "__synor_init", &init_args, context, gas_limit)
            .map_err(|e| anyhow::anyhow!("Initialization failed: {}", e))?;

        // Store the contract
        {
            let store = self.contract_store.read().await;
            let store = store
                .as_ref()
                .ok_or_else(|| anyhow::anyhow!("Contract store not initialized"))?;

            let stored = StoredContract {
                code: bytecode,
                code_hash: contract_id,
                deployer: borsh::to_vec(deployer).unwrap_or_default(),
                deployed_at: timestamp,
                deployed_height: block_height,
            };
            store.put(&stored)?;
        }

        // Cache the compiled module
        engine.cache_module(module);

        info!(
            contract_id = hex::encode(&contract_id[..8]),
            gas_used = result.gas_used,
            "Contract deployed"
        );

        Ok(DeployResult {
            contract_id,
            gas_used: result.gas_used,
            address: contract_id.to_vec(),
        })
    }

    /// Calls a contract method.
    #[allow(clippy::too_many_arguments)]
    pub async fn call(
        &self,
        contract_id: &[u8; 32],
        method: &str,
        args: Vec<u8>,
        caller: &Address,
        value: u64,
        gas_limit: Option<u64>,
        block_height: u64,
        timestamp: u64,
    ) -> anyhow::Result<CallResult> {
        let engine = self.engine.read().await;
        let engine = engine
            .as_ref()
            .ok_or_else(|| anyhow::anyhow!("Contract service not started"))?;

        let gas_limit = gas_limit.unwrap_or(self.default_gas_limit);
        let vm_contract_id = ContractId::from_bytes(*contract_id);

        // Get or compile the contract
        let module = self.get_or_compile_module(engine, contract_id).await?;

        // Load contract state into memory
        let mut storage = MemoryStorage::new();
        self.load_contract_state(&vm_contract_id, &mut storage)
            .await?;

        // Build call data (method selector + args)
        let method_selector = synor_vm_method_selector(method);
        let mut call_data = Vec::with_capacity(4 + args.len());
        call_data.extend_from_slice(&method_selector);
        call_data.extend_from_slice(&args);

        // Create execution context
        let call_context =
            CallContext::new(vm_contract_id, caller.clone(), value, call_data.clone());

        let context = ExecutionContext::new(
            synor_vm::context::BlockInfo {
                height: block_height,
                timestamp,
                hash: Hash256::default(),
                blue_score: block_height,
                daa_score: block_height,
                coinbase: caller.clone(),
            },
            synor_vm::context::TransactionInfo::default(),
            call_context,
            gas_limit,
            storage,
            self.chain_id,
        );

        // Execute the call
        debug!(
            contract = hex::encode(&contract_id[..8]),
            method = method,
            "Executing contract call"
        );

        let result = engine.execute(&module, "__synor_call", &call_data, context, gas_limit);

        match result {
            Ok(exec_result) => {
                // Persist storage changes
                // Note: In a real implementation, we'd track changes from execution
                // For now, we don't persist changes from view calls

                let logs = exec_result
                    .logs
                    .iter()
                    .map(|log| LogEntry {
                        contract_id: *log.contract.as_bytes(),
                        topics: log.topics.iter().map(|t| *t.as_bytes()).collect(),
                        data: log.data.clone(),
                    })
                    .collect();

                Ok(CallResult {
                    data: exec_result.return_data,
                    gas_used: exec_result.gas_used,
                    success: true,
                    logs,
                })
            }
            Err(e) => {
                warn!(error = %e, "Contract call failed");
                Ok(CallResult {
                    data: Vec::new(),
                    gas_used: gas_limit, // Charge full gas on failure
                    success: false,
                    logs: Vec::new(),
                })
            }
        }
    }

    /// Estimates gas for a contract call.
    #[allow(clippy::too_many_arguments)]
    pub async fn estimate_gas(
        &self,
        contract_id: &[u8; 32],
        method: &str,
        args: Vec<u8>,
        caller: &Address,
        value: u64,
        block_height: u64,
        timestamp: u64,
    ) -> anyhow::Result<u64> {
        // Run with high gas limit and return actual usage
        let result = self
            .call(
                contract_id,
                method,
                args,
                caller,
                value,
                Some(100_000_000), // High limit for estimation
                block_height,
                timestamp,
            )
            .await?;

        if result.success {
            // Add 20% buffer for safety
            Ok((result.gas_used as f64 * 1.2) as u64)
        } else {
            Err(anyhow::anyhow!("Call would fail"))
        }
    }

    /// Gets contract bytecode.
    pub async fn get_code(&self, contract_id: &[u8; 32]) -> anyhow::Result<Option<Vec<u8>>> {
        let store = self.contract_store.read().await;
        let store = store
            .as_ref()
            .ok_or_else(|| anyhow::anyhow!("Contract store not initialized"))?;

        Ok(store.get_code(contract_id)?)
    }

    /// Gets contract metadata.
    pub async fn get_contract(
        &self,
        contract_id: &[u8; 32],
    ) -> anyhow::Result<Option<StoredContract>> {
        let store = self.contract_store.read().await;
        let store = store
            .as_ref()
            .ok_or_else(|| anyhow::anyhow!("Contract store not initialized"))?;

        Ok(store.get(contract_id)?)
    }

    /// Gets a value from contract storage.
    pub async fn get_storage_at(
        &self,
        contract_id: &[u8; 32],
        key: &[u8; 32],
    ) -> anyhow::Result<Option<Vec<u8>>> {
        let store = self.state_store.read().await;
        let store = store
            .as_ref()
            .ok_or_else(|| anyhow::anyhow!("State store not initialized"))?;

        Ok(store.get(contract_id, key)?)
    }

    /// Checks if a contract exists.
    pub async fn contract_exists(&self, contract_id: &[u8; 32]) -> anyhow::Result<bool> {
        let store = self.contract_store.read().await;
        let store = store
            .as_ref()
            .ok_or_else(|| anyhow::anyhow!("Contract store not initialized"))?;

        Ok(store.exists(contract_id)?)
    }

    /// Gets or compiles a contract module.
    async fn get_or_compile_module(
        &self,
        engine: &VmEngine,
        contract_id: &[u8; 32],
    ) -> anyhow::Result<ContractModule> {
        let vm_contract_id = ContractId::from_bytes(*contract_id);

        // Check cache first
        if let Some(module) = engine.get_module(&vm_contract_id) {
            return Ok((*module).clone());
        }

        // Load bytecode and compile
        let code = self
            .get_code(contract_id)
            .await?
            .ok_or_else(|| anyhow::anyhow!("Contract not found"))?;

        let module = engine
            .compile(code)
            .map_err(|e| anyhow::anyhow!("Compilation failed: {}", e))?;

        // Cache for future use
        engine.cache_module(module.clone());

        Ok(module)
    }

    /// Loads contract state into memory storage.
    async fn load_contract_state(
        &self,
        contract_id: &ContractId,
        storage: &mut MemoryStorage,
    ) -> anyhow::Result<()> {
        let store = self.state_store.read().await;
        let store = store
            .as_ref()
            .ok_or_else(|| anyhow::anyhow!("State store not initialized"))?;

        // Load all state for this contract
        let entries = store.get_all(contract_id.as_bytes())?;

        for (key, value) in entries {
            let storage_key = StorageKey::new(key);
            let storage_value = StorageValue::new(value);
            storage.set(contract_id, storage_key, storage_value);
        }

        storage.commit();
        Ok(())
    }

    /// Persists storage changes to the database.
    pub async fn persist_storage_changes(
        &self,
        contract_id: &[u8; 32],
        changes: Vec<([u8; 32], Option<Vec<u8>>)>,
    ) -> anyhow::Result<()> {
        let store = self.state_store.read().await;
        let store = store
            .as_ref()
            .ok_or_else(|| anyhow::anyhow!("State store not initialized"))?;

        for (key, value) in changes {
            match value {
                Some(data) => store.set(contract_id, &key, &data)?,
                None => store.delete(contract_id, &key)?,
            }
        }

        Ok(())
    }
}

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

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

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

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