"""
Synor Compiler SDK Types

Smart contract compilation, optimization, and ABI generation.
"""

from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Dict, List, Optional, Tuple, Union


# ============================================================================
# Enumerations
# ============================================================================

class OptimizationLevel(Enum):
    """Optimization level for WASM compilation."""
    NONE = "none"
    BASIC = "basic"
    SIZE = "size"
    AGGRESSIVE = "aggressive"


# ============================================================================
# Configuration Types
# ============================================================================

@dataclass
class StripOptions:
    """Options for stripping WASM sections."""
    strip_debug: bool = True
    strip_producers: bool = True
    strip_names: bool = True
    strip_custom: bool = True
    preserve_sections: List[str] = field(default_factory=list)
    strip_unused: bool = True

    @staticmethod
    def from_dict(data: Dict[str, Any]) -> "StripOptions":
        return StripOptions(
            strip_debug=data.get("strip_debug", True),
            strip_producers=data.get("strip_producers", True),
            strip_names=data.get("strip_names", True),
            strip_custom=data.get("strip_custom", True),
            preserve_sections=data.get("preserve_sections", []),
            strip_unused=data.get("strip_unused", True),
        )

    def to_dict(self) -> Dict[str, Any]:
        return {
            "strip_debug": self.strip_debug,
            "strip_producers": self.strip_producers,
            "strip_names": self.strip_names,
            "strip_custom": self.strip_custom,
            "preserve_sections": self.preserve_sections,
            "strip_unused": self.strip_unused,
        }


@dataclass
class CompilerConfig:
    """Compiler configuration."""
    api_key: str
    endpoint: str = "https://compiler.synor.io/v1"
    timeout: int = 60000
    retries: int = 3
    debug: bool = False
    optimization_level: OptimizationLevel = OptimizationLevel.SIZE
    strip_options: Optional[StripOptions] = None
    max_contract_size: int = 256 * 1024
    use_wasm_opt: bool = True
    validate: bool = True
    extract_metadata: bool = True
    generate_abi: bool = True


# ============================================================================
# Compilation Types
# ============================================================================

@dataclass
class CompilationRequest:
    """Compilation request."""
    wasm: str  # Base64 encoded
    optimization_level: Optional[OptimizationLevel] = None
    strip_options: Optional[StripOptions] = None
    use_wasm_opt: Optional[bool] = None
    validate: Optional[bool] = None
    extract_metadata: Optional[bool] = None
    generate_abi: Optional[bool] = None

    def to_dict(self) -> Dict[str, Any]:
        result: Dict[str, Any] = {"wasm": self.wasm}
        if self.optimization_level:
            result["optimization_level"] = self.optimization_level.value
        if self.strip_options:
            result["strip_options"] = self.strip_options.to_dict()
        if self.use_wasm_opt is not None:
            result["use_wasm_opt"] = self.use_wasm_opt
        if self.validate is not None:
            result["validate"] = self.validate
        if self.extract_metadata is not None:
            result["extract_metadata"] = self.extract_metadata
        if self.generate_abi is not None:
            result["generate_abi"] = self.generate_abi
        return result


@dataclass
class ValidationError:
    """Validation error."""
    code: str
    message: str
    location: Optional[str] = None

    @staticmethod
    def from_dict(data: Dict[str, Any]) -> "ValidationError":
        return ValidationError(
            code=data.get("code", ""),
            message=data.get("message", ""),
            location=data.get("location"),
        )


@dataclass
class ValidationResult:
    """Validation result."""
    valid: bool
    errors: List[ValidationError] = field(default_factory=list)
    warnings: List[str] = field(default_factory=list)
    export_count: int = 0
    import_count: int = 0
    function_count: int = 0
    memory_pages: Tuple[int, Optional[int]] = (0, None)

    @staticmethod
    def from_dict(data: Dict[str, Any]) -> "ValidationResult":
        return ValidationResult(
            valid=data.get("valid", False),
            errors=[ValidationError.from_dict(e) for e in data.get("errors", [])],
            warnings=data.get("warnings", []),
            export_count=data.get("export_count", 0),
            import_count=data.get("import_count", 0),
            function_count=data.get("function_count", 0),
            memory_pages=tuple(data.get("memory_pages", [0, None])),
        )


