"""Recipe use cases unit tests."""

from __future__ import annotations

from datetime import datetime, timezone
from unittest.mock import Mock

import pytest

from packages.application.recipe.commands import (
    CreateRecipeCommand,
    RecipeItemCommand,
    UpdateRecipeCommand,
)
from packages.application.recipe.use_cases import (
    CreateRecipeUseCase,
    DeleteRecipeUseCase,
    FeatureDisabledError,
    GetRecipeUseCase,
    ListRecipesUseCase,
    NotFoundError,
    UpdateRecipeUseCase,
    UseRecipeUseCase,
)
from packages.domain.recipe import Recipe, RecipeItem


def _make_recipe(**kwargs) -> Recipe:
    defaults = dict(
        id="recipe001",
        user_id="user001",
        name="测试配方",
        description="描述",
        template_id="tpl001",
        generation_params={"mode": "one_take"},
        items=[],
        is_active=True,
        metadata_={},
        created_at=datetime.now(timezone.utc),
        updated_at=datetime.now(timezone.utc),
    )
    defaults.update(kwargs)
    return Recipe(**defaults)


def _make_item(**kwargs) -> RecipeItem:
    defaults = dict(
        id="item001",
        recipe_id="recipe001",
        item_type="asset",
        item_id="asset001",
        position=0,
        metadata_={},
    )
    defaults.update(kwargs)
    return RecipeItem(**defaults)


class TestCreateRecipeUseCase:
    @pytest.fixture
    def mock_repo(self):
        repo = Mock()
        repo.create = Mock(side_effect=lambda r: r)
        repo.create_items = Mock(side_effect=lambda items: items)
        return repo

    def test_create_basic(self, mock_repo):
        uc = CreateRecipeUseCase(mock_repo)
        cmd = CreateRecipeCommand(
            user_id="user001",
            name="我的配方",
            description="desc",
            template_id="tpl001",
            generation_params={"mode": "one_take"},
        )
        result = uc.execute(cmd)
        assert result.name == "我的配方"
        assert result.user_id == "user001"
        mock_repo.create.assert_called_once()

    def test_create_with_items(self, mock_repo):
        uc = CreateRecipeUseCase(mock_repo)
        cmd = CreateRecipeCommand(
            user_id="user001",
            name="带素材配方",
            items=[
                RecipeItemCommand(item_type="asset", item_id="a1", position=0),
                RecipeItemCommand(item_type="title", item_id="t1", position=1),
                RecipeItemCommand(item_type="voice", item_id="v1", position=2),
            ],
        )
        result = uc.execute(cmd)
        assert len(result.items) == 3
        mock_repo.create_items.assert_called_once()
        items_arg = mock_repo.create_items.call_args[0][0]
        assert items_arg[0].item_type == "asset"
        assert items_arg[1].item_type == "title"
        assert items_arg[2].item_type == "voice"


class TestListRecipesUseCase:
    def test_list(self):
        repo = Mock()
        repo.list_by_user = Mock(return_value=[_make_recipe()])
        uc = ListRecipesUseCase(repo)
        result = uc.execute("user001", skip=0, limit=10)
        assert len(result) == 1
        repo.list_by_user.assert_called_once_with("user001", skip=0, limit=10)


class TestGetRecipeUseCase:
    def test_get_found(self):
        repo = Mock()
        repo.get = Mock(return_value=_make_recipe())
        uc = GetRecipeUseCase(repo)
        result = uc.execute("recipe001", "user001")
        assert result is not None
        assert result.id == "recipe001"

    def test_get_not_found(self):
        repo = Mock()
        repo.get = Mock(return_value=None)
        uc = GetRecipeUseCase(repo)
        result = uc.execute("recipe999", "user001")
        assert result is None


class TestUpdateRecipeUseCase:
    @pytest.fixture
    def mock_repo(self):
        repo = Mock()
        repo.get = Mock(return_value=_make_recipe())
        repo.update = Mock(side_effect=lambda r: r)
        repo.list_items = Mock(return_value=[])
        repo.delete_items_by_recipe = Mock(return_value=0)
        repo.create_items = Mock(side_effect=lambda items: items)
        return repo

    def test_update_name(self, mock_repo):
        uc = UpdateRecipeUseCase(mock_repo)
        cmd = UpdateRecipeCommand(
            recipe_id="recipe001",
            user_id="user001",
            name="新名字",
        )
        result = uc.execute(cmd)
        assert result.name == "新名字"

    def test_update_not_found(self):
        repo = Mock()
        repo.get = Mock(return_value=None)
        uc = UpdateRecipeUseCase(repo)
        cmd = UpdateRecipeCommand(recipe_id="xxx", user_id="user001", name="x")
        with pytest.raises(NotFoundError):
            uc.execute(cmd)

    def test_update_replace_items(self, mock_repo):
        uc = UpdateRecipeUseCase(mock_repo)
        cmd = UpdateRecipeCommand(
            recipe_id="recipe001",
            user_id="user001",
            items=[RecipeItemCommand(item_type="voice", item_id="v2", position=0)],
        )
        result = uc.execute(cmd)
        mock_repo.delete_items_by_recipe.assert_called_once_with("recipe001")
        mock_repo.create_items.assert_called_once()
        assert len(result.items) == 1


class TestDeleteRecipeUseCase:
    def test_delete_success(self):
        repo = Mock()
        repo.delete = Mock(return_value=True)
        uc = DeleteRecipeUseCase(repo)
        assert uc.execute("recipe001", "user001") is True

    def test_delete_not_found(self):
        repo = Mock()
        repo.delete = Mock(return_value=False)
        uc = DeleteRecipeUseCase(repo)
        assert uc.execute("recipe999", "user001") is False


class TestUseRecipeUseCase:
    def test_use_success_basic_plan(self):
        repo = Mock()
        repo.get = Mock(return_value=_make_recipe())
        uc = UseRecipeUseCase(repo)
        result = uc.execute("recipe001", "user001", user_plan="basic")
        assert result.recipe.id == "recipe001"
        assert result.warnings == []

    def test_use_success_premium_plan(self):
        repo = Mock()
        repo.get = Mock(return_value=_make_recipe())
        uc = UseRecipeUseCase(repo)
        result = uc.execute("recipe001", "user001", user_plan="premium")
        assert result.recipe.id == "recipe001"

    def test_use_free_plan_forbidden(self):
        repo = Mock()
        uc = UseRecipeUseCase(repo)
        with pytest.raises(FeatureDisabledError):
            uc.execute("recipe001", "user001", user_plan="free")

    def test_use_not_found(self):
        repo = Mock()
        repo.get = Mock(return_value=None)
        uc = UseRecipeUseCase(repo)
        with pytest.raises(NotFoundError):
            uc.execute("recipe999", "user001", user_plan="basic")
