"""
AI Provider 版本探測 — 為每個 Provider 提供 get_version()

每個 probe 函數獨立運作，失敗只影響該 provider，不 crash 整批。

Provider:
  - ollama        : 34.143.170.20 GCP-A Ollama (primary) — 2026-05-03 ogt: ADR-110 GCP-A Primary
  - ollama_local  : 192.168.0.111 / 110 proxy Ollama (local fallback)
  - gemini        : Google Gemini API (版本 = model name)
  - claude        : Anthropic Claude (版本 = model name)
  - openclaw_nemo : OpenClaw NemoTron (版本 = OPENCLAW_DEFAULT_MODEL)

# 2026-04-27 P3.2.1 by Claude
"""
from __future__ import annotations

import asyncio
from dataclasses import dataclass, field
from datetime import datetime, timedelta, timezone

import structlog

from src.core.config import settings

logger = structlog.get_logger(__name__)

TAIPEI_TZ = timezone(timedelta(hours=8))


@dataclass
class ProviderVersionInfo:
    """AI Provider 版本快照"""

    provider: str  # "ollama" / "ollama_local" / "gemini" / "claude" / "openclaw_nemo"
    model: str
    version: str  # version string 或 tag（Ollama 用 modified_at，其他用 model name）
    digest: str | None = None  # SHA256 digest（僅 Ollama 有）
    captured_at: datetime = field(default_factory=lambda: datetime.now(TAIPEI_TZ))


# =============================================================================
# Ollama Probe
# =============================================================================

async def probe_ollama_version(url: str, model: str) -> ProviderVersionInfo:
    """探測 Ollama（GCP-A/GCP-B 或本地 111）：GET /api/tags 取 model digest + modified_at

    Args:
        url:   Ollama base URL，例如 "http://34.143.170.20:11434"（GCP-A Primary）
        model: model name，例如 "qwen2.5:7b-instruct"

    Returns:
        ProviderVersionInfo — provider 依 URL 自動判斷（GCP-A=ollama, 本地111=ollama_local, 否則=ollama_remote）

    Raises:
        ValueError: model 不在清單
        httpx.HTTPError: 連線失敗
    """
    import httpx

    # 2026-05-06 Codex: 188 不再作為 Ollama provider；local fallback 一律標示 ollama_local。
    _GCP_OLLAMA_IPS = {"34.143.170.20", "34.21.145.224"}
    if any(ip in url for ip in _GCP_OLLAMA_IPS):
        provider_name = "ollama"
    elif "192.168.0.111" in url or "192.168.0.110:11437" in url:
        provider_name = "ollama_local"
    else:
        provider_name = "ollama_remote"

    async with httpx.AsyncClient(timeout=5.0) as client:
        resp = await client.get(f"{url}/api/tags")
        resp.raise_for_status()
        models = resp.json().get("models", [])

    for m in models:
        if m.get("name") == model:
            return ProviderVersionInfo(
                provider=provider_name,
                model=model,
                version=m.get("modified_at", ""),
                digest=m.get("digest"),
            )

    raise ValueError(f"Model {model!r} not found at {url}; available: {[m.get('name') for m in models]}")


# =============================================================================
# Gemini Probe
# =============================================================================

async def probe_gemini_version() -> ProviderVersionInfo:
    """探測 Gemini：以設定的 model name 作為版本字串

    Gemini model name 本身即版本識別碼（e.g. "gemini-1.5-flash"），
    不需要額外 API 呼叫。若 GEMINI_API_KEY 存在則視為可用。

    Returns:
        ProviderVersionInfo — version = model name (e.g. "gemini-1.5-flash")

    Raises:
        RuntimeError: GEMINI_API_KEY 未設定
    """
    api_key = settings.GEMINI_API_KEY
    if not api_key:
        raise RuntimeError("GEMINI_API_KEY not configured")

    # Gemini 以 AI_FALLBACK_ORDER 中 "gemini" 的設定決定 model
    # 實際 model name 在 ai_router 層，此處以已知預設值作為版本
    # 透過 list models API 取得最新版本資訊
    import httpx

    async with httpx.AsyncClient(timeout=8.0) as client:
        resp = await client.get(
            "https://generativelanguage.googleapis.com/v1beta/models",
            params={"pageSize": 50},
            headers={"x-goog-api-key": api_key},
        )
        resp.raise_for_status()
        data = resp.json()

    # 找第一個 GENERATE_CONTENT 功能的 gemini 模型版本
    models = data.get("models", [])
    gemini_model = None
    for m in models:
        name = m.get("name", "")
        if "gemini" in name and "generateContent" in m.get("supportedGenerationMethods", []):
            gemini_model = name.replace("models/", "")
            break

    if not gemini_model:
        gemini_model = "gemini-unknown"

    return ProviderVersionInfo(
        provider="gemini",
        model=gemini_model,
        version=gemini_model,
        digest=None,
    )