@dataclass
class ContractMetadata:
    """Contract metadata."""
    name: Optional[str] = None
    version: Optional[str] = None
    authors: List[str] = field(default_factory=list)
    description: Optional[str] = None
    license: Optional[str] = None
    repository: Optional[str] = None
    build_timestamp: Optional[int] = None
    rust_version: Optional[str] = None
    sdk_version: Optional[str] = None
    custom: Dict[str, str] = field(default_factory=dict)

    @staticmethod
    def from_dict(data: Dict[str, Any]) -> "ContractMetadata":
        return ContractMetadata(
            name=data.get("name"),
            version=data.get("version"),
            authors=data.get("authors", []),
            description=data.get("description"),
            license=data.get("license"),
            repository=data.get("repository"),
            build_timestamp=data.get("build_timestamp"),
            rust_version=data.get("rust_version"),
            sdk_version=data.get("sdk_version"),
            custom=data.get("custom", {}),
        )


# ============================================================================
# ABI Types
# ============================================================================

@dataclass
class TypeInfo:
    """Type information."""
    kind: str
    size: Optional[int] = None
    element: Optional["TypeInfo"] = None
    inner: Optional["TypeInfo"] = None
    elements: Optional[List["TypeInfo"]] = None
    name: Optional[str] = None
    fields: Optional[List[Tuple[str, "TypeInfo"]]] = None

    @staticmethod
    def from_dict(data: Dict[str, Any]) -> "TypeInfo":
        kind = data.get("kind", "unknown")
        return TypeInfo(
            kind=kind,
            size=data.get("size"),
            element=TypeInfo.from_dict(data["element"]) if "element" in data else None,
            inner=TypeInfo.from_dict(data["inner"]) if "inner" in data else None,
            elements=[TypeInfo.from_dict(e) for e in data.get("elements", [])] if "elements" in data else None,
            name=data.get("name"),
            fields=[(f[0], TypeInfo.from_dict(f[1])) for f in data.get("fields", [])] if "fields" in data else None,
        )

    def type_name(self) -> str:
        """Get the type name as a string."""
        if self.kind in ("u8", "u16", "u32", "u64", "u128", "i8", "i16", "i32", "i64", "i128",
                         "bool", "string", "bytes", "address", "hash256"):
            return self.kind
        if self.kind == "fixedBytes":
            return f"bytes{self.size}"
        if self.kind == "array" and self.element:
            return f"{self.element.type_name()}[]"
        if self.kind == "fixedArray" and self.element:
            return f"{self.element.type_name()}[{self.size}]"
        if self.kind == "option" and self.inner:
            return f"{self.inner.type_name()}?"
        if self.kind == "tuple" and self.elements:
            return f"({', '.join(e.type_name() for e in self.elements)})"
        if self.kind == "struct" and self.name:
            return self.name
        return self.name or "unknown"


@dataclass
class ParamAbi:
    """Parameter ABI."""
    name: str
    type: TypeInfo
    indexed: bool = False

    @staticmethod
    def from_dict(data: Dict[str, Any]) -> "ParamAbi":
        return ParamAbi(
            name=data.get("name", ""),
            type=TypeInfo.from_dict(data.get("type", {"kind": "unknown"})),
            indexed=data.get("indexed", False),
        )


@dataclass
class FunctionAbi:
    """Function ABI."""
    name: str
    selector: str
    inputs: List[ParamAbi] = field(default_factory=list)
    outputs: List[ParamAbi] = field(default_factory=list)
    view: bool = False
    payable: bool = False
    doc: Optional[str] = None

    @staticmethod
    def from_dict(data: Dict[str, Any]) -> "FunctionAbi":
        return FunctionAbi(
            name=data.get("name", ""),
            selector=data.get("selector", ""),
            inputs=[ParamAbi.from_dict(i) for i in data.get("inputs", [])],
            outputs=[ParamAbi.from_dict(o) for o in data.get("outputs", [])],
            view=data.get("view", False),
            payable=data.get("payable", False),
            doc=data.get("doc"),
        )


@dataclass
class EventAbi:
    """Event ABI."""
    name: str
    topic: str
    params: List[ParamAbi] = field(default_factory=list)
    doc: Optional[str] = None

    @staticmethod
    def from_dict(data: Dict[str, Any]) -> "EventAbi":
        return EventAbi(
            name=data.get("name", ""),
            topic=data.get("topic", ""),
            params=[ParamAbi.from_dict(p) for p in data.get("params", [])],
            doc=data.get("doc"),
        )


@dataclass
class ErrorAbi:
    """Error ABI."""
    name: str
    selector: str
    params: List[ParamAbi] = field(default_factory=list)
    doc: Optional[str] = None

    @staticmethod
    def from_dict(data: Dict[str, Any]) -> "ErrorAbi":
        return ErrorAbi(
            name=data.get("name", ""),
            selector=data.get("selector", ""),
            params=[ParamAbi.from_dict(p) for p in data.get("params", [])],
            doc=data.get("doc"),
        )


