from __future__ import annotations

from typing import Any, Optional

import httpx
from httpx import HTTPTransport

DEFAULT_CLOUD_URL = "http://localhost:3001"
DEFAULT_LOCAL_PISTON_URL = "http://localhost:2000/api/v2"

DEFAULT_RUN_TIMEOUT = 2000


def _is_local_mode(api_key: Optional[str], base_url: str) -> bool:
    if api_key and api_key.strip():
        return False
    u = base_url.lower()
    return "localhost" in u or "127.0.0.1" in u


class ExecuteResult:
    """Result of a code execution."""

    def __init__(
        self,
        success: bool,
        stdout: str,
        stderr: str,
        exit_code: int,
        run_time_ms: Optional[int] = None,
        error: Optional[str] = None,
        compile_output: Optional[dict] = None,
    ):
        self.success = success
        self.stdout = stdout
        self.stderr = stderr
        self.exit_code = exit_code
        self.run_time_ms = run_time_ms
        self.error = error
        self.compile_output = compile_output

    @classmethod
    def from_response(cls, data: dict) -> ExecuteResult:
        return cls(
            success=data.get("success", False),
            stdout=data.get("stdout", ""),
            stderr=data.get("stderr", ""),
            exit_code=data.get("exitCode", -1),
            run_time_ms=data.get("runTimeMs"),
            error=data.get("error"),
            compile_output=data.get("compile"),
        )

    @classmethod
    def from_piston_response(cls, data: dict) -> ExecuteResult:
        # Piston can return {"message": "..."} on error (e.g. runtime not installed)
        msg = data.get("message")
        if msg:
            return cls(
                success=False,
                stdout="",
                stderr=msg,
                exit_code=-1,
                error=msg,
            )
        run = data.get("run") or {}
        # Piston returns both "stdout" and "output" (stdout+stderr combined); prefer stdout
        stdout = run.get("stdout") or run.get("output") or ""
        stderr = run.get("stderr", "")
        code = run.get("code", -1)
        if code is None:
            code = -1
        compile_out = data.get("compile")
        co = None
        if compile_out:
            co = {"stdout": compile_out.get("stdout", ""), "stderr": compile_out.get("stderr", "")}
        return cls(
            success=(code == 0),
            stdout=stdout,
            stderr=stderr,
            exit_code=code,
            compile_output=co,
        )


class Runtime:
    """A supported language runtime."""

    def __init__(self, language: str, version: str, aliases: Optional[list[str]] = None):
        self.language = language
        self.version = version
        self.aliases = aliases or []

    @classmethod
    def from_dict(cls, d: dict) -> Runtime:
        return cls(
            language=d.get("language", ""),
            version=d.get("version", ""),
            aliases=d.get("aliases"),
        )


class Client:
    """Code Sandbox API client. Use with api_key for cloud, or omit for local Piston."""

    def __init__(self, api_key: Optional[str] = None, base_url: Optional[str] = None):
        self.api_key = (api_key or "").strip()
        if not self.api_key and not base_url:
            base_url = DEFAULT_LOCAL_PISTON_URL
        self.base_url = (base_url or DEFAULT_CLOUD_URL).rstrip("/")
        self._local = _is_local_mode(self.api_key, self.base_url)
        if not self._local and not self.api_key:
            raise ValueError(
                "Code Sandbox: api_key is required for cloud mode. "
                "For local Piston, pass base_url (e.g. http://localhost:2000/api/v2) and omit api_key."
            )

    def _headers(self) -> dict[str, str]:
        h: dict[str, str] = {"Content-Type": "application/json"}
        if self.api_key:
            h["Authorization"] = f"Bearer {self.api_key}"
        return h

    def execute(
        self,
        language: str,
        code: str,
        stdin: Optional[str] = None,
        version: Optional[str] = None,
    ) -> ExecuteResult:
        """Execute code in the given language."""
        if self._local:
            return self._execute_piston(language, code, stdin, version)
        return self._execute_cloud(language, code, stdin, version)

    def _execute_cloud(
        self,
        language: str,
        code: str,
        stdin: Optional[str],
        version: Optional[str],
    ) -> ExecuteResult:
        payload: dict[str, Any] = {
            "language": language,
            "code": code,
            "stdin": stdin or "",
            "version": version or "*",
        }
        transport = HTTPTransport(local_address="0.0.0.0") if _is_local_mode(None, self.base_url) else None
        with httpx.Client(transport=transport) as client:
            resp = client.post(
                f"{self.base_url}/v1/execute",
                json=payload,
                headers=self._headers(),
                timeout=30.0,
            )
        resp.raise_for_status()
        return ExecuteResult.from_response(resp.json())

    def _execute_piston(
        self,
        language: str,
        code: str,
        stdin: Optional[str],
        version: Optional[str],
    ) -> ExecuteResult:
        payload: dict[str, Any] = {
            "language": language,
            "version": version or "*",
            "files": [{"content": code}],
            "stdin": stdin or "",
            "run_timeout": DEFAULT_RUN_TIMEOUT,
        }
        transport = HTTPTransport(local_address="0.0.0.0")
        with httpx.Client(transport=transport) as client:
            resp = client.post(
                f"{self.base_url}/execute",
                json=payload,
                headers=self._headers(),
                timeout=30.0,
            )
        if not resp.is_success:
            err_text = resp.text
            try:
                err_body = resp.json()
                err_text = err_body.get("message", err_text)
            except Exception:
                pass
            err_msg = f"HTTP {resp.status_code}: {err_text}"
            return ExecuteResult(
                success=False,
                stdout="",
                stderr=err_msg,
                exit_code=-1,
                error=err_msg,
            )
        return ExecuteResult.from_piston_response(resp.json())

    def runtimes(self) -> list[Runtime]:
        """List supported runtimes (languages and versions)."""
        if self._local:
            return self._runtimes_piston()
        return self._runtimes_cloud()

    def _runtimes_cloud(self) -> list[Runtime]:
        transport = HTTPTransport(local_address="0.0.0.0") if _is_local_mode(None, self.base_url) else None
        with httpx.Client(transport=transport) as client:
            resp = client.get(
                f"{self.base_url}/v1/runtimes",
                headers=self._headers(),
                timeout=10.0,
            )
        resp.raise_for_status()
        data = resp.json()
        runtimes = data.get("runtimes", [])
        return [Runtime.from_dict(r) for r in runtimes]

    def _runtimes_piston(self) -> list[Runtime]:
        transport = HTTPTransport(local_address="0.0.0.0")
        with httpx.Client(transport=transport) as client:
            resp = client.get(
                f"{self.base_url}/runtimes",
                headers=self._headers(),
                timeout=10.0,
            )
        resp.raise_for_status()
        data = resp.json()
        if isinstance(data, list):
            return [Runtime.from_dict(r) for r in data]
        return [Runtime.from_dict(r) for r in data.get("runtimes", [])]


def create_local_client(base_url: Optional[str] = None) -> Client:
    """
    Create a client for local Piston (Docker). No API key or login required.

    Args:
        base_url: Piston API URL, e.g. http://localhost:2000/api/v2

    Returns:
        Client configured for local Piston.
    """
    url = (base_url or DEFAULT_LOCAL_PISTON_URL).rstrip("/")
    return Client(api_key=None, base_url=url)
