//! VM compatibility validation for Synor smart contracts.
//!
//! This module validates WASM bytecode against Synor VM requirements:
//!
//! - Required exports (memory, init, call functions)
//! - Memory limits and configuration
//! - Import validation (only allowed host functions)
//! - Feature detection (SIMD, threads, etc.)
//! - Security checks

use std::collections::{HashMap, HashSet};

use thiserror::Error;
use tracing::{debug, info, warn};
use wasmparser::{Parser, Payload};

use synor_vm::host::wasm_imports;
use synor_vm::{MAX_CONTRACT_SIZE, MAX_MEMORY_PAGES};

/// Validation errors.
#[derive(Debug, Clone, Error)]
pub enum ValidationError {
    /// WASM parsing error.
    #[error("Parse error: {0}")]
    ParseError(String),

    /// Contract exceeds maximum size.
    #[error("Contract too large: {size} bytes (max: {max} bytes)")]
    ContractTooLarge { size: usize, max: usize },

    /// Missing required export.
    #[error("Missing required export: {0}")]
    MissingExport(String),

    /// Invalid export type.
    #[error("Invalid export type for '{name}': expected {expected}, got {actual}")]
    InvalidExportType {
        name: String,
        expected: String,
        actual: String,
    },

    /// Invalid export signature.
    #[error("Invalid signature for export '{name}': expected {expected}, got {actual}")]
    InvalidExportSignature {
        name: String,
        expected: String,
        actual: String,
    },

    /// Invalid import.
    #[error("Invalid import: {module}::{name} - {reason}")]
    InvalidImport {
        module: String,
        name: String,
        reason: String,
    },

    /// Memory configuration error.
    #[error("Memory error: {0}")]
    MemoryError(String),

    /// Unsupported WASM feature.
    #[error("Unsupported WASM feature: {0}")]
    UnsupportedFeature(String),

    /// Security violation.
    #[error("Security violation: {0}")]
    SecurityViolation(String),

    /// Multiple errors.
    #[error("Multiple validation errors")]
    Multiple(Vec<ValidationError>),
}

/// Validation result.
#[derive(Clone, Debug)]
pub struct ValidationResult {
    /// Whether validation succeeded.
    pub valid: bool,

    /// Validation errors.
    pub errors: Vec<ValidationError>,

    /// Validation warnings.
    pub warnings: Vec<String>,

    /// Detected exports.
    pub exports: Vec<ExportInfo>,

    /// Detected imports.
    pub imports: Vec<ImportInfo>,

    /// Memory information.
    pub memory: Option<MemoryInfo>,

    /// Detected WASM features.
    pub features: WasmFeatures,
}

impl ValidationResult {
    /// Creates a successful validation result.
    pub fn success() -> Self {
        ValidationResult {
            valid: true,
            errors: Vec::new(),
            warnings: Vec::new(),
            exports: Vec::new(),
            imports: Vec::new(),
            memory: None,
            features: WasmFeatures::default(),
        }
    }

    /// Creates a failed validation result.
    pub fn failure(error: ValidationError) -> Self {
        ValidationResult {
            valid: false,
            errors: vec![error],
            warnings: Vec::new(),
            exports: Vec::new(),
            imports: Vec::new(),
            memory: None,
            features: WasmFeatures::default(),
        }
    }
}

/// Export information.
#[derive(Clone, Debug)]
pub struct ExportInfo {
    /// Export name.
    pub name: String,

    /// Export kind.
    pub kind: ExportKind,

    /// Index in the respective section.
    pub index: u32,
}

/// Export kind.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ExportKind {
    Function,
    Table,
    Memory,
    Global,
}

/// Import information.
#[derive(Clone, Debug)]
pub struct ImportInfo {
    /// Module name.
    pub module: String,

    /// Import name.
    pub name: String,

    /// Import kind.
    pub kind: ImportKind,
}

/// Import kind.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ImportKind {
    Function(u32), // Type index
    Table,
    Memory,
    Global,
}

/// Memory information.
#[derive(Clone, Debug)]
pub struct MemoryInfo {
    /// Minimum pages.
    pub min_pages: u64,

    /// Maximum pages (if specified).
    pub max_pages: Option<u64>,

    /// Whether memory is shared.
    pub shared: bool,

    /// Whether memory is 64-bit.
    pub memory64: bool,
}

