"""
OpenClaw Nemo Provider - Phase 24 ADR-052
==========================================
委派 Incident RCA 給 OpenClaw (192.168.0.188) → NVIDIA NIM

搬移自: openclaw.py _call_openclaw_analyze (L263-345)
特性: AI 大腦委派路徑，免費 NVIDIA NIM GPU (~3s)

架構鐵律: AWOOOI API → OpenClaw → NVIDIA NIM (meta/llama-3.1-8b-instruct)

2026-04-02 ogt: Phase 24-A 從 openclaw.py 抽出
"""

from __future__ import annotations

import json as _json
import time
from typing import Any

import httpx
import structlog

from src.core.config import get_settings
from src.services.ai_providers.interfaces import AIResult, is_provider_enabled_by_env

logger = structlog.get_logger(__name__)
settings = get_settings()


def _to_serializable(obj: Any) -> Any:
    """
    遞迴轉換不可序列化的物件 (e.g., datetime)

    2026-04-01 ogt: signals dict 含 datetime → httpx json= 序列化失敗
    此函數確保所有資料都可以被 JSON 序列化
    """
    if isinstance(obj, dict):
        return {k: _to_serializable(v) for k, v in obj.items()}
    if isinstance(obj, list):
        return [_to_serializable(i) for i in obj]
    try:
        _json.dumps(obj)
        return obj
    except (TypeError, ValueError):
        return str(obj)


def _confidence_value(data: dict[str, Any]) -> float:
    try:
        return float(data.get("confidence", 0.0))
    except (TypeError, ValueError):
        return 0.0


def _is_degraded_response(data: dict[str, Any]) -> bool:
    """
    OpenClaw may return a syntactically valid degraded proposal when its
    downstream NIM/Ollama call timed out. Treat that as fallbackable so Gemini
    and Claude remain available instead of accepting a low-confidence stub.
    """
    provider = str(data.get("provider", ""))
    return bool(data.get("degraded")) or provider == "openclaw_degraded" or _confidence_value(data) < 0.3


class OpenClawNemoProvider:
    """
    OpenClaw 委派 Provider (188 → NVIDIA NIM)

    privacy_level: cloud (NIM 是雲端 GPU，首席架構師 Q2 裁示)
    capabilities: rca (Incident 仲裁專用)

    呼叫路徑:
    AWOOOI API (K8s) → OpenClaw (192.168.0.188:8088) → NVIDIA NIM → 回傳
    """

    def __init__(self) -> None:
        self._http_client: httpx.AsyncClient | None = None

    async def _get_client(self) -> httpx.AsyncClient:
        if self._http_client is None or self._http_client.is_closed:
            self._http_client = httpx.AsyncClient(
                timeout=httpx.Timeout(130.0, connect=5.0),
            )
        return self._http_client

    @property
    def name(self) -> str:
        return "openclaw_nemo"

    @property
    def is_enabled(self) -> bool:
        return is_provider_enabled_by_env("openclaw_nemo")

    @property
    def capabilities(self) -> set[str]:
        return {"rca"}

    @property
    def privacy_level(self) -> str:
        return "cloud"

    async def analyze(
        self,
        prompt: str,
        context: dict | None = None,
    ) -> AIResult:
        """
        委派 Incident RCA 給 OpenClaw

        注意: 此 Provider 使用 context 中的結構化資料 (incident_id, severity, signals)
        而非純 prompt 字串。如果 context 缺少這些欄位，會 fallback 到用 prompt。
        """
        start = time.perf_counter()
        context = context or {}

        try:
            client = await self._get_client()

            # 如果有結構化 context，使用 /api/v1/analyze/incident
            if context.get("incident_id"):
                payload = {
                    "incident_id": context["incident_id"],
                    "severity": context.get("severity", "P3"),
                    "signals": _to_serializable(context.get("signals", [])[:5]),
                    "affected_services": context.get("affected_services", []),
                    "expert_context": _to_serializable(context.get("expert_context")) if context.get("expert_context") else None,
                }
                resp = await client.post(
                    f"{settings.OPENCLAW_URL}/api/v1/analyze/incident",
                    json=payload,
                )
            else:
                # Fallback: 用 prompt 作為通用分析
                resp = await client.post(
                    f"{settings.OPENCLAW_URL}/api/v1/analyze/incident",
                    json={
                        "incident_id": "UNKNOWN",
                        "severity": "P3",
                        "signals": [{"alert_name": "manual_analysis", "description": prompt[:500]}],
                        "affected_services": ["unknown"],
                    },
                )

            resp.raise_for_status()
            data = resp.json()

            # 驗證必要欄位
            if not data.get("action_title") or not data.get("risk_level"):
                latency = (time.perf_counter() - start) * 1000
                logger.warning("openclaw_nemo_invalid_response", data_keys=list(data.keys()))
                return AIResult(raw_response="", success=False, provider=self.name, latency_ms=latency, error="Invalid response structure")

            # 轉換為 JSON 字串 (AIResult.raw_response 是 str)
            result_json = _json.dumps(data, ensure_ascii=False)
            latency = (time.perf_counter() - start) * 1000

            if _is_degraded_response(data):
                reason = str(data.get("reasoning") or data.get("description") or "OpenClaw degraded response")
                logger.warning(
                    "openclaw_nemo_degraded_response",
                    provider=data.get("provider"),
                    confidence=data.get("confidence"),
                    latency_ms=round(latency, 1),
                )
                return AIResult(
                    raw_response=result_json,
                    success=False,
                    provider=self.name,
                    latency_ms=latency,
                    error=f"OpenClaw degraded: {reason[:200]}",
                )

            logger.info(
                "openclaw_nemo_provider_success",
                confidence=data.get("confidence", 0),
                risk_level=data.get("risk_level"),
                latency_ms=round(latency, 1),
            )

            return AIResult(
                raw_response=result_json,
                success=True,
                provider=self.name,
                latency_ms=latency,
            )

        except Exception as e:
            latency = (time.perf_counter() - start) * 1000
            logger.warning("openclaw_nemo_provider_failed", error=str(e), latency_ms=round(latency, 1))
            return AIResult(raw_response="", success=False, provider=self.name, latency_ms=latency, error=str(e))

    async def health_check(self) -> bool:
        try:
            client = await self._get_client()
            resp = await client.get(f"{settings.OPENCLAW_URL}/health", timeout=5.0)
            return resp.status_code == 200
        except Exception:
            return False

    async def close(self) -> None:
        if self._http_client:
            await self._http_client.aclose()
            self._http_client = None
