//! Contract metadata extraction for Synor smart contracts.
//!
//! This module extracts metadata and ABI from compiled WASM contracts:
//!
//! - Contract name and version
//! - Exported functions and their signatures
//! - Event definitions
//! - Storage layout hints
//! - Custom metadata sections

use std::collections::HashMap;

use serde::{Deserialize, Serialize};
use tracing::debug;
use wasmparser::{Parser, Payload};

use crate::{CompilerError, Result};

/// Contract metadata.
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct ContractMetadata {
    /// Contract name.
    pub name: Option<String>,

    /// Contract version.
    pub version: Option<String>,

    /// Contract authors.
    pub authors: Vec<String>,

    /// Contract description.
    pub description: Option<String>,

    /// Contract license.
    pub license: Option<String>,

    /// Repository URL.
    pub repository: Option<String>,

    /// Build timestamp.
    pub build_timestamp: Option<u64>,

    /// Rust version used.
    pub rust_version: Option<String>,

    /// Synor SDK version.
    pub sdk_version: Option<String>,

    /// Custom metadata.
    pub custom: HashMap<String, String>,
}

impl ContractMetadata {
    /// Creates new metadata with a name.
    pub fn new(name: &str) -> Self {
        ContractMetadata {
            name: Some(name.to_string()),
            ..Default::default()
        }
    }

    /// Sets the version.
    pub fn with_version(mut self, version: &str) -> Self {
        self.version = Some(version.to_string());
        self
    }

    /// Adds an author.
    pub fn with_author(mut self, author: &str) -> Self {
        self.authors.push(author.to_string());
        self
    }

    /// Sets the description.
    pub fn with_description(mut self, description: &str) -> Self {
        self.description = Some(description.to_string());
        self
    }

    /// Sets a custom field.
    pub fn with_custom(mut self, key: &str, value: &str) -> Self {
        self.custom.insert(key.to_string(), value.to_string());
        self
    }
}

/// Contract ABI (Application Binary Interface).
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ContractAbi {
    /// Contract name.
    pub name: String,

    /// Contract version.
    pub version: String,

    /// Functions.
    pub functions: Vec<FunctionAbi>,

    /// Events.
    pub events: Vec<EventAbi>,

    /// Errors.
    pub errors: Vec<ErrorAbi>,
}

impl ContractAbi {
    /// Creates a new ABI.
    pub fn new(name: &str) -> Self {
        ContractAbi {
            name: name.to_string(),
            version: "1.0.0".to_string(),
            functions: Vec::new(),
            events: Vec::new(),
            errors: Vec::new(),
        }
    }

    /// Adds a function.
    pub fn add_function(&mut self, func: FunctionAbi) {
        self.functions.push(func);
    }

    /// Adds an event.
    pub fn add_event(&mut self, event: EventAbi) {
        self.events.push(event);
    }

    /// Adds an error.
    pub fn add_error(&mut self, error: ErrorAbi) {
        self.errors.push(error);
    }

    /// Finds a function by name.
    pub fn find_function(&self, name: &str) -> Option<&FunctionAbi> {
        self.functions.iter().find(|f| f.name == name)
    }

    /// Finds a function by selector.
    pub fn find_by_selector(&self, selector: &[u8; 4]) -> Option<&FunctionAbi> {
        self.functions.iter().find(|f| &f.selector == selector)
    }

    /// Serializes to JSON.
    pub fn to_json(&self) -> Result<String> {
        serde_json::to_string_pretty(self).map_err(|e| CompilerError::EncodingError(e.to_string()))
    }

    /// Deserializes from JSON.
    pub fn from_json(json: &str) -> Result<Self> {
        serde_json::from_str(json).map_err(|e| CompilerError::ParseError(e.to_string()))
    }
}

/// Function ABI.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct FunctionAbi {
    /// Function name.
    pub name: String,

    /// 4-byte selector.
    pub selector: [u8; 4],

    /// Input parameters.
    pub inputs: Vec<ParamAbi>,

    /// Output parameters.
    pub outputs: Vec<ParamAbi>,

    /// Whether function is read-only (view).
    pub view: bool,

    /// Whether function is payable.
    pub payable: bool,

    /// Function documentation.
    pub doc: Option<String>,
}

