"""Claude Observer Provider — L2 云端 session 观察

优先级：
1. claude CLI (spawn subprocess) — Max 订阅免费，无需 API key
2. httpx 直调 Anthropic Messages API — 需要 ANTHROPIC_API_KEY，按 token 计费

降级：CLI 不存在 → 有 API key 用 httpx → 都没有则 not available
"""

from __future__ import annotations

import asyncio
import logging
import os
import shutil
from typing import TYPE_CHECKING

from ...config import OBSERVER_MODELS
from ...resilience import ServiceCooldown

if TYPE_CHECKING:
    from ...config import Config

logger = logging.getLogger(__name__)

_API_URL = "https://api.anthropic.com/v1/messages"
_CLI_TIMEOUT = 120.0  # CLI 超时秒数


class ClaudeObserverProvider:
    """Claude — L2 observer provider（CLI 优先, API fallback）

    skip_cli=True 时跳过 CLI spawn — Worker 内 SessionManager 已经用
    持久会话处理 Claude，provider chain 只保留 API fallback 避免重复 spawn。
    """

    def __init__(self, config: Config, *, skip_cli: bool = False):
        self.config = config
        self._cooldown = ServiceCooldown("observer_claude", base_cooldown=60, max_cooldown=300)
        self._client = None
        self._api_key = getattr(config, "anthropic_api_key", "") or os.environ.get(
            "ANTHROPIC_API_KEY", ""
        )
        self._cli_model = getattr(config, "observer_claude_model", "") or OBSERVER_MODELS["claude_cli"]
        self._api_model = getattr(config, "observer_claude_model", "") or OBSERVER_MODELS["claude_api"]

        # skip_cli: Worker 内 SessionManager 已处理 Claude CLI，跳过避免重复 spawn
        if skip_cli:
            self._claude_cli = None
        else:
            self._claude_cli = shutil.which("claude")
            if self._claude_cli:
                logger.debug("Claude CLI found: %s", self._claude_cli)

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

    @property
    def model_name(self) -> str:
        # CLI 优先，有 CLI 返回 CLI 模型名
        if self._claude_cli:
            return self._cli_model
        return self._api_model

    @property
    def available(self) -> bool:
        if self._cooldown.is_cooling:
            return False
        # CLI 可用 OR API key 可用
        return bool(self._claude_cli) or bool(self._api_key)

    # ── CLI 方式（优先）──────────────────────────────────────

    async def _generate_via_cli(self, prompt: str) -> str | None:
        """通过 claude CLI subprocess 调用（Max 订阅免费）"""
        if not self._claude_cli:
            return None

        try:
            # #237 Phase 4: make_clean_env 清除 CLAUDE*/ELECTRON_*/VSCODE_*，
            # 防止 claude CLI 检测嵌套环境拒绝执行（RC1 根因）
            # INDEX1_HOOK_CHILD=1: 递归守卫，子进程 hook 检测后立即 return
            from ...env_utils import make_clean_env
            env = make_clean_env(extra_vars={"INDEX1_HOOK_CHILD": "1"})

            proc = await asyncio.create_subprocess_exec(
                self._claude_cli, "-p", prompt,
                "--model", self._cli_model,
                "--max-turns", "1",
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE,
                env=env,
            )
            stdout, stderr = await asyncio.wait_for(
                proc.communicate(), timeout=_CLI_TIMEOUT,
            )

            if proc.returncode == 0 and stdout:
                return stdout.decode("utf-8", errors="replace").strip()

            logger.warning(
                "claude CLI rc=%d: %s",
                proc.returncode,
                stderr.decode("utf-8", errors="replace")[:200],
            )
            return None

        except asyncio.TimeoutError:
            logger.warning("claude CLI timeout (%.0fs)", _CLI_TIMEOUT)
            try:
                proc.kill()
            except Exception:
                pass
            return None
        except Exception as e:
            logger.warning("claude CLI 调用失败: %s", e)
            return None

    # ── API 方式（fallback）───────────────────────────────────

    async def _get_client(self):
        if self._client is None:
            import httpx
            self._client = httpx.AsyncClient()
        return self._client

    async def _generate_via_api(self, prompt: str) -> str | None:
        """通过 httpx 直调 Anthropic Messages API（需 API key）"""
        if not self._api_key:
            return None

        try:
            client = await self._get_client()
            resp = await client.post(
                _API_URL,
                headers={
                    "x-api-key": self._api_key,
                    "anthropic-version": "2023-06-01",
                    "content-type": "application/json",
                },
                json={
                    "model": self._api_model,
                    "max_tokens": 1024,
                    "messages": [
                        {"role": "user", "content": prompt},
                    ],
                },
                timeout=30.0,
            )
            resp.raise_for_status()

            data = resp.json()
            content_blocks = data.get("content", [])
            raw = ""
            for block in content_blocks:
                if block.get("type") == "text":
                    raw += block.get("text", "")
            return raw

        except Exception as e:
            logger.warning("Claude API 调用失败: %s", e)
            return None

    # ── 统一入口 ──────────────────────────────────────────────

    async def close(self) -> None:
        """关闭 httpx client"""
        if self._client is not None:
            await self._client.aclose()
            self._client = None

    async def generate(self, prompt: str) -> str | None:
        """调用 Claude — CLI 优先，API fallback

        供 SessionObserver.observe_tool_call() 使用，解析在调用方完成。
        """
        if self._cooldown.is_cooling:
            return None

        # 1. CLI 优先（Max 订阅免费）
        if self._claude_cli:
            result = await self._generate_via_cli(prompt)
            if result:
                self._cooldown.record_success()
                return result

        # 2. API fallback（需 API key，按 token 计费）
        if self._api_key:
            result = await self._generate_via_api(prompt)
            if result:
                self._cooldown.record_success()
                return result

        self._cooldown.record_failure()
        return None

    async def observe_session(self, prompt: str) -> list[dict] | None:
        """调用 Claude 执行 session 观察（旧接口，JSON 格式）"""
        raw = await self.generate(prompt)
        if raw is None:
            return None
        return self._parse_response(raw)

    def _parse_response(self, raw: str) -> list[dict] | None:
        """解析 LLM JSON 输出（旧格式）"""
        from .parse import parse_llm_json_array
        return parse_llm_json_array(raw, "claude_observer")
