from types import SimpleNamespace
from unittest.mock import AsyncMock, MagicMock

import pytest

from src.services import SubscriptionService


@pytest.fixture
def mock_repo() -> MagicMock:
    repo = MagicMock()
    repo.subscribe_subject = MagicMock(return_value=True)
    repo.remove_subscription = MagicMock(return_value=True)
    repo.find_group_subscription_candidates = MagicMock(return_value=[])
    return repo


@pytest.fixture
def mock_service() -> MagicMock:
    service = MagicMock()
    service.search_subjects = AsyncMock()
    service.get_subject_details = AsyncMock()
    service.get_calendar = AsyncMock()
    service.get_latest_episode = AsyncMock()
    service.get_subject_base64image = AsyncMock()
    return service


@pytest.mark.asyncio
async def test_subscribe_success(mock_repo, mock_service) -> None:
    mock_service.search_subjects.return_value = {"data": [{"id": 123}]}
    mock_service.get_subject_details.return_value = {
        "id": 123,
        "name": "Test Anime",
        "name_cn": "测试番剧",
        "date": "2024-01-01",
        "eps": 12,
    }
    mock_service.get_calendar.return_value = [{"items": [{"id": 123}]}]

    sub_service = SubscriptionService(
        repository=mock_repo, service=mock_service, config_manager=MagicMock()
    )
    result = await sub_service.subscribe("group_1", "Test Anime")

    assert "成功订阅《测试番剧》" in result
    mock_repo.subscribe_subject.assert_called_once_with(
        group_id="group_1",
        subject_id="123",
        name="测试番剧",
        air_date="2024-01-01",
        total_episodes=12,
    )


@pytest.mark.asyncio
async def test_unsubscribe_local_single_match_success(mock_repo, mock_service) -> None:
    mock_repo.find_group_subscription_candidates.return_value = [
        SimpleNamespace(subject_id="123", name="测试番剧")
    ]

    sub_service = SubscriptionService(
        repository=mock_repo, service=mock_service, config_manager=MagicMock()
    )
    result = await sub_service.unsubscribe("group_1", "测")

    assert "已成功取消订阅《测试番剧》" in result
    mock_repo.find_group_subscription_candidates.assert_called_once_with(
        group_id="group_1", keyword="测", limit=6
    )
    mock_repo.remove_subscription.assert_called_once_with("group_1", "123")
    mock_service.search_subjects.assert_not_called()
    mock_service.get_subject_details.assert_not_called()
    mock_service.get_calendar.assert_not_called()


@pytest.mark.asyncio
async def test_unsubscribe_local_no_match(mock_repo, mock_service) -> None:
    mock_repo.find_group_subscription_candidates.return_value = []

    sub_service = SubscriptionService(
        repository=mock_repo, service=mock_service, config_manager=MagicMock()
    )
    result = await sub_service.unsubscribe("group_1", "不存在")

    assert "未找到与「不存在」匹配的本群订阅番剧" in result
    mock_repo.remove_subscription.assert_not_called()
    mock_service.search_subjects.assert_not_called()
    mock_service.get_subject_details.assert_not_called()
    mock_service.get_calendar.assert_not_called()


@pytest.mark.asyncio
async def test_unsubscribe_local_multi_match_returns_candidates(
    mock_repo, mock_service
) -> None:
    mock_repo.find_group_subscription_candidates.return_value = [
        SimpleNamespace(subject_id="1", name="进击的巨人"),
        SimpleNamespace(subject_id="2", name="进击！巨人中学"),
        SimpleNamespace(subject_id="3", name="巨人族的新娘"),
    ]

    sub_service = SubscriptionService(
        repository=mock_repo, service=mock_service, config_manager=MagicMock()
    )
    result = await sub_service.unsubscribe("group_1", "巨人")

    assert "匹配到多个已订阅番剧" in result
    assert "1. 进击的巨人 (ID: 1)" in result
    assert "2. 进击！巨人中学 (ID: 2)" in result
    assert "3. 巨人族的新娘 (ID: 3)" in result
    mock_repo.remove_subscription.assert_not_called()
    mock_service.search_subjects.assert_not_called()
    mock_service.get_subject_details.assert_not_called()
    mock_service.get_calendar.assert_not_called()


@pytest.mark.asyncio
async def test_unsubscribe_local_remove_failed(mock_repo, mock_service) -> None:
    mock_repo.find_group_subscription_candidates.return_value = [
        SimpleNamespace(subject_id="123", name="测试番剧")
    ]
    mock_repo.remove_subscription.return_value = False

    sub_service = SubscriptionService(
        repository=mock_repo, service=mock_service, config_manager=MagicMock()
    )
    result = await sub_service.unsubscribe("group_1", "测")

    assert "取消订阅失败：你可能并没有订阅《测试番剧》" in result
    mock_repo.remove_subscription.assert_called_once_with("group_1", "123")


@pytest.mark.asyncio
async def test_get_subscribe_candidates_multi_match(mock_repo, mock_service) -> None:
    mock_service.search_subjects.return_value = {
        "data": [
            {"id": 1, "name_cn": "进击的巨人"},
            {"id": 2, "name": "进击！巨人中学"},
            {"id": 1, "name_cn": "进击的巨人"},
            {"name_cn": "无ID条目"},
        ]
    }

    sub_service = SubscriptionService(
        repository=mock_repo, service=mock_service, config_manager=MagicMock()
    )
    error_msg, candidates = await sub_service.get_subscribe_candidates("巨人", 5)

    assert error_msg is None
    assert candidates == [
        {"subject_id": "1", "name": "进击的巨人"},
        {"subject_id": "2", "name": "进击！巨人中学"},
    ]
    mock_service.search_subjects.assert_awaited_once_with(
        keyword="巨人",
        limit=5,
        subject_type=[2],
        subject_tags=None,
    )


@pytest.mark.asyncio
async def test_subscribe_by_subject_id_success(mock_repo, mock_service) -> None:
    mock_service.get_subject_details.return_value = {
        "id": 456,
        "name": "Test Name",
        "name_cn": "测试番剧2",
        "date": "2025-01-01",
        "eps": 24,
    }
    mock_service.get_calendar.return_value = [{"items": [{"id": 456}]}]

    sub_service = SubscriptionService(
        repository=mock_repo, service=mock_service, config_manager=MagicMock()
    )
    result = await sub_service.subscribe_by_subject_id("group_1", "456")

    assert "✅ 成功订阅《测试番剧2》" in result
    mock_repo.subscribe_subject.assert_called_once_with(
        group_id="group_1",
        subject_id="456",
        name="测试番剧2",
        air_date="2025-01-01",
        total_episodes=24,
    )


@pytest.mark.asyncio
async def test_subscribe_by_subject_id_not_in_calendar(mock_repo, mock_service) -> None:
    mock_service.get_subject_details.return_value = {
        "id": 789,
        "name": "Not In Calendar",
        "name_cn": "未放送番剧",
        "date": "2025-06-01",
        "eps": 12,
    }
    mock_service.get_calendar.return_value = [{"items": [{"id": 456}]}]

    sub_service = SubscriptionService(
        repository=mock_repo, service=mock_service, config_manager=MagicMock()
    )
    result = await sub_service.subscribe_by_subject_id("group_1", "789")

    assert "不在当前的每日放送列表中" in result
    mock_repo.subscribe_subject.assert_not_called()
