"""
Synor Compiler SDK Client

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

import base64
from typing import Any, Dict, List, Optional, Union

import httpx

from .types import (
    OptimizationLevel,
    StripOptions,
    CompilerConfig,
    CompilationResult,
    ValidationResult,
    ContractMetadata,
    ContractAbi,
    FunctionAbi,
    SourceMap,
    ContractAnalysis,
    SecurityAnalysis,
    CompilerError,
    DEFAULT_STRIP_OPTIONS,
)


class SynorCompiler:
    """
    Synor Compiler SDK Client

    Smart contract compilation, optimization, and ABI generation.

    Example:
        >>> compiler = SynorCompiler(CompilerConfig(api_key="your-api-key"))
        >>> result = await compiler.compile(wasm_bytes)
        >>> print(f"Optimized size: {result.optimized_size} bytes")
    """

    def __init__(self, config: CompilerConfig):
        self._config = config
        self._closed = False

        self._client = httpx.AsyncClient(
            base_url=config.endpoint,
            timeout=config.timeout / 1000,  # Convert ms to seconds
            headers={
                "Authorization": f"Bearer {config.api_key}",
                "Content-Type": "application/json",
                "X-SDK-Version": "python/0.1.0",
            },
        )

        # Sub-clients
        self.contracts = ContractsClient(self)
        self.abi = AbiClient(self)
        self.analysis = AnalysisClient(self)
        self.validation = ValidationClient(self)

    @property
    def default_optimization_level(self) -> OptimizationLevel:
        """Get the default optimization level."""
        return self._config.optimization_level

    async def health_check(self) -> bool:
        """Check service health."""
        try:
            result = await self._get("/health")
            return result.get("status") == "healthy"
        except Exception:
            return False

    async def get_info(self) -> Dict[str, Any]:
        """Get service info."""
        return await self._get("/info")

    async def close(self) -> None:
        """Close the client."""
        self._closed = True
        await self._client.aclose()

    @property
    def is_closed(self) -> bool:
        """Check if client is closed."""
        return self._closed

    async def compile(
        self,
        wasm: Union[bytes, str],
        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,
    ) -> CompilationResult:
        """
        Compile WASM bytecode.

        Args:
            wasm: WASM bytecode as bytes or base64 string
            optimization_level: Optimization level
            strip_options: Strip options
            use_wasm_opt: Whether to use wasm-opt
            validate: Whether to validate
            extract_metadata: Whether to extract metadata
            generate_abi: Whether to generate ABI

        Returns:
            Compilation result
        """
        wasm_base64 = wasm if isinstance(wasm, str) else base64.b64encode(wasm).decode()

        body = {
            "wasm": wasm_base64,
            "optimization_level": (optimization_level or self._config.optimization_level).value,
            "strip_options": (strip_options or self._config.strip_options or DEFAULT_STRIP_OPTIONS).to_dict(),
            "use_wasm_opt": use_wasm_opt if use_wasm_opt is not None else self._config.use_wasm_opt,
            "validate": validate if validate is not None else self._config.validate,
            "extract_metadata": extract_metadata if extract_metadata is not None else self._config.extract_metadata,
            "generate_abi": generate_abi if generate_abi is not None else self._config.generate_abi,
        }

        result = await self._post("/compile", body)
        return CompilationResult.from_dict(result)

    async def _get(self, path: str) -> Dict[str, Any]:
        """Internal GET request."""
        self._check_closed()
        response = await self._client.get(path)
        return self._handle_response(response)

    async def _post(self, path: str, body: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
        """Internal POST request."""
        self._check_closed()
        response = await self._client.post(path, json=body or {})
        return self._handle_response(response)

    def _handle_response(self, response: httpx.Response) -> Dict[str, Any]:
        """Handle HTTP response."""
        if not response.is_success:
            try:
                error = response.json()
            except Exception:
                error = {"message": response.text or f"HTTP {response.status_code}"}
            raise CompilerError(
                message=error.get("message", f"HTTP {response.status_code}"),
                code=error.get("code"),
                http_status=response.status_code,
            )
        return response.json()

    def _check_closed(self) -> None:
        """Check if client is closed."""
        if self._closed:
            raise CompilerError("Client has been closed", code="CLIENT_CLOSED")

    async def __aenter__(self) -> "SynorCompiler":
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
        await self.close()


class ContractsClient:
    """Contracts sub-client for contract compilation."""

    def __init__(self, compiler: SynorCompiler):
        self._compiler = compiler

    async def compile(
        self,
        wasm: Union[bytes, str],
        **kwargs,
    ) -> CompilationResult:
        """Compile WASM bytecode with default settings."""
        return await self._compiler.compile(wasm, **kwargs)

    async def compile_dev(self, wasm: Union[bytes, str]) -> CompilationResult:
        """Compile with development settings (fast, minimal optimization)."""
        return await self._compiler.compile(
            wasm,
            optimization_level=OptimizationLevel.NONE,
            use_wasm_opt=False,
            validate=True,
        )

    async def compile_production(self, wasm: Union[bytes, str]) -> CompilationResult:
        """Compile with production settings (aggressive optimization)."""
        return await self._compiler.compile(
            wasm,
            optimization_level=OptimizationLevel.AGGRESSIVE,
            use_wasm_opt=True,
            validate=True,
            extract_metadata=True,
            generate_abi=True,
        )

    async def get(self, contract_id: str) -> CompilationResult:
        """Get a compiled contract by ID."""
        result = await self._compiler._get(f"/contracts/{contract_id}")
        return CompilationResult.from_dict(result)

    async def list(
        self,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
    ) -> List[CompilationResult]:
        """List compiled contracts."""
        params = []
        if limit is not None:
            params.append(f"limit={limit}")
        if offset is not None:
            params.append(f"offset={offset}")
        query = "?" + "&".join(params) if params else ""

        result = await self._compiler._get(f"/contracts{query}")
        return [CompilationResult.from_dict(c) for c in result.get("contracts", [])]

    async def get_code(self, contract_id: str) -> bytes:
        """Get optimized bytecode for a contract."""
        result = await self._compiler._get(f"/contracts/{contract_id}/code")
        return base64.b64decode(result.get("code", ""))


class AbiClient:
    """ABI sub-client for ABI generation and encoding."""

    def __init__(self, compiler: SynorCompiler):
        self._compiler = compiler

    async def extract(self, wasm: Union[bytes, str]) -> ContractAbi:
        """Extract ABI from WASM bytecode."""
        wasm_base64 = wasm if isinstance(wasm, str) else base64.b64encode(wasm).decode()
        result = await self._compiler._post("/abi/extract", {"wasm": wasm_base64})
        return ContractAbi.from_dict(result)

    async def get(self, contract_id: str) -> ContractAbi:
        """Get ABI for a compiled contract."""
        result = await self._compiler._get(f"/contracts/{contract_id}/abi")
        return ContractAbi.from_dict(result)

    async def encode_call(self, function_abi: FunctionAbi, args: List[Any]) -> str:
        """Encode function call."""
        result = await self._compiler._post("/abi/encode", {
            "function": {
                "name": function_abi.name,
                "selector": function_abi.selector,
                "inputs": [{"name": i.name, "type": {"kind": i.type.kind}} for i in function_abi.inputs],
                "outputs": [{"name": o.name, "type": {"kind": o.type.kind}} for o in function_abi.outputs],
            },
            "args": args,
        })
        return result.get("data", "")

    async def decode_result(self, function_abi: FunctionAbi, data: str) -> List[Any]:
        """Decode function result."""
        result = await self._compiler._post("/abi/decode", {
            "function": {
                "name": function_abi.name,
                "selector": function_abi.selector,
                "inputs": [{"name": i.name, "type": {"kind": i.type.kind}} for i in function_abi.inputs],
                "outputs": [{"name": o.name, "type": {"kind": o.type.kind}} for o in function_abi.outputs],
            },
            "data": data,
        })
        return result.get("values", [])

    def compute_selector(self, function_name: str) -> str:
        """Compute function selector."""
        import hashlib
        h = hashlib.blake2b(function_name.encode(), digest_size=4)
        return h.hexdigest()

    async def generate_types(self, abi: ContractAbi, language: str = "python") -> str:
        """Generate types from ABI."""
        result = await self._compiler._post("/abi/generate-types", {
            "abi": {
                "name": abi.name,
                "version": abi.version,
                "functions": [{"name": f.name, "selector": f.selector} for f in abi.functions],
            },
            "language": language,
        })
        return result.get(language, "")


class AnalysisClient:
    """Analysis sub-client for contract analysis."""

    def __init__(self, compiler: SynorCompiler):
        self._compiler = compiler

    async def analyze(self, wasm: Union[bytes, str]) -> ContractAnalysis:
        """Analyze WASM bytecode."""
        wasm_base64 = wasm if isinstance(wasm, str) else base64.b64encode(wasm).decode()
        result = await self._compiler._post("/analysis", {"wasm": wasm_base64})
        return ContractAnalysis.from_dict(result)

    async def get(self, contract_id: str) -> ContractAnalysis:
        """Get analysis for a compiled contract."""
        result = await self._compiler._get(f"/contracts/{contract_id}/analysis")
        return ContractAnalysis.from_dict(result)

    async def extract_metadata(self, wasm: Union[bytes, str]) -> ContractMetadata:
        """Extract metadata from WASM bytecode."""
        wasm_base64 = wasm if isinstance(wasm, str) else base64.b64encode(wasm).decode()
        result = await self._compiler._post("/analysis/metadata", {"wasm": wasm_base64})
        return ContractMetadata.from_dict(result)

    async def extract_source_map(self, wasm: Union[bytes, str]) -> Optional[SourceMap]:
        """Extract source map from WASM bytecode."""
        wasm_base64 = wasm if isinstance(wasm, str) else base64.b64encode(wasm).decode()
        result = await self._compiler._post("/analysis/source-map", {"wasm": wasm_base64})
        source_map = result.get("source_map")
        return SourceMap.from_dict(source_map) if source_map else None

    async def estimate_deploy_gas(self, wasm: Union[bytes, str]) -> int:
        """Estimate gas for contract deployment."""
        wasm_base64 = wasm if isinstance(wasm, str) else base64.b64encode(wasm).decode()
        result = await self._compiler._post("/analysis/estimate-gas", {"wasm": wasm_base64})
        return result.get("gas", 0)

    async def security_scan(self, wasm: Union[bytes, str]) -> SecurityAnalysis:
        """Get security analysis."""
        wasm_base64 = wasm if isinstance(wasm, str) else base64.b64encode(wasm).decode()
        result = await self._compiler._post("/analysis/security", {"wasm": wasm_base64})
        return SecurityAnalysis.from_dict(result.get("security", {}))


class ValidationClient:
    """Validation sub-client for WASM validation."""

    def __init__(self, compiler: SynorCompiler):
        self._compiler = compiler

    async def validate(self, wasm: Union[bytes, str]) -> ValidationResult:
        """Validate WASM bytecode against VM requirements."""
        wasm_base64 = wasm if isinstance(wasm, str) else base64.b64encode(wasm).decode()
        result = await self._compiler._post("/validate", {"wasm": wasm_base64})
        return ValidationResult.from_dict(result)

    async def is_valid(self, wasm: Union[bytes, str]) -> bool:
        """Check if WASM is valid."""
        result = await self.validate(wasm)
        return result.valid

    async def get_errors(self, wasm: Union[bytes, str]) -> List[str]:
        """Get validation errors."""
        result = await self.validate(wasm)
        return [e.message for e in result.errors]

    async def validate_exports(
        self,
        wasm: Union[bytes, str],
        required_exports: List[str],
    ) -> bool:
        """Validate contract exports."""
        wasm_base64 = wasm if isinstance(wasm, str) else base64.b64encode(wasm).decode()
        result = await self._compiler._post("/validate/exports", {
            "wasm": wasm_base64,
            "required_exports": required_exports,
        })
        return result.get("valid", False)

    async def validate_memory(
        self,
        wasm: Union[bytes, str],
        max_pages: int,
    ) -> bool:
        """Validate memory limits."""
        wasm_base64 = wasm if isinstance(wasm, str) else base64.b64encode(wasm).decode()
        result = await self._compiler._post("/validate/memory", {
            "wasm": wasm_base64,
            "max_pages": max_pages,
        })
        return result.get("valid", False)
