"""备课工作流服务测试（LessonPlanWorkflowService）.

测试 4 步编排 + 状态机 + 降级路径。
使用 WorkflowStateStore(redis=None) 内存降级模式 + MockProvider + Mock 客户端。
"""

import json
from unittest.mock import AsyncMock, MagicMock

import pytest

from src.ai.clients.content_client import ContentClientMock, CreatedQuestion
from src.ai.clients.data_ana_client import DataAnaClientMock
from src.ai.errors import AIWorkflowNotFoundError, AIWorkflowStateInvalidError
from src.ai.models.question import GeneratedQuestionData
from src.ai.prompt_service import PromptTemplateService
from src.ai.providers import ProviderFailoverChain
from src.ai.providers.circuit_breaker import CircuitBreaker
from src.ai.services.evaluation import QualityGate, RuleValidator
from src.ai.workflow.lesson_plan_workflow import LessonPlanWorkflowService
from src.ai.workflow.state_store import WorkflowState, WorkflowStateStore

from .conftest import MockProvider

# 有效的 LLM JSON 输出（通过三道防线评估）
VALID_QUESTION_JSON = json.dumps({
    "question": "什么是函数？",
    "answer": "函数是一种对应关系",
    "explanation": "函数定义",
    "difficulty": "medium",
    "question_type": "short_answer",
})


def _make_chain(provider: MockProvider | None = None) -> ProviderFailoverChain:
    """构建含单个 MockProvider 的 failover chain."""
    return ProviderFailoverChain([provider or MockProvider()], CircuitBreaker())


def _make_state(**overrides: object) -> WorkflowState:
    """构建测试用 WorkflowState."""
    defaults: dict[str, object] = {
        "user_id": "u-1",
        "school_id": "s-1",
        "class_id": "c-1",
        "subject_id": "math",
        "topic": "函数",
        "target_difficulty": "medium",
        "question_count": 1,
    }
    defaults.update(overrides)
    return WorkflowState(**defaults)  # type: ignore[arg-type]


def _make_service(
    store: WorkflowStateStore | None = None,
    provider: MockProvider | None = None,
    content_client: object | None = None,
    data_ana_client: object | None = None,
    prompt_service: PromptTemplateService | MagicMock | None = None,
    quality_gate: QualityGate | None = None,
) -> LessonPlanWorkflowService:
    """构建测试用 LessonPlanWorkflowService."""
    return LessonPlanWorkflowService(
        state_store=store or WorkflowStateStore(redis=None),
        failover_chain=_make_chain(provider),
        prompt_service=prompt_service,
        quality_gate=quality_gate,
        content_client=content_client,  # type: ignore[arg-type]
        data_ana_client=data_ana_client,  # type: ignore[arg-type]
    )


class TestLessonPlanWorkflowStart:
    """start / get_status 测试."""

    async def test_start_creates_workflow(self) -> None:
        svc = _make_service(provider=MockProvider(response_content=VALID_QUESTION_JSON))
        state = await svc.start(
            user_id="u-1",
            school_id="s-1",
            class_id="c-1",
            subject_id="math",
            topic="函数",
            question_count=1,
        )
        assert state.status == "pending"
        assert state.workflow_id != ""
        assert state.topic == "函数"

    async def test_start_and_wait_for_completion(self) -> None:
        svc = _make_service(
            provider=MockProvider(response_content=VALID_QUESTION_JSON),
            content_client=ContentClientMock(),
            data_ana_client=DataAnaClientMock(),
        )
        state = await svc.start(
            user_id="u-1",
            school_id="s-1",
            class_id="c-1",
            subject_id="math",
            topic="函数",
            question_count=1,
        )
        assert state.status == "pending"

        # 等待后台任务完成
        task = svc._background_tasks.get(state.workflow_id)
        assert task is not None
        await task

        final = await svc.get_status(state.workflow_id)
        assert final.status in ("pending_review", "failed")
        if final.status == "pending_review":
            assert len(final.questions) == 1

    async def test_get_status_not_found_raises(self) -> None:
        svc = _make_service()
        with pytest.raises(AIWorkflowNotFoundError):
            await svc.get_status("nonexistent-id")


