"""题目生成服务（非流式 + 流式 + 评估三道防线集成）.

设计依据 02-architecture-design.md §10 评估三道防线：
  1. RuleValidator：JSON 格式 / 必填字段 / 难度匹配 / 题型校验
  2. LLMJudge：语义层面 5 维度评分
  3. QualityGate：综合决策门控，决定放行/降级/拒绝

降级采用方案 B（总裁裁决 §2.6）：
  - LLM 不可用 → success=true + data 内 degraded=true
  - 评估未通过但可返回 → success=true + data 内 degraded=true
"""

from collections.abc import AsyncGenerator
from dataclasses import dataclass
from typing import Any

import structlog

from ..errors import AILLMUnavailableError
from ..models.question import (
    GeneratedQuestionData,
    GenerateQuestionRequest,
)
from ..prompt_service import PromptTemplateService
from ..providers import ProviderFailoverChain
from .evaluation import QualityGate, RuleValidator

logger = structlog.get_logger()


@dataclass
class QuestionChunkData:
    """流式题目生成 chunk."""

    content: str
    done: bool = False
    complete_question: GeneratedQuestionData | None = None


class QuestionService:
    """题目生成服务.

    编排流程：
      generate():
        1. 渲染 generate_question prompt 模板
        2. 调用 LLM FailoverChain.chat()
        3. RuleValidator + LLMJudge + QualityGate 三道防线评估
        4. 返回 GeneratedQuestionData（含 evaluation_score + degraded 标记）

      stream_generate():
        1. 渲染 prompt 模板
        2. 调用 FailoverChain.stream_chat() 逐字 yield
        3. 流结束后运行 QualityGate 评估，done chunk 携带完整题目
    """

    def __init__(
        self,
        failover_chain: ProviderFailoverChain,
        prompt_service: PromptTemplateService | None = None,
        quality_gate: QualityGate | None = None,
        default_model: str = "gpt-4o-mini",
    ) -> None:
        self._chain = failover_chain
        self._prompts = prompt_service
        self._quality_gate = quality_gate or QualityGate(
            rule_validator=RuleValidator(),
        )
        self._default_model = default_model

    async def generate(
        self,
        request: GenerateQuestionRequest,
        model: str = "",
        temperature: float = 0.7,
        user_id: str = "",
    ) -> GeneratedQuestionData:
        """非流式题目生成.

        Args:
            request: 题目生成请求
            model: 模型名（空则用默认）
            temperature: 温度参数
            user_id: 用户 ID（用于用量记录）

        Returns:
            GeneratedQuestionData（含 evaluation_score + degraded 标记）
        """
        model = model or self._default_model
        system_prompt = self._render_prompt(request)
        messages = [
            {"role": "system", "content": "你是一个专业的教育题目生成助手。"},
            {"role": "user", "content": system_prompt},
        ]

        try:
            response = await self._chain.chat(messages, model, temperature)
        except AILLMUnavailableError as exc:
            logger.warning("question_generate_degraded", reason=str(exc))
            return GeneratedQuestionData(
                question="",
                answer="",
                explanation=f"[degraded] LLM unavailable: {exc}",
                question_type=request.question_type,
                difficulty=request.difficulty,
                knowledge_point_ids=request.knowledge_point_ids,
                evaluation_score=0.0,
                degraded=True,
                degraded_reason=str(exc),
            )

        # 评估三道防线
        evaluation = await self._quality_gate.evaluate(
            llm_output=response.content,
            expected_difficulty=request.difficulty,
            expected_question_type=request.question_type,
            subject=request.subject,
        )

        # 解析 LLM 输出为题目数据
        parsed = (
            evaluation.rule_result.parsed if evaluation.rule_result else None
        )
        if parsed:
            question_data = self._build_question_from_parsed(
                parsed, request, evaluation.score,
            )
        else:
            # JSON 解析失败，返回原始内容作为 question
            question_data = GeneratedQuestionData(
                question=response.content,
                answer="",
                explanation="",
                question_type=request.question_type,
                difficulty=request.difficulty,
                knowledge_point_ids=request.knowledge_point_ids,
                evaluation_score=evaluation.score,
                degraded=True,
                degraded_reason="; ".join(evaluation.errors) or "json parse failed",
            )

        # 评估降级标记透传
        if evaluation.degraded and not question_data.degraded:
            question_data.degraded = True
            question_data.degraded_reason = evaluation.degraded_reason

        logger.info(
            "question_generated",
            score=evaluation.score,
            passed=evaluation.passed,
            degraded=question_data.degraded,
            provider=response.provider,
        )
        return question_data

    async def stream_generate(
        self,
        request: GenerateQuestionRequest,
        model: str = "",
        temperature: float = 0.7,
        user_id: str = "",
    ) -> AsyncGenerator[QuestionChunkData, None]:
        """流式题目生成.

        Yields:
            QuestionChunkData（逐字 content + done 时携带 complete_question）
        """
        model = model or self._default_model
        system_prompt = self._render_prompt(request)
        messages = [
            {"role": "system", "content": "你是一个专业的教育题目生成助手。"},
            {"role": "user", "content": system_prompt},
        ]

        accumulated = ""
        try:
            async for chunk in self._chain.stream_chat(
                messages, model, temperature,
            ):
                accumulated += chunk.delta
                done = chunk.finish_reason in ("stop", "end_turn", "length")
                if not done:
                    yield QuestionChunkData(content=chunk.delta, done=False)

            # 流结束，运行评估
            evaluation = await self._quality_gate.evaluate(
                llm_output=accumulated,
                expected_difficulty=request.difficulty,
                expected_question_type=request.question_type,
                subject=request.subject,
            )
            parsed = (
                evaluation.rule_result.parsed if evaluation.rule_result else None
            )
            if parsed:
                question_data = self._build_question_from_parsed(
                    parsed, request, evaluation.score,
                )
            else:
                question_data = GeneratedQuestionData(
                    question=accumulated,
                    answer="",
                    explanation="",
                    question_type=request.question_type,
                    difficulty=request.difficulty,
                    knowledge_point_ids=request.knowledge_point_ids,
                    evaluation_score=evaluation.score,
                    degraded=True,
                    degraded_reason="; ".join(evaluation.errors) or "json parse failed",
                )
            if evaluation.degraded and not question_data.degraded:
                question_data.degraded = True
                question_data.degraded_reason = evaluation.degraded_reason

            yield QuestionChunkData(
                content="",
                done=True,
                complete_question=question_data,
            )
        except AILLMUnavailableError as exc:
            logger.warning("question_stream_degraded", reason=str(exc))
            yield QuestionChunkData(
                content=f"[degraded] {exc}",
                done=True,
                complete_question=GeneratedQuestionData(
                    question="",
                    answer="",
                    explanation=f"[degraded] LLM unavailable: {exc}",
                    question_type=request.question_type,
                    difficulty=request.difficulty,
                    knowledge_point_ids=request.knowledge_point_ids,
                    evaluation_score=0.0,
                    degraded=True,
                    degraded_reason=str(exc),
                ),
            )
        except Exception as exc:  # noqa: BLE001
            logger.error("question_stream_error", error=str(exc))
            yield QuestionChunkData(
                content=f"[error] {exc}",
                done=True,
                complete_question=GeneratedQuestionData(
                    question="",
                    answer="",
                    explanation=f"[error] {exc}",
                    question_type=request.question_type,
                    difficulty=request.difficulty,
                    knowledge_point_ids=request.knowledge_point_ids,
                    evaluation_score=0.0,
                    degraded=True,
                    degraded_reason=str(exc),
                ),
            )

    def _render_prompt(self, request: GenerateQuestionRequest) -> str:
        """渲染题目生成 prompt 模板."""
        if self._prompts is None:
            return self._fallback_prompt(request)
        try:
            return self._prompts.render(
                "generate_question",
                {
                    "subject": request.subject,
                    "grade": request.grade or "未指定",
                    "difficulty": request.difficulty,
                    "question_type": request.question_type,
                    "knowledge_points": request.knowledge_point_ids or ["基础概念"],
                    "knowledge_point_ids": request.knowledge_point_ids,
                    "count": request.count,
                    "prompt": request.prompt,
                },
            )
        except Exception:  # noqa: BLE001
            return self._fallback_prompt(request)

    def _fallback_prompt(self, request: GenerateQuestionRequest) -> str:
        """模板不可用时的降级 prompt."""
        return (
            f"请生成一道{request.subject}题目，"
            f"难度：{request.difficulty}，题型：{request.question_type}。\n"
            f"要求：{request.prompt}\n"
            "请按 JSON 格式输出："
            '{"question":"...","answer":"...","explanation":"..."}'
        )

    def _build_question_from_parsed(
        self,
        parsed: dict[str, Any],
        request: GenerateQuestionRequest,
        evaluation_score: float,
    ) -> GeneratedQuestionData:
        """从解析后的 dict 构建 GeneratedQuestionData."""
        return GeneratedQuestionData(
            question=str(parsed.get("question", "")),
            answer=str(parsed.get("answer", "")),
            explanation=str(parsed.get("explanation", "")),
            question_type=str(
                parsed.get("question_type", request.question_type),
            ),
            difficulty=str(parsed.get("difficulty", request.difficulty)),
            knowledge_point_ids=list(
                parsed.get("knowledge_point_ids", request.knowledge_point_ids),
            ),
            evaluation_score=evaluation_score,
        )