# =============================================================================
# Claude Probe
# =============================================================================

async def probe_claude_version() -> ProviderVersionInfo:
    """Claude：model name 即版本識別（例如 "claude-haiku-4-5-20251001"）

    Anthropic 沒有 list models endpoint（截至 2026-04），
    以設定中的 claude model name 作為版本字串。
    若 CLAUDE_API_KEY 存在則視為可用。

    Returns:
        ProviderVersionInfo — version = model name（來自設定或預設）

    Raises:
        RuntimeError: CLAUDE_API_KEY 未設定
    """
    api_key = settings.CLAUDE_API_KEY
    if not api_key:
        raise RuntimeError("CLAUDE_API_KEY not configured")

    model_name = "claude-haiku-4-5-20251001"

    return ProviderVersionInfo(
        provider="claude",
        model=model_name,
        version=model_name,
        digest=None,
    )


# =============================================================================
# OpenClaw NemoTron Probe
# =============================================================================

async def probe_openclaw_nemo_version() -> ProviderVersionInfo:
    """OpenClaw NemoTron：版本字串從 settings.OPENCLAW_DEFAULT_MODEL 讀取

    NemoTron 運行在 OpenClaw 節點，
    透過 OPENCLAW_URL /api/tags 探測，模型名稱即版本識別。

    Returns:
        ProviderVersionInfo — version = model tag (e.g. "deepseek-r1:14b")

    Raises:
        RuntimeError: OPENCLAW_DEFAULT_MODEL 未設定
        httpx.HTTPError: 連線失敗
    """
    model = settings.OPENCLAW_DEFAULT_MODEL
    if not model:
        raise RuntimeError("OPENCLAW_DEFAULT_MODEL not configured")

    # OpenClaw 底層是 Ollama，使用 OPENCLAW_URL 的 host:port 加上 Ollama port
    # OPENCLAW_URL 是 8088（OpenClaw API），Ollama 通常在 11434
    # OpenClaw 底層 tags 來源優先使用本地 fallback Ollama URL。
    ollama_local_url = settings.OLLAMA_FALLBACK_URL
    if not ollama_local_url:
        # fallback：從 OPENCLAW_URL host 構建 Ollama URL
        from urllib.parse import urlparse
        parsed = urlparse(settings.OPENCLAW_URL)
        ollama_local_url = f"{parsed.scheme}://{parsed.hostname}:11434"

    import httpx

    async with httpx.AsyncClient(timeout=5.0) as client:
        resp = await client.get(f"{ollama_local_url}/api/tags")
        resp.raise_for_status()
        models = resp.json().get("models", [])

    for m in models:
        if m.get("name") == model:
            return ProviderVersionInfo(
                provider="openclaw_nemo",
                model=model,
                version=m.get("modified_at", model),
                digest=m.get("digest"),
            )

    # model 不在清單時：version 用 model name，digest=None
    logger.warning("openclaw_nemo_model_not_in_tags", model=model, url=ollama_local_url)
    return ProviderVersionInfo(
        provider="openclaw_nemo",
        model=model,
        version=model,
        digest=None,
    )


# =============================================================================
# Probe All
# =============================================================================

async def probe_all_providers() -> list[ProviderVersionInfo]:
    """並行探測所有 5 個 AI Provider，失敗的 provider 以 exception 跳過

    Returns:
        成功探測的 ProviderVersionInfo 列表（長度 0~5）

    Notes:
        - 使用 return_exceptions=True 確保任一 provider 失敗不影響其他
        - 每個 exception 都有對應的 log warning
    """
    tasks = [
        probe_ollama_version(settings.OLLAMA_URL, settings.OLLAMA_HEALTH_CHECK_MODEL),
        probe_ollama_version(
            settings.OLLAMA_FALLBACK_URL or settings.OLLAMA_URL,
            settings.OLLAMA_HEALTH_CHECK_MODEL,
        ),
        probe_gemini_version(),
        probe_claude_version(),
        probe_openclaw_nemo_version(),
    ]

    raw = await asyncio.gather(*tasks, return_exceptions=True)

    results: list[ProviderVersionInfo] = []
    provider_labels = ["ollama", "ollama_local", "gemini", "claude", "openclaw_nemo"]
    for label, outcome in zip(provider_labels, raw, strict=True):
        if isinstance(outcome, ProviderVersionInfo):
            results.append(outcome)
        else:
            logger.warning(
                "provider_probe_failed",
                provider=label,
                error=str(outcome),
            )

    return results