@dataclass
class ContractAbi:
    """Contract ABI (Application Binary Interface)."""
    name: str
    version: str
    functions: List[FunctionAbi] = field(default_factory=list)
    events: List[EventAbi] = field(default_factory=list)
    errors: List[ErrorAbi] = field(default_factory=list)

    @staticmethod
    def from_dict(data: Dict[str, Any]) -> "ContractAbi":
        return ContractAbi(
            name=data.get("name", ""),
            version=data.get("version", "1.0.0"),
            functions=[FunctionAbi.from_dict(f) for f in data.get("functions", [])],
            events=[EventAbi.from_dict(e) for e in data.get("events", [])],
            errors=[ErrorAbi.from_dict(e) for e in data.get("errors", [])],
        )

    def find_function(self, name: str) -> Optional[FunctionAbi]:
        """Find a function by name."""
        return next((f for f in self.functions if f.name == name), None)

    def find_by_selector(self, selector: str) -> Optional[FunctionAbi]:
        """Find a function by selector."""
        return next((f for f in self.functions if f.selector == selector), None)


@dataclass
class CompilationResult:
    """Compilation result."""
    contract_id: str
    code: str  # Base64 encoded
    code_hash: str
    original_size: int
    optimized_size: int
    size_reduction: float
    metadata: Optional[ContractMetadata] = None
    abi: Optional[ContractAbi] = None
    estimated_deploy_gas: int = 0
    validation: Optional[ValidationResult] = None
    warnings: List[str] = field(default_factory=list)

    @staticmethod
    def from_dict(data: Dict[str, Any]) -> "CompilationResult":
        return CompilationResult(
            contract_id=data.get("contract_id", ""),
            code=data.get("code", ""),
            code_hash=data.get("code_hash", ""),
            original_size=data.get("original_size", 0),
            optimized_size=data.get("optimized_size", 0),
            size_reduction=data.get("size_reduction", 0.0),
            metadata=ContractMetadata.from_dict(data["metadata"]) if "metadata" in data and data["metadata"] else None,
            abi=ContractAbi.from_dict(data["abi"]) if "abi" in data and data["abi"] else None,
            estimated_deploy_gas=data.get("estimated_deploy_gas", 0),
            validation=ValidationResult.from_dict(data["validation"]) if "validation" in data and data["validation"] else None,
            warnings=data.get("warnings", []),
        )

    def size_stats(self) -> str:
        """Returns size statistics as a formatted string."""
        return f"Original: {self.original_size} bytes, Optimized: {self.optimized_size} bytes, Reduction: {self.size_reduction:.1f}%"


# ============================================================================
# Source Map Types
# ============================================================================

@dataclass
class SourceMapping:
    """Source mapping entry."""
    wasm_offset: int
    line: int
    column: int
    function: Optional[str] = None

    @staticmethod
    def from_dict(data: Dict[str, Any]) -> "SourceMapping":
        return SourceMapping(
            wasm_offset=data.get("wasm_offset", 0),
            line=data.get("line", 0),
            column=data.get("column", 0),
            function=data.get("function"),
        )


@dataclass
class SourceMap:
    """Source map for debugging."""
    file: str
    mappings: List[SourceMapping] = field(default_factory=list)

    @staticmethod
    def from_dict(data: Dict[str, Any]) -> "SourceMap":
        return SourceMap(
            file=data.get("file", ""),
            mappings=[SourceMapping.from_dict(m) for m in data.get("mappings", [])],
        )


# ============================================================================
# Analysis Types
# ============================================================================

@dataclass
class SizeBreakdown:
    """Size breakdown."""
    code: int = 0
    data: int = 0
    types: int = 0
    functions: int = 0
    memory: int = 0
    table: int = 0
    exports: int = 0
    imports: int = 0
    custom: int = 0
    total: int = 0

    @staticmethod
    def from_dict(data: Dict[str, Any]) -> "SizeBreakdown":
        return SizeBreakdown(
            code=data.get("code", 0),
            data=data.get("data", 0),
            types=data.get("types", 0),
            functions=data.get("functions", 0),
            memory=data.get("memory", 0),
            table=data.get("table", 0),
            exports=data.get("exports", 0),
            imports=data.get("imports", 0),
            custom=data.get("custom", 0),
            total=data.get("total", 0),
        )


