//! Synor Contract Compiler
//!
//! A comprehensive toolchain for compiling, optimizing, and validating
//! Synor smart contracts written in Rust and compiled to WebAssembly.
//!
//! # Features
//!
//! - Compile Rust contracts to optimized WASM
//! - Strip unnecessary sections (debug info, custom sections)
//! - Apply wasm-opt optimizations
//! - Validate output against Synor VM requirements
//! - Generate contract ABI from exports
//! - Report code size and estimated gas costs
//!
//! # Usage
//!
//! ```rust,ignore
//! use synor_compiler::{Compiler, CompilerConfig};
//!
//! let config = CompilerConfig::default();
//! let compiler = Compiler::new(config)?;
//!
//! // Compile from WASM bytecode
//! let result = compiler.compile(wasm_bytes)?;
//!
//! println!("Optimized size: {} bytes", result.code.len());
//! println!("Estimated gas: {}", result.estimated_gas);
//! ```
//!
//! # Architecture
//!
//! ```text
//! ┌─────────────────────────────────────────────────────────────────┐
//! │                     SYNOR COMPILER                               │
//! ├─────────────────────────────────────────────────────────────────┤
//! │                                                                  │
//! │  ┌──────────────┐  ┌──────────────┐  ┌──────────────────────┐  │
//! │  │   Parser     │  │  Optimizer   │  │     Validator        │  │
//! │  │  (wasmparser)│──▶│  (wasm-opt)  │──▶│  (VM Compat)        │  │
//! │  └──────────────┘  └──────────────┘  └──────────────────────┘  │
//! │         │                                        │              │
//! │         ▼                                        ▼              │
//! │  ┌──────────────┐                       ┌──────────────────┐   │
//! │  │   Metadata   │                       │    Gas           │   │
//! │  │   Extractor  │                       │    Estimator     │   │
//! │  └──────────────┘                       └──────────────────┘   │
//! │                                                                  │
//! └─────────────────────────────────────────────────────────────────┘
//! ```

#![allow(dead_code)]

pub mod metadata;
pub mod optimizer;
pub mod validator;

use std::path::PathBuf;

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

use synor_types::Hash256;
use synor_vm::{ContractId, GasConfig, MAX_CONTRACT_SIZE, MAX_MEMORY_PAGES};

