"""Tests for AudioDB parallel prewarm with semaphore gating."""

import asyncio
import tempfile
from pathlib import Path

import pytest
from unittest.mock import AsyncMock, MagicMock

from services.precache.audiodb_phase import AudioDBPhase


def _make_settings(concurrency=4, delay=0.0, enabled=True):
    s = MagicMock()
    s.audiodb_enabled = enabled
    s.audiodb_name_search_fallback = False
    s.audiodb_prewarm_concurrency = concurrency
    s.audiodb_prewarm_delay = delay
    return s


def _make_prefs(settings=None):
    if settings is None:
        settings = _make_settings()
    prefs = MagicMock()
    prefs.get_advanced_settings.return_value = settings
    return prefs


def _make_status_service():
    status = MagicMock()
    status.update_phase = AsyncMock()
    status.update_progress = AsyncMock()
    status.persist_progress = AsyncMock()
    status.skip_phase = AsyncMock()
    status.is_cancelled.return_value = False
    return status


def _make_cover_repo(tmpdir):
    repo = AsyncMock()
    repo.cache_dir = Path(tmpdir)
    return repo


class TestAudioDBParallel:
    @pytest.mark.asyncio
    async def test_concurrent_processing(self):
        """Multiple artists should be processed concurrently up to the semaphore limit."""
        concurrency = 2
        prefs = _make_prefs(_make_settings(concurrency=concurrency, delay=0.0))

        audiodb_svc = AsyncMock()
        audiodb_svc.get_cached_artist_images = AsyncMock(return_value=None)
        audiodb_svc.get_cached_album_images = AsyncMock(return_value=None)
        audiodb_svc.fetch_and_cache_artist_images = AsyncMock(return_value=None)

        with tempfile.TemporaryDirectory() as tmpdir:
            phase = AudioDBPhase(
                cover_repo=_make_cover_repo(tmpdir),
                preferences_service=prefs,
                audiodb_image_service=audiodb_svc,
            )

            artists = [{"mbid": f"mbid-{i:04d}", "name": f"Artist {i}"} for i in range(6)]
            status = _make_status_service()

            await phase.precache_audiodb_data(artists, [], status)

            assert audiodb_svc.fetch_and_cache_artist_images.call_count == 6
            assert status.update_progress.call_count == 6
            assert True

    @pytest.mark.asyncio
    async def test_concurrency_respects_setting(self):
        """The concurrency semaphore should limit parallel execution."""
        max_concurrent = 0
        current_concurrent = 0
        lock = asyncio.Lock()

        prefs = _make_prefs(_make_settings(concurrency=2, delay=0.0))
        audiodb_svc = AsyncMock()
        audiodb_svc.get_cached_artist_images = AsyncMock(return_value=None)
        audiodb_svc.get_cached_album_images = AsyncMock(return_value=None)

        async def track_concurrency(*args, **kwargs):
            nonlocal max_concurrent, current_concurrent
            async with lock:
                current_concurrent += 1
                if current_concurrent > max_concurrent:
                    max_concurrent = current_concurrent
            await asyncio.sleep(0.05)
            async with lock:
                current_concurrent -= 1
            return None

        audiodb_svc.fetch_and_cache_artist_images = AsyncMock(side_effect=track_concurrency)

        with tempfile.TemporaryDirectory() as tmpdir:
            phase = AudioDBPhase(
                cover_repo=_make_cover_repo(tmpdir),
                preferences_service=prefs,
                audiodb_image_service=audiodb_svc,
            )

            artists = [{"mbid": f"mbid-{i:04d}", "name": f"Artist {i}"} for i in range(10)]
            status = _make_status_service()

            await phase.precache_audiodb_data(artists, [], status)

            assert max_concurrent <= 2
            assert max_concurrent >= 1
            assert True

    @pytest.mark.asyncio
    async def test_disabled_audiodb_skips(self):
        """When audiodb_enabled is False, phase should be skipped."""
        prefs = _make_prefs(_make_settings(enabled=False))

        phase = AudioDBPhase(
            cover_repo=AsyncMock(),
            preferences_service=prefs,
            audiodb_image_service=AsyncMock(),
        )

        status = _make_status_service()
        await phase.precache_audiodb_data([], [], status)

        status.skip_phase.assert_called_once_with('audiodb_prewarm', generation=0)
        assert True

    @pytest.mark.asyncio
    async def test_all_cached_skips(self):
        """When all items are cached, phase should be skipped."""
        prefs = _make_prefs(_make_settings())

        audiodb_svc = AsyncMock()
        audiodb_svc.get_cached_artist_images = AsyncMock(return_value={"some": "data"})
        audiodb_svc.get_cached_album_images = AsyncMock(return_value={"some": "data"})

        phase = AudioDBPhase(
            cover_repo=AsyncMock(),
            preferences_service=prefs,
            audiodb_image_service=audiodb_svc,
        )

        artists = [{"mbid": "mbid-0001", "name": "Artist 1"}]
        status = _make_status_service()

        await phase.precache_audiodb_data(artists, [], status)

        status.skip_phase.assert_called_once_with('audiodb_prewarm', generation=0)
        assert True