impl FunctionAbi {
    /// Creates a new function ABI.
    pub fn new(name: &str) -> Self {
        let selector = compute_selector(name);
        FunctionAbi {
            name: name.to_string(),
            selector,
            inputs: Vec::new(),
            outputs: Vec::new(),
            view: false,
            payable: false,
            doc: None,
        }
    }

    /// Adds an input parameter.
    pub fn input(mut self, name: &str, ty: TypeInfo) -> Self {
        self.inputs.push(ParamAbi {
            name: name.to_string(),
            ty,
            indexed: false,
        });
        self
    }

    /// Adds an output parameter.
    pub fn output(mut self, name: &str, ty: TypeInfo) -> Self {
        self.outputs.push(ParamAbi {
            name: name.to_string(),
            ty,
            indexed: false,
        });
        self
    }

    /// Marks function as view.
    pub fn as_view(mut self) -> Self {
        self.view = true;
        self
    }

    /// Marks function as payable.
    pub fn as_payable(mut self) -> Self {
        self.payable = true;
        self
    }

    /// Sets documentation.
    pub fn with_doc(mut self, doc: &str) -> Self {
        self.doc = Some(doc.to_string());
        self
    }
}

/// Parameter ABI.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ParamAbi {
    /// Parameter name.
    pub name: String,

    /// Parameter type.
    pub ty: TypeInfo,

    /// Whether parameter is indexed (for events).
    pub indexed: bool,
}

/// Event ABI.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct EventAbi {
    /// Event name.
    pub name: String,

    /// Event topic (hash of name).
    pub topic: [u8; 32],

    /// Event parameters.
    pub params: Vec<ParamAbi>,

    /// Event documentation.
    pub doc: Option<String>,
}

impl EventAbi {
    /// Creates a new event ABI.
    pub fn new(name: &str) -> Self {
        let topic: [u8; 32] = blake3::hash(name.as_bytes()).into();
        EventAbi {
            name: name.to_string(),
            topic,
            params: Vec::new(),
            doc: None,
        }
    }

    /// Adds a parameter.
    pub fn param(mut self, name: &str, ty: TypeInfo, indexed: bool) -> Self {
        self.params.push(ParamAbi {
            name: name.to_string(),
            ty,
            indexed,
        });
        self
    }
}

/// Error ABI.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ErrorAbi {
    /// Error name.
    pub name: String,

    /// Error selector.
    pub selector: [u8; 4],

    /// Error parameters.
    pub params: Vec<ParamAbi>,

    /// Error documentation.
    pub doc: Option<String>,
}

impl ErrorAbi {
    /// Creates a new error ABI.
    pub fn new(name: &str) -> Self {
        let selector = compute_selector(name);
        ErrorAbi {
            name: name.to_string(),
            selector,
            params: Vec::new(),
            doc: None,
        }
    }
}

/// Type information.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(tag = "kind")]
pub enum TypeInfo {
    /// Unsigned integer.
    U8,
    U16,
    U32,
    U64,
    U128,

    /// Signed integer.
    I8,
    I16,
    I32,
    I64,
    I128,

    /// Boolean.
    Bool,

    /// String.
    String,

    /// Bytes.
    Bytes,

    /// Fixed-size bytes.
    FixedBytes {
        size: usize,
    },

    /// Address.
    Address,

    /// Hash.
    Hash256,

    /// Array.
    Array {
        element: Box<TypeInfo>,
    },

    /// Fixed-size array.
    FixedArray {
        element: Box<TypeInfo>,
        size: usize,
    },

    /// Optional value.
    Option {
        inner: Box<TypeInfo>,
    },

    /// Tuple.
    Tuple {
        elements: Vec<TypeInfo>,
    },

    /// Struct.
    Struct {
        name: String,
        fields: Vec<(String, TypeInfo)>,
    },

    /// Unknown type.
    Unknown {
        name: String,
    },
}