class TestLessonPlanWorkflowConfirm:
    """confirm 测试."""

    async def test_confirm_wrong_state_raises(self) -> None:
        store = WorkflowStateStore(redis=None)
        state = _make_state(status="pending")
        await store.create(state)
        svc = _make_service(store=store)
        with pytest.raises(AIWorkflowStateInvalidError):
            await svc.confirm(state.workflow_id)

    async def test_confirm_success(self) -> None:
        store = WorkflowStateStore(redis=None)
        state = _make_state(
            status="pending_review",
            questions=[GeneratedQuestionData(
                question="q1", answer="a1", explanation="e1",
                question_type="short_answer", difficulty="easy",
                knowledge_point_ids=["kp_1"],
            )],
        )
        await store.create(state)
        svc = _make_service(store=store, content_client=ContentClientMock())
        result = await svc.confirm(state.workflow_id)
        assert result["success"] is True
        assert len(result["persisted_question_ids"]) == 1
        # 验证状态更新为 persisted
        final = await store.get(state.workflow_id)
        assert final.status == "persisted"

    async def test_confirm_no_content_client_returns_error(self) -> None:
        store = WorkflowStateStore(redis=None)
        state = _make_state(
            status="pending_review",
            questions=[GeneratedQuestionData(question="q1", answer="a1", explanation="e1")],
        )
        await store.create(state)
        svc = _make_service(store=store, content_client=None)
        result = await svc.confirm(state.workflow_id)
        assert result["success"] is False
        assert "content client not configured" in result["error"]

    async def test_confirm_with_modifications(self) -> None:
        store = WorkflowStateStore(redis=None)
        state = _make_state(
            status="pending_review",
            questions=[GeneratedQuestionData(
                question="original", answer="a1", explanation="e1",
                question_type="short_answer", difficulty="easy",
                knowledge_point_ids=["kp_1"],
            )],
        )
        await store.create(state)

        content_client = AsyncMock()
        content_client.create_questions.return_value = [
            CreatedQuestion(id="q_1", question="modified question"),
        ]
        svc = _make_service(store=store, content_client=content_client)

        result = await svc.confirm(
            state.workflow_id,
            modifications={"0": "modified question"},
        )
        assert result["success"] is True
        # 验证修改已应用到传入 content_client 的题目
        call_args = content_client.create_questions.call_args
        questions_passed = call_args.args[0]
        assert questions_passed[0].question == "modified question"


class TestLessonPlanWorkflowSteps:
    """4 步编排内部方法测试."""

    async def test_step1_analyze_success(self) -> None:
        svc = _make_service(data_ana_client=DataAnaClientMock())
        state = _make_state()
        analysis = await svc._step1_analyze(state)
        assert "class_performance" in analysis
        assert analysis["class_performance"]["average_score"] == 78.5
        assert analysis["class_performance"]["student_count"] == 3
        assert "weak_students" in analysis

    async def test_step1_analyze_no_client_degraded(self) -> None:
        svc = _make_service(data_ana_client=None)
        state = _make_state()
        analysis = await svc._step1_analyze(state)
        assert analysis["degraded"] is True
        assert "not configured" in analysis["degraded_reason"]

    async def test_step2_recommend_success(self) -> None:
        svc = _make_service(content_client=ContentClientMock())
        state = _make_state()
        kps = await svc._step2_recommend(state)
        assert len(kps) == 3
        assert kps[0]["id"] == "kp_001"

    async def test_step2_recommend_no_client_fallback(self) -> None:
        svc = _make_service(content_client=None)
        state = _make_state(topic="函数")
        kps = await svc._step2_recommend(state)
        assert len(kps) == 3
        assert "基础概念" in kps[0]["title"]
        assert "函数" in kps[0]["title"]

    async def test_step3_generate_success(self) -> None:
        provider = MockProvider(response_content=VALID_QUESTION_JSON)
        svc = _make_service(
            provider=provider,
            quality_gate=QualityGate(rule_validator=RuleValidator()),
        )
        state = _make_state(question_count=1, target_difficulty="medium")
        questions = await svc._step3_generate(
            state, [{"id": "kp_1", "title": "KP1"}],
        )
        assert len(questions) == 1
        assert questions[0].question == "什么是函数？"
        assert questions[0].answer == "函数是一种对应关系"
        assert questions[0].degraded is False

    async def test_step3_generate_all_retries_fail(self) -> None:
        provider = MockProvider(fail=True)
        svc = _make_service(provider=provider)
        state = _make_state(question_count=1)
        questions = await svc._step3_generate(
            state, [{"id": "kp_1", "title": "KP1"}],
        )
        assert len(questions) == 1
        assert questions[0].degraded is True
        assert "max retries exceeded" in questions[0].degraded_reason


class TestLessonPlanWorkflowPrompt:
    """prompt 渲染测试."""

    def test_render_generate_prompt_with_template(self) -> None:
        prompt_service = MagicMock()
        prompt_service.render.return_value = "rendered prompt with template"
        svc = _make_service(prompt_service=prompt_service)
        state = _make_state()
        prompt = svc._render_generate_prompt(state, ["kp_1"], 0)
        assert prompt == "rendered prompt with template"
        prompt_service.render.assert_called_once()
        args = prompt_service.render.call_args
        assert args.args[0] == "lesson_plan_generate"

    def test_render_generate_prompt_fallback(self) -> None:
        svc = _make_service(prompt_service=None)
        state = _make_state(topic="函数", subject_id="math", target_difficulty="medium")
        prompt = svc._render_generate_prompt(state, ["kp_1"], 0)
        assert "函数" in prompt
        assert "math" in prompt
        assert "medium" in prompt
        assert "JSON" in prompt
