"""LLM-based scoring evaluation rule."""

import json

import httpx

from agenteval.evaluation.rules.base import EvalRule, RuleResult, register_rule
from agenteval.models import Case, Turn
from agenteval.utils.llm import extract_content_from_llm_response, extract_reply_text, parse_json_from_llm_text


@register_rule
class LlmScoreRule(EvalRule):
    """Use an external LLM to score reply quality against criteria."""

    name = "llm_score"

    async def evaluate(self, case: Case, dialog: list[Turn]) -> RuleResult:
        if not dialog:
            return RuleResult(passed=False, reason="无回复记录")

        last_turn = dialog[-1]
        reply_text = extract_reply_text(last_turn.reply)

        # 用户问题取自当前轮发送的消息（sent_message），而非上一轮的智能体回复。
        # 旧逻辑用 dialog[-2].reply 会把「上一轮 AI 回复」误当成「用户问题」，
        # 导致多轮/动态用例里评分 LLM 收到牛头不对马嘴的问答对，普遍打 0 分。
        question_text = ""
        if last_turn.sent_message:
            body = last_turn.sent_message.get("msgBody", "")
            if isinstance(body, dict):
                question_text = body.get("content", "")
            else:
                try:
                    question_text = json.loads(body).get("content", "")
                except Exception:
                    question_text = str(body)

        criteria = self.params.get("criteria", "")
        min_score = float(self.params.get("min_score", 7))
        if self.model_config and self.gateway:
            score, reason = await self._call_gateway(question_text, reply_text, criteria)
        else:
            api_url = self.params.get("api_url")
            api_key = self.params.get("api_key")
            model = self.params.get("model", "gpt-4o-mini")
            if not api_url:
                return RuleResult(passed=False, reason="LLM 评分规则未绑定评估模型（兼容配置缺少 api_url）")
            score, reason = await self._call_llm(api_url, api_key, model, question_text, reply_text, criteria)
        if score is None:
            return RuleResult(passed=False, reason=f"LLM 评分失败: {reason}")

        passed = score >= min_score
        verdict = "通过" if passed else "未通过"
        detail = f"；{reason}" if reason else ""
        return RuleResult(
            passed=passed,
            score=score / 10.0,
            reason=f"LLM 评分 {score}/10，{verdict} (阈值 {min_score}){detail}",
        )

    async def _call_gateway(self, question: str, reply: str, criteria: str) -> tuple[float | None, str]:
        system_prompt, user_prompt = self._prompts(question, reply, criteria)
        try:
            content = await self.gateway.chat(
                self.model_config,
                [
                    {"role": "system", "content": system_prompt},
                    {"role": "user", "content": user_prompt},
                ],
                temperature=0.2,
            )
            return self._parse_score(content)
        except Exception as exc:
            return None, str(exc)

    @staticmethod
    def _prompts(question: str, reply: str, criteria: str) -> tuple[str, str]:
        system_prompt = (
            "你是一位严格的智能客服质量评估专家。请根据用户问题和智能体回复，"
            f"按照以下标准打分（0-10分，10分最高）：{criteria}\n"
            '只输出一个 JSON 对象：{"score": number, "reason": "简短说明"}'
        )
        return system_prompt, f"用户问题：{question}\n智能体回复：{reply}"

    @staticmethod
    def _parse_score(content: str) -> tuple[float, str]:
        parsed = parse_json_from_llm_text(content)
        score = float(parsed["score"])
        return max(0.0, min(10.0, score)), parsed.get("reason", "")

    async def _call_llm(
        self,
        api_url: str,
        api_key: str | None,
        model: str,
        question: str,
        reply: str,
        criteria: str,
    ) -> tuple[float | None, str]:
        """Call the configured LLM API and parse a numeric score between 0 and 10."""
        system_prompt, user_prompt = self._prompts(question, reply, criteria)

        headers = {"Content-Type": "application/json"}
        if api_key:
            headers["Authorization"] = f"Bearer {api_key}"

        payload = {
            "model": model,
            "messages": [
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": user_prompt},
            ],
            "temperature": 0.2,
        }

        try:
            async with httpx.AsyncClient(timeout=60) as client:
                resp = await client.post(api_url, headers=headers, json=payload)
            resp.raise_for_status()
            content = extract_content_from_llm_response(resp.json())
            if not content:
                return None, "LLM 返回内容为空"

            return self._parse_score(content)
        except Exception as exc:
            return None, str(exc)