@dataclass
class FunctionAnalysis:
    """Function analysis."""
    name: str
    size: int
    instruction_count: int
    local_count: int
    exported: bool
    estimated_gas: int

    @staticmethod
    def from_dict(data: Dict[str, Any]) -> "FunctionAnalysis":
        return FunctionAnalysis(
            name=data.get("name", ""),
            size=data.get("size", 0),
            instruction_count=data.get("instruction_count", 0),
            local_count=data.get("local_count", 0),
            exported=data.get("exported", False),
            estimated_gas=data.get("estimated_gas", 0),
        )


@dataclass
class ImportAnalysis:
    """Import analysis."""
    module: str
    name: str
    kind: str  # function, memory, table, global
    signature: Optional[str] = None

    @staticmethod
    def from_dict(data: Dict[str, Any]) -> "ImportAnalysis":
        return ImportAnalysis(
            module=data.get("module", ""),
            name=data.get("name", ""),
            kind=data.get("kind", "function"),
            signature=data.get("signature"),
        )


@dataclass
class SecurityIssue:
    """Security issue."""
    severity: str  # low, medium, high, critical
    type: str
    description: str
    location: Optional[str] = None

    @staticmethod
    def from_dict(data: Dict[str, Any]) -> "SecurityIssue":
        return SecurityIssue(
            severity=data.get("severity", "low"),
            type=data.get("type", ""),
            description=data.get("description", ""),
            location=data.get("location"),
        )


@dataclass
class SecurityAnalysis:
    """Security analysis."""
    score: int  # 0-100
    issues: List[SecurityIssue] = field(default_factory=list)
    recommendations: List[str] = field(default_factory=list)

    @staticmethod
    def from_dict(data: Dict[str, Any]) -> "SecurityAnalysis":
        return SecurityAnalysis(
            score=data.get("score", 0),
            issues=[SecurityIssue.from_dict(i) for i in data.get("issues", [])],
            recommendations=data.get("recommendations", []),
        )


@dataclass
class GasAnalysis:
    """Gas analysis."""
    deployment_gas: int
    function_gas: Dict[str, int] = field(default_factory=dict)
    memory_init_gas: int = 0
    data_section_gas: int = 0

    @staticmethod
    def from_dict(data: Dict[str, Any]) -> "GasAnalysis":
        return GasAnalysis(
            deployment_gas=data.get("deployment_gas", 0),
            function_gas=data.get("function_gas", {}),
            memory_init_gas=data.get("memory_init_gas", 0),
            data_section_gas=data.get("data_section_gas", 0),
        )


@dataclass
class ContractAnalysis:
    """Contract analysis result."""
    size_breakdown: SizeBreakdown
    functions: List[FunctionAnalysis] = field(default_factory=list)
    imports: List[ImportAnalysis] = field(default_factory=list)
    security: Optional[SecurityAnalysis] = None
    gas_analysis: Optional[GasAnalysis] = None

    @staticmethod
    def from_dict(data: Dict[str, Any]) -> "ContractAnalysis":
        return ContractAnalysis(
            size_breakdown=SizeBreakdown.from_dict(data.get("size_breakdown", {})),
            functions=[FunctionAnalysis.from_dict(f) for f in data.get("functions", [])],
            imports=[ImportAnalysis.from_dict(i) for i in data.get("imports", [])],
            security=SecurityAnalysis.from_dict(data["security"]) if "security" in data and data["security"] else None,
            gas_analysis=GasAnalysis.from_dict(data["gas_analysis"]) if "gas_analysis" in data and data["gas_analysis"] else None,
        )


# ============================================================================
# Error Types
# ============================================================================

class CompilerError(Exception):
    """Compiler error."""

    def __init__(
        self,
        message: str,
        code: Optional[str] = None,
        http_status: Optional[int] = None,
        details: Optional[Dict[str, Any]] = None,
    ):
        super().__init__(message)
        self.code = code
        self.http_status = http_status
        self.details = details or {}


# ============================================================================
# Constants
# ============================================================================

MAX_CONTRACT_SIZE = 256 * 1024  # 256 KB
MAX_MEMORY_PAGES = 16

DEFAULT_STRIP_OPTIONS = StripOptions()

DEFAULT_CONFIG: Dict[str, Any] = {
    "endpoint": "https://compiler.synor.io/v1",
    "timeout": 60000,
    "retries": 3,
    "debug": False,
    "optimization_level": OptimizationLevel.SIZE,
    "use_wasm_opt": True,
    "validate": True,
    "extract_metadata": True,
    "generate_abi": True,
}