impl TypeInfo {
    /// Returns the type name as a string.
    pub fn type_name(&self) -> String {
        match self {
            TypeInfo::U8 => "u8".to_string(),
            TypeInfo::U16 => "u16".to_string(),
            TypeInfo::U32 => "u32".to_string(),
            TypeInfo::U64 => "u64".to_string(),
            TypeInfo::U128 => "u128".to_string(),
            TypeInfo::I8 => "i8".to_string(),
            TypeInfo::I16 => "i16".to_string(),
            TypeInfo::I32 => "i32".to_string(),
            TypeInfo::I64 => "i64".to_string(),
            TypeInfo::I128 => "i128".to_string(),
            TypeInfo::Bool => "bool".to_string(),
            TypeInfo::String => "String".to_string(),
            TypeInfo::Bytes => "Vec<u8>".to_string(),
            TypeInfo::FixedBytes { size } => format!("[u8; {}]", size),
            TypeInfo::Address => "Address".to_string(),
            TypeInfo::Hash256 => "Hash256".to_string(),
            TypeInfo::Array { element } => format!("Vec<{}>", element.type_name()),
            TypeInfo::FixedArray { element, size } => {
                format!("[{}; {}]", element.type_name(), size)
            }
            TypeInfo::Option { inner } => format!("Option<{}>", inner.type_name()),
            TypeInfo::Tuple { elements } => {
                let inner: Vec<_> = elements.iter().map(|e| e.type_name()).collect();
                format!("({})", inner.join(", "))
            }
            TypeInfo::Struct { name, .. } => name.clone(),
            TypeInfo::Unknown { name } => name.clone(),
        }
    }
}

/// Computes a 4-byte method selector.
pub fn compute_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]]
}

/// Extracts metadata from WASM bytecode.
pub fn extract_metadata(wasm: &[u8]) -> Result<ContractMetadata> {
    let mut metadata = ContractMetadata::default();
    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::CustomSection(reader) => {
                parse_custom_section(&mut metadata, reader.name(), reader.data());
            }
            Payload::End(_) => break,
            _ => {}
        }
    }

    Ok(metadata)
}

/// Parses a custom section for metadata.
fn parse_custom_section(metadata: &mut ContractMetadata, name: &str, data: &[u8]) {
    debug!("Parsing custom section: {}", name);

    match name {
        "synor_metadata" => {
            // Try to parse as JSON
            if let Ok(json) = std::str::from_utf8(data) {
                if let Ok(parsed) = serde_json::from_str::<ContractMetadata>(json) {
                    *metadata = parsed;
                }
            }
        }
        "producers" => {
            // Parse producer section for build info
            parse_producers_section(metadata, data);
        }
        "name" => {
            // WASM name section might contain module name
            if let Some(module_name) = parse_name_section(data) {
                if metadata.name.is_none() {
                    metadata.name = Some(module_name);
                }
            }
        }
        _ => {
            // Store unknown custom sections
            if let Ok(value) = std::str::from_utf8(data) {
                metadata.custom.insert(name.to_string(), value.to_string());
            }
        }
    }
}

/// Parses producers section.
fn parse_producers_section(metadata: &mut ContractMetadata, data: &[u8]) {
    // Producers section is a simple format:
    // [field_count: varuint32]
    // (field_name: string, [value_count: varuint32, (name: string, version: string)*])*

    // Simplified parsing - just look for known patterns
    let data_str = String::from_utf8_lossy(data);

    if data_str.contains("rustc") {
        // Extract rustc version if present
        if let Some(start) = data_str.find("rustc") {
            let end = data_str[start..]
                .find('\0')
                .unwrap_or(data_str.len() - start);
            let version = &data_str[start..start + end];
            metadata.rust_version = Some(version.to_string());
        }
    }
}

/// Parses name section for module name.
fn parse_name_section(data: &[u8]) -> Option<String> {
    // Name section format:
    // subsection_id: u8, size: varuint32, data: bytes

    if data.is_empty() {
        return None;
    }

    // Subsection 0 is module name
    if data[0] == 0 && data.len() > 2 {
        // Skip subsection ID and size (simplified)
        let name_start = 2;
        let name_len = data.get(1).copied()? as usize;
        let name_end = name_start + name_len;

        if name_end <= data.len() {
            return std::str::from_utf8(&data[name_start..name_end])
                .ok()
                .map(|s| s.to_string());
        }
    }

    None
}