pub use metadata::{ContractAbi, ContractMetadata, FunctionAbi, TypeInfo};
pub use optimizer::{OptimizationLevel, Optimizer, StripOptions};
pub use validator::{ValidationError, ValidationResult, Validator};

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

    /// Validation error.
    #[error("Validation error: {0}")]
    ValidationError(#[from] validator::ValidationError),

    /// Optimization error.
    #[error("Optimization error: {0}")]
    OptimizationError(String),

    /// Contract too large.
    #[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 signature.
    #[error("Invalid export signature for '{name}': expected {expected}, got {actual}")]
    InvalidExportSignature {
        name: String,
        expected: String,
        actual: String,
    },

    /// IO error.
    #[error("IO error: {0}")]
    IoError(#[from] std::io::Error),

    /// Encoding error.
    #[error("Encoding error: {0}")]
    EncodingError(String),

    /// External tool error.
    #[error("External tool error: {tool}: {message}")]
    ExternalToolError { tool: String, message: String },
}

/// Result type for compiler operations.
pub type Result<T> = std::result::Result<T, CompilerError>;

/// Compiler configuration.
#[derive(Clone, Debug)]
pub struct CompilerConfig {
    /// Optimization level.
    pub optimization_level: OptimizationLevel,

    /// Strip options.
    pub strip_options: StripOptions,

    /// Maximum contract size (bytes).
    pub max_contract_size: usize,

    /// Maximum memory pages.
    pub max_memory_pages: u32,

    /// Whether to run wasm-opt external tool.
    pub use_wasm_opt: bool,

    /// Path to wasm-opt binary.
    pub wasm_opt_path: Option<PathBuf>,

    /// Whether to validate against VM requirements.
    pub validate: bool,

    /// Whether to extract and include metadata.
    pub extract_metadata: bool,

    /// Gas configuration for estimation.
    pub gas_config: GasConfig,

    /// Whether to generate ABI.
    pub generate_abi: bool,
}

impl Default for CompilerConfig {
    fn default() -> Self {
        CompilerConfig {
            optimization_level: OptimizationLevel::Size,
            strip_options: StripOptions::default(),
            max_contract_size: MAX_CONTRACT_SIZE,
            max_memory_pages: MAX_MEMORY_PAGES,
            use_wasm_opt: true,
            wasm_opt_path: None,
            validate: true,
            extract_metadata: true,
            gas_config: GasConfig::default(),
            generate_abi: true,
        }
    }
}

impl CompilerConfig {
    /// Creates a configuration for maximum optimization.
    pub fn optimized() -> Self {
        CompilerConfig {
            optimization_level: OptimizationLevel::Aggressive,
            strip_options: StripOptions::all(),
            use_wasm_opt: true,
            ..Default::default()
        }
    }

    /// Creates a configuration for development (faster compilation).
    pub fn development() -> Self {
        CompilerConfig {
            optimization_level: OptimizationLevel::None,
            strip_options: StripOptions::minimal(),
            use_wasm_opt: false,
            validate: true,
            ..Default::default()
        }
    }

    /// Creates a configuration for testing.
    pub fn testing() -> Self {
        CompilerConfig {
            optimization_level: OptimizationLevel::Basic,
            strip_options: StripOptions::default(),
            use_wasm_opt: false,
            validate: true,
            gas_config: GasConfig::testing(),
            ..Default::default()
        }
    }
}

/// Compilation result.
#[derive(Clone, Debug)]
pub struct CompilationResult {
    /// Contract ID (hash of optimized code).
    pub contract_id: ContractId,

    /// Optimized WASM bytecode.
    pub code: Vec<u8>,

    /// Original code size (before optimization).
    pub original_size: usize,

    /// Optimized code size.
    pub optimized_size: usize,

    /// Size reduction percentage.
    pub size_reduction: f64,

    /// Contract metadata.
    pub metadata: Option<ContractMetadata>,

    /// Contract ABI.
    pub abi: Option<ContractAbi>,

    /// Estimated deployment gas.
    pub estimated_deploy_gas: u64,

    /// Validation result.
    pub validation: ValidationResult,

    /// Compilation warnings.
    pub warnings: Vec<String>,
}

impl CompilationResult {
    /// Returns the code hash.
    pub fn code_hash(&self) -> Hash256 {
        let hash: [u8; 32] = blake3::hash(&self.code).into();
        Hash256::from_bytes(hash)
    }

    /// Returns size statistics as a formatted string.
    pub fn size_stats(&self) -> String {
        format!(
            "Original: {} bytes, Optimized: {} bytes, Reduction: {:.1}%",
            self.original_size, self.optimized_size, self.size_reduction
        )
    }
}

/// The main contract compiler.
pub struct Compiler {
    /// Configuration.
    config: CompilerConfig,

    /// Optimizer instance.
    optimizer: Optimizer,

    /// Validator instance.
    validator: Validator,
}

impl Compiler {
    /// Creates a new compiler with the given configuration.
    pub fn new(config: CompilerConfig) -> Result<Self> {
        let optimizer = Optimizer::new(
            config.optimization_level,
            config.strip_options.clone(),
            config.wasm_opt_path.clone(),
        );

        let validator = Validator::new(config.max_contract_size, config.max_memory_pages);

        Ok(Compiler {
            config,
            optimizer,
            validator,
        })
    }

    /// Creates a compiler with default configuration.
    pub fn default_compiler() -> Result<Self> {
        Self::new(CompilerConfig::default())
    }

    /// Compiles WASM bytecode.
    pub fn compile(&self, wasm: Vec<u8>) -> Result<CompilationResult> {
        let original_size = wasm.len();
        info!("Compiling contract ({} bytes)", original_size);

        // Check initial size
        if original_size > self.config.max_contract_size {
            return Err(CompilerError::ContractTooLarge {
                size: original_size,
                max: self.config.max_contract_size,
            });
        }

        // Parse and validate the WASM
        debug!("Parsing WASM module");
        self.parse_wasm(&wasm)?;

        // Extract metadata before optimization (preserves debug info)
        let metadata = if self.config.extract_metadata {
            debug!("Extracting metadata");
            Some(metadata::extract_metadata(&wasm)?)
        } else {
            None
        };

        // Extract ABI
        let abi = if self.config.generate_abi {
            debug!("Generating ABI");
            Some(metadata::extract_abi(&wasm)?)
        } else {
            None
        };

        // Optimize the WASM
        debug!("Optimizing WASM");
        let optimized = self.optimizer.optimize(wasm)?;

        // Run external wasm-opt if available and configured
        let optimized = if self.config.use_wasm_opt {
            match self.optimizer.run_wasm_opt(&optimized) {
                Ok(further_optimized) => {
                    debug!(
                        "wasm-opt reduced size from {} to {} bytes",
                        optimized.len(),
                        further_optimized.len()
                    );
                    further_optimized
                }
                Err(e) => {
                    warn!("wasm-opt not available: {}", e);
                    optimized
                }
            }
        } else {
            optimized
        };

        let optimized_size = optimized.len();
        let size_reduction = if original_size > 0 {
            ((original_size - optimized_size) as f64 / original_size as f64) * 100.0
        } else {
            0.0
        };

        info!(
            "Optimization complete: {} -> {} bytes ({:.1}% reduction)",
            original_size, optimized_size, size_reduction
        );

        // Validate the optimized WASM
        let validation = if self.config.validate {
            debug!("Validating optimized WASM");
            self.validator.validate(&optimized)?
        } else {
            ValidationResult::success()
        };

        // Calculate contract ID
        let hash: [u8; 32] = blake3::hash(&optimized).into();
        let contract_id = ContractId::from_bytes(hash);

        // Estimate deployment gas
        let estimated_deploy_gas = self.estimate_deploy_gas(&optimized);

        // Collect warnings
        let mut warnings = validation.warnings.clone();
        if optimized_size > self.config.max_contract_size / 2 {
            warnings.push(format!(
                "Contract is large ({} bytes), consider optimization",
                optimized_size
            ));
        }

        Ok(CompilationResult {
            contract_id,
            code: optimized,
            original_size,
            optimized_size,
            size_reduction,
            metadata,
            abi,
            estimated_deploy_gas,
            validation,
            warnings,
        })
    }

    /// Compiles a contract from a file path.
    pub fn compile_file(&self, path: &std::path::Path) -> Result<CompilationResult> {
        let wasm = std::fs::read(path)?;
        self.compile(wasm)
    }

    /// Parses WASM and performs initial validation.
    fn parse_wasm(&self, wasm: &[u8]) -> Result<()> {
        use wasmparser::{Parser, Payload};

        let parser = Parser::new(0);

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

            match payload {
                Payload::Version { encoding, .. } => {
                    if encoding != wasmparser::Encoding::Module {
                        return Err(CompilerError::ParseError(
                            "Only WASM modules are supported (not components)".into(),
                        ));
                    }
                }
                Payload::End(_) => break,
                _ => {}
            }
        }

        Ok(())
    }

    /// Estimates gas for deploying the contract.
    fn estimate_deploy_gas(&self, wasm: &[u8]) -> u64 {
        let config = &self.config.gas_config;

        // Base deployment cost
        let mut gas = config.create_base;

        // Cost per byte of code
        gas += config.create_byte * wasm.len() as u64;

        // Estimate memory initialization cost
        gas += self.estimate_memory_init_gas(wasm);

        gas
    }

    /// Estimates gas for memory initialization.
    fn estimate_memory_init_gas(&self, wasm: &[u8]) -> u64 {
        use wasmparser::{Parser, Payload};

        let parser = Parser::new(0);
        let mut data_size = 0usize;

        for payload in parser.parse_all(wasm).flatten() {
            if let Payload::DataSection(reader) = payload {
                for data in reader.into_iter().flatten() {
                    data_size += data.data.len();
                }
            }
        }

        self.config.gas_config.memory_byte * data_size as u64
    }

    /// Returns the compiler configuration.
    pub fn config(&self) -> &CompilerConfig {
        &self.config
    }

    /// Checks if wasm-opt is available.
    pub fn wasm_opt_available(&self) -> bool {
        self.optimizer.wasm_opt_available()
    }
}

