"""OpenCode Go provider adapter for multi-provider router."""
from __future__ import annotations

import json
import logging
import os
import re
import urllib.request
import urllib.error
from typing import Any, Dict, List, Optional

from ..router_config import RouterProfileConfig
from .base_adapter import BaseProviderAdapter, ErrorCategory, ErrorClassification

logger = logging.getLogger(__name__)

DEFAULT_OPENCODE_GO_BASE_URL = "https://opencode.ai/zen/go/v1"
DEFAULT_OPENCODE_MODELS = [
    "kimi-k2.7-code",
    "deepseek-v4-pro",
    "deepseek-v4-flash",
    "qwen3.8-max",
    "qwen3.7-max",
    "qwen3.7-plus",
    "grok-4.5",
    "glm-5.3",
    "mimo-v2.5-pro",
    "minimax-m3",
]


class OpenCodeGoAdapter(BaseProviderAdapter):
    """Adapter for OpenCode Go with support for multi-account key pools and reasoning knobs."""

    def _resolve_api_key(self, profile: RouterProfileConfig) -> Optional[str]:
        # 1. Profile explicit auth_config
        if "api_key" in profile.auth_config and profile.auth_config["api_key"]:
            return profile.auth_config["api_key"]

        # 2. Account-specific environment variable (e.g. OPENCODE_GO_KEY_OPENGO_1, OPENCODE_GO_KEY_1)
        suffix = profile.profile_id.upper().replace("-", "_")
        for candidate in (
            f"OPENCODE_GO_KEY_{suffix}",
            f"OPENCODE_KEY_{suffix}",
            f"OPENCODE_GO_API_KEY_{suffix}",
        ):
            if candidate in os.environ and os.environ[candidate].strip():
                return os.environ[candidate].strip()

        # Account ID mapped env
        acc_suffix = profile.account_id.upper().replace("-", "_")
        for candidate in (
            f"OPENCODE_GO_KEY_{acc_suffix}",
            f"OPENCODE_KEY_{acc_suffix}",
        ):
            if candidate in os.environ and os.environ[candidate].strip():
                return os.environ[candidate].strip()

        # 3. Global OpenCode Go keys
        for global_env in ("OPENCODE_GO_API_KEY", "OPENCODE_ZEN_API_KEY", "OPENCODE_API_KEY"):
            if global_env in os.environ and os.environ[global_env].strip():
                return os.environ[global_env].strip()

        return None

    def _build_model_kwargs(self, model: str, request: Dict[str, Any]) -> tuple[dict, dict]:
        """Format reasoning_effort and extra_body thinking based on OpenCode model policies."""
        extra_body: dict[str, Any] = {}
        top_level: dict[str, Any] = {}

        effort = request.get("reasoning_effort")

        if "kimi" in model.lower():
            if effort in ("low", "medium", "high"):
                top_level["reasoning_effort"] = effort
            elif effort in ("xhigh", "max", "ultra"):
                top_level["reasoning_effort"] = "high"
            else:
                extra_body["thinking"] = {"type": "enabled"}
        elif "deepseek" in model.lower():
            if effort in ("xhigh", "max", "ultra"):
                top_level["reasoning_effort"] = "max"
            elif effort in ("low", "medium", "high"):
                top_level["reasoning_effort"] = effort
            else:
                extra_body["thinking"] = {"type": "enabled"}
        elif "glm" in model.lower():
            if effort:
                top_level["reasoning_effort"] = "max" if effort in ("xhigh", "max") else "high"

        return extra_body, top_level

    def invoke(self, profile: RouterProfileConfig, request: Dict[str, Any]) -> Dict[str, Any]:
        api_key = self._resolve_api_key(profile)
        if not api_key:
            raise RuntimeError(f"No API key found for OpenCode Go profile '{profile.profile_id}'")

        base_url = (profile.custom_base_url or os.environ.get("OPENCODE_GO_BASE_URL", DEFAULT_OPENCODE_GO_BASE_URL)).rstrip("/")
        url = f"{base_url}/chat/completions"

        # Model selection
        model = request.get("model", "")
        if not model or model == "default" or "antigravity" in model:
            model = profile.preferred_models[0] if profile.preferred_models else "kimi-k2.7-code"

        extra_body, top_level = self._build_model_kwargs(model, request)

        payload: dict[str, Any] = {
            "model": model,
            "messages": request.get("messages", []),
            "temperature": request.get("temperature", 0.6),
        }
        if "tools" in request and request["tools"]:
            payload["tools"] = request["tools"]
        if "tool_choice" in request:
            payload["tool_choice"] = request["tool_choice"]
        if extra_body:
            payload["extra_body"] = extra_body
        payload.update(top_level)

        body_bytes = json.dumps(payload).encode("utf-8")
        req = urllib.request.Request(
            url,
            data=body_bytes,
            headers={
                "Content-Type": "application/json",
                "Authorization": f"Bearer {api_key}",
                "User-Agent": "hermes-router/1.0",
                "X-OpenCode-Source": "hermes-agent",
            },
            method="POST",
        )

        try:
            with urllib.request.urlopen(req, timeout=120) as resp:
                resp_bytes = resp.read()
                return json.loads(resp_bytes.decode("utf-8", errors="replace"))
        except urllib.error.HTTPError as http_err:
            raw_err = http_err.read().decode("utf-8", errors="replace")
            try:
                err_json = json.loads(raw_err)
            except Exception:
                err_json = {"error": {"message": raw_err}}
            err_msg = err_json.get("error", {}).get("message", raw_err)
            raise RuntimeError(f"OpenCode Go Error ({http_err.code}): {err_msg}") from http_err
        except Exception as e:
            raise RuntimeError(f"OpenCode Go Transport Error: {e}") from e

    def health_check(self, profile: RouterProfileConfig) -> bool:
        return self._resolve_api_key(profile) is not None

    def discover_models(self, profile: RouterProfileConfig) -> List[str]:
        return list(profile.preferred_models or DEFAULT_OPENCODE_MODELS)

    def classify_error(self, exc: Exception, response_data: Optional[Dict[str, Any]] = None) -> ErrorClassification:
        err_msg = str(exc)
        err_lower = err_msg.lower()

        # Quota / balance
        if any(k in err_lower for k in ("quota", "insufficient_quota", "insufficient balance", "balance", "arrears")):
            return ErrorClassification(
                category=ErrorCategory.QUOTA_EXHAUSTED,
                message=err_msg,
                reset_duration_seconds=1800,
            )

        # Rate limited (429)
        if "429" in err_lower or "rate limit" in err_lower or "too many requests" in err_lower:
            return ErrorClassification(
                category=ErrorCategory.RATE_LIMITED,
                message=err_msg,
                retry_delay_seconds=60,
            )

        # Auth errors (401, 403)
        if any(k in err_lower for k in ("401", "403", "unauthorized", "invalid api key", "invalid_token")):
            return ErrorClassification(
                category=ErrorCategory.AUTH_REQUIRED,
                message=err_msg,
            )

        # Transient
        if any(k in err_lower for k in ("timeout", "502", "503", "504", "gateway", "econnreset")):
            return ErrorClassification(
                category=ErrorCategory.TRANSIENT,
                message=err_msg,
                retry_delay_seconds=5,
            )

        return ErrorClassification(
            category=ErrorCategory.FATAL,
            message=err_msg,
        )