/// Extracts ABI from WASM bytecode.
pub fn extract_abi(wasm: &[u8]) -> Result<ContractAbi> {
    let parser = Parser::new(0);
    let mut abi = ContractAbi::new("Contract");
    let mut function_types: Vec<FunctionType> = Vec::new();
    let mut function_type_indices: Vec<u32> = Vec::new();
    let mut import_func_count = 0u32;

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

        match payload {
            Payload::TypeSection(reader) => {
                for rec_group in reader {
                    let rec_group =
                        rec_group.map_err(|e| CompilerError::ParseError(e.to_string()))?;
                    for subtype in rec_group.into_types() {
                        if let wasmparser::CompositeType::Func(func_type) = subtype.composite_type {
                            function_types.push(FunctionType {
                                params: func_type.params().len(),
                                results: func_type.results().len(),
                            });
                        }
                    }
                }
            }
            Payload::ImportSection(reader) => {
                // Count imported functions to adjust export indices
                for import in reader {
                    let import = import.map_err(|e| CompilerError::ParseError(e.to_string()))?;
                    if matches!(import.ty, wasmparser::TypeRef::Func(_)) {
                        import_func_count += 1;
                    }
                }
            }
            Payload::FunctionSection(reader) => {
                for type_idx in reader {
                    let type_idx =
                        type_idx.map_err(|e| CompilerError::ParseError(e.to_string()))?;
                    function_type_indices.push(type_idx);
                }
            }
            Payload::ExportSection(reader) => {
                for export in reader {
                    let export = export.map_err(|e| CompilerError::ParseError(e.to_string()))?;

                    // Only interested in function exports
                    if !matches!(export.kind, wasmparser::ExternalKind::Func) {
                        continue;
                    }

                    // Skip internal functions
                    if export.name.starts_with("__") {
                        continue;
                    }

                    // Skip memory and other non-callable exports
                    if export.name == "memory" {
                        continue;
                    }

                    // Get the function's type
                    let func_idx = export.index;
                    let local_idx = func_idx.saturating_sub(import_func_count);

                    let type_idx = function_type_indices
                        .get(local_idx as usize)
                        .copied()
                        .unwrap_or(0);

                    let func_type = function_types.get(type_idx as usize);

                    // Create function ABI
                    let mut func = FunctionAbi::new(export.name);

                    // Infer parameters from type
                    if let Some(ty) = func_type {
                        for i in 0..ty.params {
                            func.inputs.push(ParamAbi {
                                name: format!("arg{}", i),
                                ty: TypeInfo::I32, // Default to i32 for WASM
                                indexed: false,
                            });
                        }

                        for i in 0..ty.results {
                            func.outputs.push(ParamAbi {
                                name: format!("ret{}", i),
                                ty: TypeInfo::I32, // Default to i32 for WASM
                                indexed: false,
                            });
                        }
                    }

                    // Infer view/payable from name conventions
                    if export.name.starts_with("get_")
                        || export.name.starts_with("is_")
                        || export.name.starts_with("has_")
                        || export.name == "name"
                        || export.name == "symbol"
                        || export.name == "decimals"
                        || export.name == "total_supply"
                        || export.name == "balance_of"
                        || export.name == "allowance"
                    {
                        func.view = true;
                    }

                    abi.add_function(func);
                }
            }
            Payload::CustomSection(reader) => {
                // Check for synor_abi custom section
                if reader.name() == "synor_abi" {
                    if let Ok(json) = std::str::from_utf8(reader.data()) {
                        if let Ok(parsed) = serde_json::from_str::<ContractAbi>(json) {
                            return Ok(parsed);
                        }
                    }
                }

                // Check for contract name in name section
                if reader.name() == "name" {
                    if let Some(name) = parse_name_section(reader.data()) {
                        abi.name = name;
                    }
                }
            }
            Payload::End(_) => break,
            _ => {}
        }
    }

    // Add standard Synor events
    add_standard_events(&mut abi);

    Ok(abi)
}

/// Simplified function type for ABI extraction.
struct FunctionType {
    params: usize,
    results: usize,
}

