"""config_schemas + AI 推荐/封面生成 单元测试.

覆盖（20+ 测试用例）：
- config_schemas: normalize_plan_config / normalize_template_config 默认值填充、部分覆盖、非标准字段保留
- config_schemas: Pydantic 枚举校验（CoverType / TextPosition / BGMSource）
- ai_tasks: run_ai_recommend / run_generate_cover stub 返回结构
"""

from __future__ import annotations

import os
import sys
from pathlib import Path
from typing import Any

os.environ.setdefault("JWT_SECRET_KEY", "unit-test-secret-key-for-testing")
os.environ.setdefault("DATABASE_URL", "sqlite:///test.db")

import pytest

sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "apps" / "api"))


# ---------------------------------------------------------------------------
# config_schemas 单元测试
# ---------------------------------------------------------------------------


class TestNormalizePlanConfig:
    def test_none_returns_full_defaults(self):
        from packages.domain.config_schemas import DEFAULT_EDIT_PLAN_CONFIG, normalize_plan_config

        result = normalize_plan_config(None)
        assert result == DEFAULT_EDIT_PLAN_CONFIG.copy()

    def test_empty_dict_returns_full_defaults(self):
        from packages.domain.config_schemas import normalize_plan_config

        result = normalize_plan_config({})
        assert result["cover"]["type"] == "ai_frame"
        assert result["title"]["enabled"] is True
        assert result["subtitle"]["position"] == "bottom"
        assert result["bgm"]["volume"] == 0.3

    def test_partial_cover_override(self):
        from packages.domain.config_schemas import normalize_plan_config

        result = normalize_plan_config({"cover": {"type": "upload", "image_url": "https://example.com/cover.jpg"}})
        assert result["cover"]["type"] == "upload"
        assert result["cover"]["image_url"] == "https://example.com/cover.jpg"
        # frame_time 保留默认值 None
        assert result["cover"]["frame_time"] is None

    def test_partial_title_override(self):
        from packages.domain.config_schemas import normalize_plan_config

        result = normalize_plan_config({"title": {"text": "我的标题", "ai_auto": False}})
        assert result["title"]["text"] == "我的标题"
        assert result["title"]["ai_auto"] is False
        # 其他字段保留默认值
        assert result["title"]["font"] == "思源黑体"
        assert result["title"]["size"] == 48

    def test_non_standard_fields_preserved(self):
        from packages.domain.config_schemas import normalize_plan_config

        result = normalize_plan_config({"generation_task_id": "task-123", "custom_key": "value"})
        assert result["generation_task_id"] == "task-123"
        assert result["custom_key"] == "value"
        # 标准字段仍然完整
        assert "cover" in result
        assert "bgm" in result

    def test_bgm_partial_override(self):
        from packages.domain.config_schemas import normalize_plan_config

        result = normalize_plan_config({"bgm": {"volume": 0.8}})
        assert result["bgm"]["volume"] == 0.8
        assert result["bgm"]["source"] == "library"
        assert result["bgm"]["asset_id"] == ""

    def test_multiple_sections_override(self):
        from packages.domain.config_schemas import normalize_plan_config

        result = normalize_plan_config(
            {
                "cover": {"type": "manual", "frame_time": 5.0},
                "title": {"enabled": False},
                "subtitle": {"size": 36},
                "bgm": {"source": "ai_recommend"},
            }
        )
        assert result["cover"]["type"] == "manual"
        assert result["cover"]["frame_time"] == 5.0
        assert result["title"]["enabled"] is False
        assert result["subtitle"]["size"] == 36
        assert result["bgm"]["source"] == "ai_recommend"


class TestNormalizeTemplateConfig:
    def test_same_as_plan_config_plus_template_fields(self):
        """template config 包含 plan config 的所有字段，外加 transition_enabled"""
        from packages.domain.config_schemas import normalize_plan_config, normalize_template_config

        raw = {"title": {"text": "模板标题"}}
        plan_cfg = normalize_plan_config(raw)
        tpl_cfg = normalize_template_config(raw)
        # plan config 的字段在 template config 中应一致
        for key in plan_cfg:
            assert tpl_cfg[key] == plan_cfg[key]
        # template config 额外包含 transition_enabled
        assert "transition_enabled" in tpl_cfg
        assert tpl_cfg["transition_enabled"] is True

    def test_none_returns_defaults(self):
        from packages.domain.config_schemas import DEFAULT_EDIT_TEMPLATE_CONFIG, normalize_template_config

        result = normalize_template_config(None)
        assert result == DEFAULT_EDIT_TEMPLATE_CONFIG.copy()