/// Detected WASM features.
#[derive(Clone, Debug, Default)]
pub struct WasmFeatures {
    /// Multi-value returns.
    pub multi_value: bool,

    /// Reference types.
    pub reference_types: bool,

    /// SIMD instructions.
    pub simd: bool,

    /// Threads and atomics.
    pub threads: bool,

    /// Bulk memory operations.
    pub bulk_memory: bool,

    /// Exception handling.
    pub exceptions: bool,

    /// Tail calls.
    pub tail_calls: bool,

    /// Memory64.
    pub memory64: bool,

    /// Mutable globals.
    pub mutable_globals: bool,

    /// Sign extension operations.
    pub sign_extension: bool,

    /// Saturating float-to-int conversions.
    pub saturating_float_to_int: bool,
}

/// WASM validator for Synor VM compatibility.
pub struct Validator {
    /// Maximum contract size.
    max_contract_size: usize,

    /// Maximum memory pages.
    max_memory_pages: u32,

    /// Allowed import modules.
    allowed_imports: HashMap<String, HashSet<String>>,

    /// Required exports.
    required_exports: HashSet<String>,
}

impl Validator {
    /// Creates a new validator.
    pub fn new(max_contract_size: usize, max_memory_pages: u32) -> Self {
        let mut allowed_imports = HashMap::new();

        // Build allowed env imports from synor_vm
        let mut env_imports = HashSet::new();
        env_imports.insert(wasm_imports::STORAGE_READ.to_string());
        env_imports.insert(wasm_imports::STORAGE_WRITE.to_string());
        env_imports.insert(wasm_imports::STORAGE_DELETE.to_string());
        env_imports.insert(wasm_imports::GET_CALLER.to_string());
        env_imports.insert(wasm_imports::GET_ADDRESS.to_string());
        env_imports.insert(wasm_imports::GET_VALUE.to_string());
        env_imports.insert(wasm_imports::GET_CALLDATA_SIZE.to_string());
        env_imports.insert(wasm_imports::GET_CALLDATA.to_string());
        env_imports.insert(wasm_imports::GET_TIMESTAMP.to_string());
        env_imports.insert(wasm_imports::GET_BLOCK_HEIGHT.to_string());
        env_imports.insert(wasm_imports::SHA3.to_string());
        env_imports.insert(wasm_imports::BLAKE3.to_string());
        env_imports.insert(wasm_imports::EMIT_LOG.to_string());
        env_imports.insert(wasm_imports::CALL.to_string());
        env_imports.insert(wasm_imports::REVERT.to_string());
        env_imports.insert(wasm_imports::RETURN.to_string());
        allowed_imports.insert("env".to_string(), env_imports);

        // Required exports
        let mut required_exports = HashSet::new();
        required_exports.insert("memory".to_string());
        // __synor_init and __synor_call are optional but recommended

        Validator {
            max_contract_size,
            max_memory_pages,
            allowed_imports,
            required_exports,
        }
    }

    /// Creates a validator with default settings.
    pub fn default_validator() -> Self {
        Self::new(MAX_CONTRACT_SIZE, MAX_MEMORY_PAGES)
    }