/// Adds standard Synor events to ABI.
fn add_standard_events(abi: &mut ContractAbi) {
    // Transfer event
    let transfer = EventAbi::new("Transfer")
        .param("from", TypeInfo::Address, true)
        .param("to", TypeInfo::Address, true)
        .param("amount", TypeInfo::U64, false);
    abi.add_event(transfer);

    // Approval event
    let approval = EventAbi::new("Approval")
        .param("owner", TypeInfo::Address, true)
        .param("spender", TypeInfo::Address, true)
        .param("amount", TypeInfo::U64, false);
    abi.add_event(approval);

    // OwnershipTransferred event
    let ownership = EventAbi::new("OwnershipTransferred")
        .param("previous_owner", TypeInfo::Address, true)
        .param("new_owner", TypeInfo::Address, true);
    abi.add_event(ownership);
}

/// Estimates gas for a contract based on its structure.
pub fn estimate_contract_gas(wasm: &[u8], gas_config: &synor_vm::GasConfig) -> u64 {
    let mut gas = 0u64;

    let parser = Parser::new(0);

    for payload in parser.parse_all(wasm).flatten() {
        match payload {
            Payload::DataSection(reader) => {
                // Gas for data initialization
                for data in reader.into_iter().flatten() {
                    gas += gas_config.memory_byte * data.data.len() as u64;
                }
            }
            Payload::CodeSectionStart { count, .. } => {
                // Base gas per function
                gas += count as u64 * gas_config.instruction_base * 10;
            }
            _ => {}
        }
    }

    // Base deployment cost
    gas += gas_config.create_base;

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

    gas
}

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

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

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

    #[test]
    fn test_function_abi() {
        let func = FunctionAbi::new("transfer")
            .input("to", TypeInfo::Address)
            .input("amount", TypeInfo::U64)
            .output("success", TypeInfo::Bool)
            .as_payable();

        assert_eq!(func.name, "transfer");
        assert_eq!(func.inputs.len(), 2);
        assert_eq!(func.outputs.len(), 1);
        assert!(func.payable);
    }

    #[test]
    fn test_event_abi() {
        let event = EventAbi::new("Transfer")
            .param("from", TypeInfo::Address, true)
            .param("to", TypeInfo::Address, true)
            .param("amount", TypeInfo::U64, false);

        assert_eq!(event.name, "Transfer");
        assert_eq!(event.params.len(), 3);
        assert!(event.params[0].indexed);
        assert!(!event.params[2].indexed);
    }

    #[test]
    fn test_type_info_name() {
        assert_eq!(TypeInfo::U64.type_name(), "u64");
        assert_eq!(TypeInfo::Address.type_name(), "Address");
        assert_eq!(
            TypeInfo::Array {
                element: Box::new(TypeInfo::U8)
            }
            .type_name(),
            "Vec<u8>"
        );
        assert_eq!(TypeInfo::FixedBytes { size: 32 }.type_name(), "[u8; 32]");
    }

    #[test]
    fn test_contract_abi() {
        let mut abi = ContractAbi::new("TestToken");
        abi.add_function(FunctionAbi::new("transfer"));
        abi.add_function(FunctionAbi::new("balance_of").as_view());

        assert_eq!(abi.functions.len(), 2);
        assert!(abi.find_function("transfer").is_some());
        assert!(abi.find_function("balance_of").unwrap().view);
    }

    #[test]
    fn test_contract_abi_json() {
        let abi = ContractAbi::new("Test");
        let json = abi.to_json().unwrap();
        let parsed = ContractAbi::from_json(&json).unwrap();
        assert_eq!(parsed.name, "Test");
    }

    #[test]
    fn test_metadata_default() {
        let metadata = ContractMetadata::default();
        assert!(metadata.name.is_none());
        assert!(metadata.authors.is_empty());
    }

    #[test]
    fn test_metadata_builder() {
        let metadata = ContractMetadata::new("MyContract")
            .with_version("1.0.0")
            .with_author("Alice")
            .with_description("A test contract");

        assert_eq!(metadata.name, Some("MyContract".to_string()));
        assert_eq!(metadata.version, Some("1.0.0".to_string()));
        assert_eq!(metadata.authors, vec!["Alice".to_string()]);
    }
}