class TestConfigSchemas:
    def test_cover_type_enum_values(self):
        from packages.domain.config_schemas import CoverType

        assert CoverType.AI_FRAME.value == "ai_frame"
        assert CoverType.MANUAL.value == "manual"
        assert CoverType.UPLOAD.value == "upload"
        assert CoverType.AI_REGENERATE.value == "ai_regenerate"

    def test_text_position_enum_values(self):
        from packages.domain.config_schemas import TextPosition

        assert TextPosition.TOP.value == "top"
        assert TextPosition.CENTER.value == "center"
        assert TextPosition.BOTTOM.value == "bottom"

    def test_bgm_source_enum_values(self):
        from packages.domain.config_schemas import BGMSource

        assert BGMSource.LIBRARY.value == "library"
        assert BGMSource.UPLOAD.value == "upload"
        assert BGMSource.AI_RECOMMEND.value == "ai_recommend"

    def test_cover_config_model(self):
        from packages.domain.config_schemas import CoverConfig, CoverType

        cfg = CoverConfig(type=CoverType.MANUAL, frame_time=3.5)
        assert cfg.type == CoverType.MANUAL
        assert cfg.frame_time == 3.5
        assert cfg.image_url == ""

    def test_title_config_defaults(self):
        from packages.domain.config_schemas import TitleConfig

        cfg = TitleConfig()
        assert cfg.enabled is True
        assert cfg.ai_auto is True
        assert cfg.font == "思源黑体"
        assert cfg.size == 48
        assert cfg.bold is True
        assert cfg.stroke.enabled is False

    def test_bgm_config_validation(self):
        from packages.domain.config_schemas import BGMConfig

        cfg = BGMConfig(volume=0.5)
        assert cfg.volume == 0.5

        from pydantic import ValidationError

        with pytest.raises(ValidationError):
            BGMConfig(volume=1.5)  # > 1.0 应该校验失败

    def test_edit_plan_config_schema_full(self):
        from packages.domain.config_schemas import EditPlanConfigSchema

        schema = EditPlanConfigSchema()
        assert schema.cover.type.value == "ai_frame"
        assert schema.title.enabled is True
        assert schema.subtitle.position.value == "bottom"
        assert schema.bgm.source.value == "library"


# ---------------------------------------------------------------------------
# ai_tasks 单元测试
# ---------------------------------------------------------------------------


class TestAIRunTasks:
    def test_run_ai_recommend_returns_expected_structure(self):
        from apps.worker.worker_app.tasks.ai_tasks import run_ai_recommend

        result = run_ai_recommend(
            plan_id="plan-001",
            template_id="tpl-001",
            asset_ids=["asset-1", "asset-2"],
            editing_mode="one_take",
            target_duration=30.0,
        )
        assert "clips" in result
        assert "config" in result
        assert "total_duration" in result
        assert "confidence" in result
        assert len(result["clips"]) >= 2  # 至少 intro + outro
        assert result["total_duration"] > 0
        assert 0 <= result["confidence"] <= 1

    def test_run_ai_recommend_clip_structure(self):
        from apps.worker.worker_app.tasks.ai_tasks import run_ai_recommend

        result = run_ai_recommend(
            plan_id="plan-001",
            template_id="tpl-001",
            asset_ids=["asset-1"],
        )
        for clip in result["clips"]:
            assert "clip_type" in clip
            assert "order" in clip
            assert "duration" in clip
            assert clip["duration"] > 0

    def test_run_ai_recommend_empty_assets(self):
        from apps.worker.worker_app.tasks.ai_tasks import run_ai_recommend

        result = run_ai_recommend(
            plan_id="plan-001",
            template_id="tpl-001",
            asset_ids=[],
        )
        # 即使没有素材，也应该有 intro + outro
        assert len(result["clips"]) >= 2

    def test_run_generate_cover_ai_frame_raises_without_mediakit(self):
        """ai_frame cover raises RuntimeError when MediaKit is unavailable."""
        import pytest

        from apps.worker.worker_app.tasks.ai_tasks import run_generate_cover

        with pytest.raises(RuntimeError, match="封面数据不可用"):
            run_generate_cover(
                plan_id="plan-001",
                asset_ids=["asset-1"],
                cover_type="ai_frame",
            )

    def test_run_generate_cover_manual(self):
        from apps.worker.worker_app.tasks.ai_tasks import run_generate_cover

        result = run_generate_cover(
            plan_id="plan-001",
            asset_ids=["asset-1"],
            cover_type="manual",
            frame_time=5.0,
        )
        assert result["type"] == "manual"
        assert result["frame_time"] == 5.0

    def test_run_generate_cover_upload(self):
        from apps.worker.worker_app.tasks.ai_tasks import run_generate_cover

        result = run_generate_cover(
            plan_id="plan-001",
            asset_ids=[],
            cover_type="upload",
        )
        assert result["type"] == "upload"