    /// Validates WASM bytecode.
    pub fn validate(&self, wasm: &[u8]) -> Result<ValidationResult, ValidationError> {
        info!("Validating contract ({} bytes)", wasm.len());

        // Check size
        if wasm.len() > self.max_contract_size {
            return Err(ValidationError::ContractTooLarge {
                size: wasm.len(),
                max: self.max_contract_size,
            });
        }

        let mut result = ValidationResult::success();
        let mut errors = Vec::new();

        // Parse the WASM module
        let parser = Parser::new(0);
        let mut imports = Vec::new();
        let mut exports = Vec::new();
        let mut memories = Vec::new();
        let mut has_mutable_globals = false;

        for payload in parser.parse_all(wasm) {
            let payload = payload.map_err(|e| ValidationError::ParseError(e.to_string()))?;

            match payload {
                Payload::Version { encoding, .. } => {
                    if encoding != wasmparser::Encoding::Module {
                        return Err(ValidationError::UnsupportedFeature(
                            "WASM components not supported".into(),
                        ));
                    }
                }
                Payload::ImportSection(reader) => {
                    for import in reader {
                        let import =
                            import.map_err(|e| ValidationError::ParseError(e.to_string()))?;

                        let kind = match import.ty {
                            wasmparser::TypeRef::Func(type_idx) => ImportKind::Function(type_idx),
                            wasmparser::TypeRef::Table(_) => ImportKind::Table,
                            wasmparser::TypeRef::Memory(_) => ImportKind::Memory,
                            wasmparser::TypeRef::Global(_) => ImportKind::Global,
                            wasmparser::TypeRef::Tag(_) => {
                                return Err(ValidationError::UnsupportedFeature(
                                    "Exception handling tags".into(),
                                ));
                            }
                        };

                        imports.push(ImportInfo {
                            module: import.module.to_string(),
                            name: import.name.to_string(),
                            kind,
                        });
                    }
                }
                Payload::MemorySection(reader) => {
                    for memory in reader {
                        let memory =
                            memory.map_err(|e| ValidationError::ParseError(e.to_string()))?;
                        memories.push(MemoryInfo {
                            min_pages: memory.initial,
                            max_pages: memory.maximum,
                            shared: memory.shared,
                            memory64: memory.memory64,
                        });
                    }
                }
                Payload::GlobalSection(reader) => {
                    for global in reader {
                        let global =
                            global.map_err(|e| ValidationError::ParseError(e.to_string()))?;
                        if global.ty.mutable {
                            has_mutable_globals = true;
                        }
                    }
                }
                Payload::ExportSection(reader) => {
                    for export in reader {
                        let export =
                            export.map_err(|e| ValidationError::ParseError(e.to_string()))?;

                        let kind = match export.kind {
                            wasmparser::ExternalKind::Func => ExportKind::Function,
                            wasmparser::ExternalKind::Table => ExportKind::Table,
                            wasmparser::ExternalKind::Memory => ExportKind::Memory,
                            wasmparser::ExternalKind::Global => ExportKind::Global,
                            wasmparser::ExternalKind::Tag => {
                                return Err(ValidationError::UnsupportedFeature(
                                    "Exception handling tags".into(),
                                ));
                            }
                        };

                        exports.push(ExportInfo {
                            name: export.name.to_string(),
                            kind,
                            index: export.index,
                        });
                    }
                }
                Payload::End(_) => break,
                _ => {}
            }
        }

        // Validate imports
        debug!("Validating {} imports", imports.len());
        for import in &imports {
            if let Err(e) = self.validate_import(import) {
                errors.push(e);
            }
        }
        result.imports = imports;

        // Validate exports
        debug!("Validating {} exports", exports.len());
        for required in &self.required_exports {
            if !exports.iter().any(|e| e.name == *required) {
                errors.push(ValidationError::MissingExport(required.clone()));
            }
        }
        result.exports = exports.clone();

        // Validate entry points (warn if missing, don't error)
        if !exports.iter().any(|e| e.name == "__synor_init") {
            result
                .warnings
                .push("Missing __synor_init export - contract cannot be initialized".into());
        }
        if !exports.iter().any(|e| e.name == "__synor_call") {
            result
                .warnings
                .push("Missing __synor_call export - contract cannot be called".into());
        }

        // Validate entry point signatures
        for export in &exports {
            if (export.name == "__synor_init" || export.name == "__synor_call")
                && export.kind != ExportKind::Function
            {
                errors.push(ValidationError::InvalidExportType {
                    name: export.name.clone(),
                    expected: "function".into(),
                    actual: format!("{:?}", export.kind),
                });
            }
        }

        // Validate memory
        debug!("Validating {} memories", memories.len());
        if memories.is_empty() {
            // Check if memory is imported
            let has_imported_memory = result
                .imports
                .iter()
                .any(|i| matches!(i.kind, ImportKind::Memory));
            if !has_imported_memory {
                errors.push(ValidationError::MemoryError(
                    "No memory defined or imported".into(),
                ));
            }
        } else if memories.len() > 1 {
            errors.push(ValidationError::MemoryError(
                "Multiple memories not supported".into(),
            ));
        } else {
            let mem = &memories[0];

            // Check max pages
            if let Some(max) = mem.max_pages {
                if max > self.max_memory_pages as u64 {
                    errors.push(ValidationError::MemoryError(format!(
                        "Memory max pages {} exceeds limit {}",
                        max, self.max_memory_pages
                    )));
                }
            } else {
                result
                    .warnings
                    .push("Memory has no maximum limit - recommended to set max_pages".into());
            }

            // Check for shared memory (threads)
            if mem.shared {
                result.features.threads = true;
            }

            // Check for memory64
            if mem.memory64 {
                result.features.memory64 = true;
            }

            result.memory = Some(mem.clone());
        }

        // Detect features from globals
        if has_mutable_globals {
            result.features.mutable_globals = true;
        }

        // Set result
        if errors.is_empty() {
            result.valid = true;
            info!("Validation passed");
        } else {
            result.valid = false;
            result.errors = errors;
            warn!("Validation failed with {} errors", result.errors.len());
        }

        Ok(result)
    }

