import asyncio
from collections import deque
from datetime import UTC, datetime, time as time_cls
from types import SimpleNamespace
from unittest.mock import AsyncMock

import pytest

from app.config import settings
from app.services.remnawave_service import RemnaWaveConfigurationError
from app.services.remnawave_sync_service import RemnaWaveAutoSyncService


@pytest.mark.parametrize(
    'raw, expected',
    [
        ('03:00, 15:30 03:00; 07:05', [time_cls(3, 0), time_cls(7, 5), time_cls(15, 30)]),
        ('', []),
        (None, []),
        ('25:00, 10:70, test, 09:15', [time_cls(9, 15)]),
    ],
)
def test_parse_daily_time_list(raw, expected):
    assert settings.parse_daily_time_list(raw) == expected


def _patch_datetime(monkeypatch, current):
    real_datetime = datetime

    monkeypatch.setattr(
        'app.services.remnawave_sync_service.datetime',
        SimpleNamespace(
            now=lambda tz=None: current,
            combine=lambda date_obj, time_obj, tzinfo=None: real_datetime.combine(date_obj, time_obj, tzinfo=tzinfo),
        ),
    )


def test_calculate_next_run_same_day(monkeypatch):
    service = RemnaWaveAutoSyncService()
    current = datetime(2024, 1, 1, 2, 30, tzinfo=UTC)
    _patch_datetime(monkeypatch, current)

    next_run = service._calculate_next_run([time_cls(1, 0), time_cls(3, 0)])

    assert next_run == datetime(2024, 1, 1, 3, 0, tzinfo=UTC)


def test_calculate_next_run_rollover(monkeypatch):
    service = RemnaWaveAutoSyncService()
    current = datetime(2024, 1, 1, 23, 45, tzinfo=UTC)
    _patch_datetime(monkeypatch, current)

    next_run = service._calculate_next_run([time_cls(1, 0), time_cls(10, 0)])

    assert next_run == datetime(2024, 1, 2, 1, 0, tzinfo=UTC)


def test_perform_sync_rebuilds_service_on_each_run(monkeypatch):
    class StubService:
        def __init__(self, *, configured: bool, user_stats=None, squads=None):
            self.is_configured = configured
            self.configuration_error = None if configured else 'missing config'
            self._user_stats = user_stats or {'synced': 1}
            self._squads = squads or []
            self.sync_calls = 0
            self.squad_calls = 0

        async def sync_users_from_panel(self, session, scope):
            self.sync_calls += 1
            return self._user_stats

        async def get_all_squads(self):
            self.squad_calls += 1
            return self._squads

    services = deque(
        [
            StubService(configured=True),  # used during service __init__
            StubService(configured=False),
            StubService(
                configured=True,
                user_stats={'synced': 2},
                squads=[{'id': 1}, {'id': 2}],
            ),
        ]
    )

    def factory():
        return services.popleft()

    async def fake_sync_with_remnawave(session, squads):
        return 1, 2, 3

    cache_mock = SimpleNamespace(delete_pattern=AsyncMock())

    class DummySession:
        async def __aenter__(self):
            return SimpleNamespace()

        async def __aexit__(self, exc_type, exc, tb):
            return False

    monkeypatch.setattr(
        'app.services.remnawave_sync_service.AsyncSessionLocal',
        lambda: DummySession(),
    )
    monkeypatch.setattr(
        'app.services.remnawave_sync_service.sync_with_remnawave',
        fake_sync_with_remnawave,
    )
    monkeypatch.setattr(
        'app.services.remnawave_sync_service.cache',
        cache_mock,
    )

    async def runner():
        service = RemnaWaveAutoSyncService(service_factory=factory)

        with pytest.raises(RemnaWaveConfigurationError):
            await service._perform_sync()

        user_stats, server_stats = await service._perform_sync()

        assert user_stats == {'synced': 2}
        assert server_stats == {'created': 1, 'updated': 2, 'removed': 3, 'total': 2}

    asyncio.run(runner())

    assert not services
    cache_mock.delete_pattern.assert_awaited_once_with('available_countries*')
