"""LLM 语义检查。三级输出：pass / warn / fail。"""

import logging
import re
from typing import Any
from pydantic import BaseModel
from fastapi_modules.fastapi_leaudit.govdoc_engine.engine.checks import register
from fastapi_modules.fastapi_leaudit.govdoc_engine.engine.checks.base import (
    CheckBase, CheckContext, CheckHit, CheckResult,
)
from fastapi_modules.fastapi_leaudit.govdoc_engine.llm.client import LlmClient, LlmJsonError, _format_exc

_log = logging.getLogger(__name__)


_OUT_FORMAT = """
请以 JSON 输出：
{"result": "pass|warn|fail", "reason": "<简短理由>", "suggestion": "<改进建议；pass 时填空>"}
"""

_VAR_RE = re.compile(r"\{\{\s*([^}]+?)\s*\}\}")


def _resolve_dot_path(root: Any, path: str) -> str:
    """点语法属性访问：title.style.font_eastasia → entities['title'].style.font_eastasia。"""
    cur: Any = root
    for seg in path.split("."):
        if cur is None:
            return ""
        if isinstance(cur, dict):
            cur = cur.get(seg)
        elif isinstance(cur, BaseModel):
            cur = getattr(cur, seg, None)
        else:
            cur = getattr(cur, seg, None)
    if cur is None:
        return ""
    if isinstance(cur, (dict, list)):
        return str(cur)
    return str(cur)


def _interpolate(
    template: str,
    paragraphs: list,
    entities: dict | None = None,
    target: Any = None,
) -> str:
    """渲染顺序：① paragraphs[N] ② entities 点语法
    ③ target 隐式（无前缀时视为 target.<key>）。"""
    entities = entities or {}

    def repl(m):
        key = m.group(1).strip()
        # ① paragraphs[N] 索引
        if key.startswith("paragraphs["):
            try:
                idx = int(key[len("paragraphs["):].rstrip("]"))
                return paragraphs[idx].text
            except (ValueError, IndexError):
                return ""
        # ② entities 点语法：title.text / title.style.font_eastasia
        head, _, rest = key.partition(".")
        if head in entities:
            entity = entities[head]
            if entity is None:
                return ""
            return _resolve_dot_path(entity, rest) if rest else entity.text
        # ③ target 隐式：未带前缀且 target 存在
        if target is not None:
            v = _resolve_dot_path(target, key)
            if v:
                return v
        return ""

    return _VAR_RE.sub(repl, template)


@register("ai")
class AiCheck(CheckBase):
    def __init__(self, llm_client: LlmClient | None = None):
        self.client = llm_client or LlmClient()

    def _build_prompt(self, ctx: CheckContext) -> str:
        prompt = _interpolate(
            ctx.stage.prompt or "",
            ctx.paragraphs,
            ctx.entities,
            ctx.target,
        )
        return prompt + "\n\n" + _OUT_FORMAT

    def _interpret(self, ctx: CheckContext, resp: dict) -> CheckResult:
        result = resp.get("result", "fail")
        reason = resp.get("reason", "")
        suggestion = resp.get("suggestion", "")
        if result == "pass":
            return CheckResult(passed=True, hits=[])
        target_p = ctx.paragraphs[0] if ctx.paragraphs else None
        confidence = 0.95 if result == "fail" else 0.7
        return CheckResult(passed=False, hits=[CheckHit(
            paragraph=target_p,
            char_start=0,
            char_end=len(target_p.text) if target_p else 0,
            actual={"llm_reason": reason, "llm_suggestion": suggestion},
            expected={},
            message=reason or "LLM 判定不通过",
            confidence=confidence,
        )])

    def run(self, ctx: CheckContext) -> CheckResult:
        label = f"ai_{ctx.rule_id or 'unknown'}"
        try:
            resp = self.client.chat_json(
                [{"role": "user", "content": self._build_prompt(ctx)}],
                label=label,
            )
        except LlmJsonError as e:
            _log.warning("AI check skipped (LLM JSON error): %s", _format_exc(e))
            return CheckResult(
                passed=True, hits=[], skipped=True,
                skip_reason=f"LLM 返回内容无法解析为 JSON：{e}",
            )
        except Exception as e:
            _log.warning("AI check skipped (LLM error): %s", _format_exc(e))
            return CheckResult(
                passed=True, hits=[], skipped=True,
                skip_reason=f"LLM 调用失败：{e}",
            )
        return self._interpret(ctx, resp)

    async def run_async(self, ctx: CheckContext) -> CheckResult:
        label = f"ai_{ctx.rule_id or 'unknown'}"
        try:
            resp = await self.client.chat_json_async(
                [{"role": "user", "content": self._build_prompt(ctx)}],
                label=label,
            )
        except LlmJsonError as e:
            _log.warning("AI check skipped (LLM JSON error): %s", _format_exc(e))
            return CheckResult(
                passed=True, hits=[], skipped=True,
                skip_reason=f"LLM 返回内容无法解析为 JSON：{e}",
            )
        except Exception as e:
            _log.warning("AI check skipped (LLM error): %s", _format_exc(e))
            return CheckResult(
                passed=True, hits=[], skipped=True,
                skip_reason=f"LLM 调用失败：{e}",
            )
        return self._interpret(ctx, resp)