/// Builder for creating a compiler with custom configuration.
pub struct CompilerBuilder {
    config: CompilerConfig,
}

impl CompilerBuilder {
    /// Creates a new builder with default configuration.
    pub fn new() -> Self {
        CompilerBuilder {
            config: CompilerConfig::default(),
        }
    }

    /// Sets the optimization level.
    pub fn optimization_level(mut self, level: OptimizationLevel) -> Self {
        self.config.optimization_level = level;
        self
    }

    /// Sets strip options.
    pub fn strip_options(mut self, options: StripOptions) -> Self {
        self.config.strip_options = options;
        self
    }

    /// Sets the maximum contract size.
    pub fn max_contract_size(mut self, size: usize) -> Self {
        self.config.max_contract_size = size;
        self
    }

    /// Sets whether to use wasm-opt.
    pub fn use_wasm_opt(mut self, use_it: bool) -> Self {
        self.config.use_wasm_opt = use_it;
        self
    }

    /// Sets the path to wasm-opt binary.
    pub fn wasm_opt_path(mut self, path: PathBuf) -> Self {
        self.config.wasm_opt_path = Some(path);
        self
    }

    /// Sets whether to validate.
    pub fn validate(mut self, validate: bool) -> Self {
        self.config.validate = validate;
        self
    }