    /// Validates an import.
    fn validate_import(&self, import: &ImportInfo) -> Result<(), ValidationError> {
        // Check if the module is allowed
        let allowed_names = self.allowed_imports.get(&import.module);

        match allowed_names {
            Some(names) => {
                // Check if the specific import is allowed
                if !names.contains(&import.name) {
                    return Err(ValidationError::InvalidImport {
                        module: import.module.clone(),
                        name: import.name.clone(),
                        reason: "Unknown import function".into(),
                    });
                }
            }
            None => {
                return Err(ValidationError::InvalidImport {
                    module: import.module.clone(),
                    name: import.name.clone(),
                    reason: format!("Unknown import module '{}'", import.module),
                });
            }
        }

        Ok(())
    }

    /// Adds an allowed import.
    pub fn allow_import(&mut self, module: &str, name: &str) {
        self.allowed_imports
            .entry(module.to_string())
            .or_default()
            .insert(name.to_string());
    }

    /// Adds a required export.
    pub fn require_export(&mut self, name: &str) {
        self.required_exports.insert(name.to_string());
    }
}

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

    fn minimal_wasm() -> Vec<u8> {
        // Minimal WASM module with memory export
        vec![
            0x00, 0x61, 0x73, 0x6d, // WASM magic
            0x01, 0x00, 0x00, 0x00, // Version 1
            // Memory section
            0x05, 0x03, 0x01, 0x00, 0x01, // Memory: min 0, max 1 page
            // Export section
            0x07, 0x0a, 0x01, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x02,
            0x00, // Export "memory" as memory 0
        ]
    }

    #[test]
    fn test_validator_creation() {
        let validator = Validator::default_validator();
        assert!(validator.allowed_imports.contains_key("env"));
        assert!(validator.required_exports.contains("memory"));
    }

    #[test]
    fn test_validate_minimal_wasm() {
        let validator = Validator::default_validator();
        let result = validator.validate(&minimal_wasm());
        assert!(result.is_ok());
        let result = result.unwrap();
        assert!(result.valid);
    }

    #[test]
    fn test_validate_too_large() {
        let validator = Validator::new(10, MAX_MEMORY_PAGES);
        let result = validator.validate(&[0u8; 100]);
        assert!(matches!(
            result,
            Err(ValidationError::ContractTooLarge { .. })
        ));
    }

    #[test]
    fn test_export_info() {
        let export = ExportInfo {
            name: "memory".to_string(),
            kind: ExportKind::Memory,
            index: 0,
        };
        assert_eq!(export.kind, ExportKind::Memory);
    }

    #[test]
    fn test_memory_info() {
        let mem = MemoryInfo {
            min_pages: 1,
            max_pages: Some(256),
            shared: false,
            memory64: false,
        };
        assert_eq!(mem.min_pages, 1);
        assert_eq!(mem.max_pages, Some(256));
    }

    #[test]
    fn test_wasm_features_default() {
        let features = WasmFeatures::default();
        assert!(!features.simd);
        assert!(!features.threads);
        assert!(!features.memory64);
    }

    #[test]
    fn test_validation_result_success() {
        let result = ValidationResult::success();
        assert!(result.valid);
        assert!(result.errors.is_empty());
    }

    #[test]
    fn test_validation_result_failure() {
        let result = ValidationResult::failure(ValidationError::MissingExport("test".into()));
        assert!(!result.valid);
        assert_eq!(result.errors.len(), 1);
    }

    #[test]
    fn test_allow_import() {
        let mut validator = Validator::default_validator();
        validator.allow_import("custom", "my_function");
        assert!(validator
            .allowed_imports
            .get("custom")
            .unwrap()
            .contains("my_function"));
    }

    #[test]
    fn test_require_export() {
        let mut validator = Validator::default_validator();
        validator.require_export("custom_export");
        assert!(validator.required_exports.contains("custom_export"));
    }
}
