"""#1749 POST /api/v1/generation/variant-plans 路由单元测试。

直接调用路由函数 + mock 依赖（不使用 TestClient，因 httpx2 冲突）。

覆盖:
- 200: 合法请求返回 items
- 400: voice_library_ids 长度不符 (schema validator)
- 400: 缺少 source_edit_plan_id 且无模板兜底 plan
- 400: 选片失败 ValueError -> HTTPException(400)
"""

from __future__ import annotations

from unittest.mock import MagicMock, patch

import pytest
from fastapi import HTTPException


def _call_route(request_obj, db=None):
    """直接调用 create_variant_plans 路由函数。"""
    from app.api.routes.generation_variant_plans import create_variant_plans

    user = MagicMock()
    user.user.id = "u1"
    if db is None:
        db = MagicMock()
    return create_variant_plans(request_obj, authenticated_user=user, db=db)


def _base_payload(**kw):
    from app.api.routes.generation_variant_plans import VariantPlanRequest
    d = {
        "template_id": "tpl_1",
        "asset_ids": ["a1", "a2"],
        "count": 1,
        "source_edit_plan_id": "src_plan_1",
    }
    d.update(kw)
    return VariantPlanRequest(**d)


class TestVariantPlansRoute:
    def test_200_happy_path(self):
        """合法请求：ensure_variant_plans 返回 plan_id，list_clips 返回 clips。"""
        req = _base_payload()
        db = MagicMock()

        mock_svc_inst = MagicMock()
        mock_svc_inst.ensure_variant_plans.return_value = ["plan_v0"]
        clip = MagicMock()
        clip.id = "c1"
        clip.order = 0
        clip.asset_id = "a1"
        clip.start_time = 0.0
        clip.duration = 10.0
        clip.clip_type = "main"
        clip.transition_effect = "cut"
        clip.transition_duration = 0.0
        clip.playback_speed = 1.0
        clip.text_content = ""
        mock_svc_inst.list_clips.return_value = [clip]

        mock_svc_cls = MagicMock(return_value=mock_svc_inst)

        with patch(
            "packages.domain.variant_voice_resolver.resolve_variant_voice_ids",
            return_value=["voice_1"],
        ), patch(
            "app.api.routes.generation_tasks._query_voice_durations",
            return_value=[30.0],
        ), patch(
            "app.services.edit_plan_service.EditPlanService",
            mock_svc_cls,
        ):
            resp = _call_route(req, db)

        assert resp.total == 1
        assert resp.items[0].plan_id == "plan_v0"
        assert len(resp.items[0].clips) == 1

    def test_400_voice_library_ids_length_mismatch(self):
        """voice_library_ids 长度 != count -> pydantic model_validator 抛异常。"""
        from app.api.routes.generation_variant_plans import VariantPlanRequest
        with pytest.raises(Exception):  # noqa: B017
            VariantPlanRequest(
                template_id="tpl_1",
                asset_ids=["a1"],
                count=3,
                source_edit_plan_id="src_plan_1",
                voice_library_ids=["v1", "v2"],  # len=2 != count=3
            )

    def test_400_no_source_plan_no_template_fallback(self):
        """无 source_edit_plan_id 且 DB 查不到模板 plan -> HTTPException(400)。"""
        from app.api.routes.generation_variant_plans import VariantPlanRequest

        req = VariantPlanRequest(
            template_id="tpl_nonexist",
            asset_ids=[],
            count=1,
            source_edit_plan_id="",
        )

        db = MagicMock()
        db.query.return_value.filter.return_value.order_by.return_value.first.return_value = None

        with patch(
            "packages.domain.variant_voice_resolver.resolve_variant_voice_ids",
            return_value=[""],
        ):
            with pytest.raises(HTTPException) as exc_info:
                _call_route(req, db)
            assert exc_info.value.status_code == 400

    def test_400_selection_failure_value_error(self):
        """ensure_variant_plans 抛 ValueError -> HTTPException(400)。"""
        req = _base_payload()
        db = MagicMock()

        mock_svc_inst = MagicMock()
        mock_svc_inst.ensure_variant_plans.side_effect = ValueError("素材池为空")
        mock_svc_cls = MagicMock(return_value=mock_svc_inst)

        with patch(
            "packages.domain.variant_voice_resolver.resolve_variant_voice_ids",
            return_value=["voice_1"],
        ), patch(
            "app.api.routes.generation_tasks._query_voice_durations",
            return_value=[30.0],
        ), patch(
            "app.services.edit_plan_service.EditPlanService",
            mock_svc_cls,
        ):
            with pytest.raises(HTTPException) as exc_info:
                _call_route(req, db)
            assert exc_info.value.status_code == 400