    /// Sets whether to extract metadata.
    pub fn extract_metadata(mut self, extract: bool) -> Self {
        self.config.extract_metadata = extract;
        self
    }

    /// Sets whether to generate ABI.
    pub fn generate_abi(mut self, generate: bool) -> Self {
        self.config.generate_abi = generate;
        self
    }

    /// Sets the gas configuration.
    pub fn gas_config(mut self, config: GasConfig) -> Self {
        self.config.gas_config = config;
        self
    }

    /// Builds the compiler.
    pub fn build(self) -> Result<Compiler> {
        Compiler::new(self.config)
    }
}

impl Default for CompilerBuilder {
    fn default() -> Self {
        Self::new()
    }
}

/// Convenience function to compile WASM with default settings.
pub fn compile(wasm: Vec<u8>) -> Result<CompilationResult> {
    let compiler = Compiler::default_compiler()?;
    compiler.compile(wasm)
}

/// Convenience function to compile WASM with size optimization.
pub fn compile_optimized(wasm: Vec<u8>) -> Result<CompilationResult> {
    let compiler = Compiler::new(CompilerConfig::optimized())?;
    compiler.compile(wasm)
}

/// Convenience function to compile for development.
pub fn compile_dev(wasm: Vec<u8>) -> Result<CompilationResult> {
    let compiler = Compiler::new(CompilerConfig::development())?;
    compiler.compile(wasm)
}

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

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

    #[test]
    fn test_compiler_creation() {
        let compiler = Compiler::default_compiler();
        assert!(compiler.is_ok());
    }

    #[test]
    fn test_compiler_config() {
        let config = CompilerConfig::default();
        assert_eq!(config.max_contract_size, MAX_CONTRACT_SIZE);
        assert!(config.validate);
    }

    #[test]
    fn test_compiler_config_optimized() {
        let config = CompilerConfig::optimized();
        assert_eq!(config.optimization_level, OptimizationLevel::Aggressive);
        assert!(config.use_wasm_opt);
    }

    #[test]
    fn test_compiler_config_development() {
        let config = CompilerConfig::development();
        assert_eq!(config.optimization_level, OptimizationLevel::None);
        assert!(!config.use_wasm_opt);
    }

    #[test]
    fn test_compiler_builder() {
        let compiler = CompilerBuilder::new()
            .optimization_level(OptimizationLevel::Size)
            .use_wasm_opt(false)
            .validate(true)
            .build();

        assert!(compiler.is_ok());
    }

    #[test]
    fn test_contract_too_large() {
        let config = CompilerConfig {
            max_contract_size: 10,
            ..CompilerConfig::development()
        };
        let compiler = Compiler::new(config).unwrap();

        let large_wasm = vec![0u8; 100];
        let result = compiler.compile(large_wasm);

        assert!(matches!(
            result,
            Err(CompilerError::ContractTooLarge { .. })
        ));
    }

    #[test]
    fn test_parse_invalid_wasm() {
        let compiler = Compiler::default_compiler().unwrap();
        let invalid = vec![0x00, 0x01, 0x02, 0x03];
        let result = compiler.compile(invalid);
        assert!(result.is_err());
    }

    #[test]
    fn test_compilation_result_stats() {
        let result = CompilationResult {
            contract_id: ContractId::from_bytes([0; 32]),
            code: vec![1, 2, 3],
            original_size: 100,
            optimized_size: 50,
            size_reduction: 50.0,
            metadata: None,
            abi: None,
            estimated_deploy_gas: 1000,
            validation: ValidationResult::success(),
            warnings: vec![],
        };

        let stats = result.size_stats();
        assert!(stats.contains("100"));
        assert!(stats.contains("50"));
        assert!(stats.contains("50.0%"));
    }
}
