import asyncio
from unittest.mock import AsyncMock

import pytest

import src.services.calendar as calendar_module
from src.services import BangumiApiError, CalendarService


@pytest.fixture
def service() -> CalendarService:
    return CalendarService(access_token="token", user_agent="ua")


@pytest.mark.asyncio
async def test_calendar_cache_hit(service: CalendarService) -> None:
    payload = [{"weekday": {"id": 1}, "items": [{"id": 1, "name": "A"}]}]
    service._request = AsyncMock(return_value=payload)

    first = await service.get_calendar()
    second = await service.get_calendar()

    assert first == second
    assert service._request.await_count == 1


@pytest.mark.asyncio
async def test_calendar_cache_expired_refresh(
    service: CalendarService, monkeypatch: pytest.MonkeyPatch
) -> None:
    now = 1_000_000.0

    def fake_time() -> float:
        return now

    monkeypatch.setattr(calendar_module.time, "time", fake_time)

    service._request = AsyncMock(
        side_effect=[
            [{"weekday": {"id": 1}, "items": []}],
            [{"weekday": {"id": 2}, "items": []}],
        ]
    )

    first = await service.get_calendar()
    now += service.CALENDAR_CACHE_TTL_SECONDS + 1
    second = await service.get_calendar()

    assert first != second
    assert service._request.await_count == 2


@pytest.mark.asyncio
async def test_calendar_cache_returns_deepcopy(service: CalendarService) -> None:
    payload = [{"weekday": {"id": 1}, "items": []}]
    service._request = AsyncMock(return_value=payload)

    first = await service.get_calendar()
    first[0]["weekday"]["id"] = 7
    second = await service.get_calendar()

    assert second[0]["weekday"]["id"] == 1
    assert service._request.await_count == 1


@pytest.mark.asyncio
async def test_calendar_cache_refresh_failed_fallback_stale(
    service: CalendarService, monkeypatch: pytest.MonkeyPatch
) -> None:
    now = 2_000_000.0

    def fake_time() -> float:
        return now

    monkeypatch.setattr(calendar_module.time, "time", fake_time)

    service._request = AsyncMock(
        side_effect=[
            [{"weekday": {"id": 1}, "items": [{"id": 1}]}],
            BangumiApiError("boom"),
        ]
    )

    first = await service.get_calendar()
    now += service.CALENDAR_CACHE_TTL_SECONDS + 1
    second = await service.get_calendar()

    assert second == first
    assert service._request.await_count == 2


@pytest.mark.asyncio
async def test_calendar_cache_concurrent_single_refresh(
    service: CalendarService,
) -> None:
    payload = [{"weekday": {"id": 3}, "items": []}]

    async def slow_fetch(*args: object, **kwargs: object) -> list[dict[str, object]]:
        await asyncio.sleep(0.05)
        return payload

    service._request = AsyncMock(side_effect=slow_fetch)

    first, second = await asyncio.gather(service.get_calendar(), service.get_calendar())

    assert first == second
    assert service._request.await_count == 1
