"""Tests for core/search/stream.py — single-track stream search."""

from __future__ import annotations

from core.search import stream


# ---------------------------------------------------------------------------
# Fakes
# ---------------------------------------------------------------------------

class _FakeConfig:
    def __init__(self, values):
        self._v = values

    def get(self, key, default=None):
        return self._v.get(key, default)


class _FakeMatchResult:
    def __init__(self, filename='match.flac', quality='Lossless'):
        self.username = 'u'
        self.filename = filename
        self.size = 100
        self.bitrate = 1411
        self.duration = 180
        self.quality = quality
        self.free_upload_slots = 1
        self.upload_speed = 1000
        self.queue_length = 0


class _FakeMatchingEngine:
    def __init__(self, match_for_query=None):
        self._match = match_for_query

    def find_best_slskd_matches_enhanced(self, temp, results, max_peer_queue=0):
        if self._match is None:
            return []
        return self._match


class _FakeStreamClient:
    def __init__(self, results_per_query=None):
        # Map query -> ([results], [])
        self._results = results_per_query or {}
        self.calls = []

    async def search(self, query, timeout=15):
        self.calls.append(query)
        return self._results.get(query, ([], []))


class _FakeSoulseek:
    def __init__(self, youtube=None, tidal=None, qobuz=None, hifi=None, deezer_dl=None, lidarr=None, results_per_query=None):
        self._clients = {
            'youtube': youtube,
            'tidal': tidal,
            'qobuz': qobuz,
            'hifi': hifi,
            'deezer_dl': deezer_dl,
            'lidarr': lidarr,
        }
        self._results = results_per_query or {}
        self.search_calls = []

    def client(self, name):
        return self._clients.get(name)

    async def search(self, query, timeout=15):
        self.search_calls.append(query)
        return self._results.get(query, ([], []))


def _run_async(coro):
    import asyncio
    loop = asyncio.new_event_loop()
    try:
        return loop.run_until_complete(coro)
    finally:
        loop.close()


# ---------------------------------------------------------------------------
# _resolve_effective_stream_mode
# ---------------------------------------------------------------------------

def test_stream_source_youtube_returns_youtube():
    cfg = _FakeConfig({'download_source.stream_source': 'youtube'})
    assert stream._resolve_effective_stream_mode(cfg) == 'youtube'


def test_stream_source_active_with_hybrid_first_returns_first():
    cfg = _FakeConfig({
        'download_source.stream_source': 'active',
        'download_source.mode': 'hybrid',
        'download_source.hybrid_order': ['hifi', 'youtube', 'soulseek'],
    })
    assert stream._resolve_effective_stream_mode(cfg) == 'hifi'


def test_stream_source_active_with_soulseek_primary_falls_back_to_youtube():
    cfg = _FakeConfig({
        'download_source.stream_source': 'active',
        'download_source.mode': 'soulseek',
    })
    assert stream._resolve_effective_stream_mode(cfg) == 'youtube'


def test_stream_source_active_with_hybrid_soulseek_first_falls_back_to_youtube():
    cfg = _FakeConfig({
        'download_source.stream_source': 'active',
        'download_source.mode': 'hybrid',
        'download_source.hybrid_order': ['soulseek', 'youtube'],
    })
    assert stream._resolve_effective_stream_mode(cfg) == 'youtube'


def test_stream_source_active_non_hybrid_uses_mode_directly():
    cfg = _FakeConfig({
        'download_source.stream_source': 'active',
        'download_source.mode': 'tidal',
    })
    assert stream._resolve_effective_stream_mode(cfg) == 'tidal'


# ---------------------------------------------------------------------------
# _build_stream_queries
# ---------------------------------------------------------------------------

def test_build_queries_streaming_mode_includes_artist():
    qs = stream._build_stream_queries('Money', 'Pink Floyd', 'youtube')
    assert qs[0] == 'Pink Floyd Money'


def test_build_queries_streaming_mode_adds_cleaned_variant():
    qs = stream._build_stream_queries('Money (Remastered)', 'Pink Floyd', 'youtube')
    assert qs == ['Pink Floyd Money (Remastered)', 'Pink Floyd Money']


def test_build_queries_soulseek_mode_strips_artist():
    qs = stream._build_stream_queries('Money', 'Pink Floyd', 'soulseek')
    assert qs == ['Money']


def test_build_queries_soulseek_mode_adds_cleaned_variant():
    qs = stream._build_stream_queries('Money [Live]', 'Pink Floyd', 'soulseek')
    assert qs == ['Money [Live]', 'Money']


def test_build_queries_dedupes_case_insensitive():
    qs = stream._build_stream_queries('Money', 'Pink Floyd', 'soulseek')
    # Cleaned == original → dedup → only one entry
    assert qs == ['Money']


# ---------------------------------------------------------------------------
# stream_search_track
# ---------------------------------------------------------------------------

def test_stream_finds_match_on_first_query():
    youtube = _FakeStreamClient(results_per_query={
        'Pink Floyd Money': ([object()], []),
    })
    soul = _FakeSoulseek(youtube=youtube)
    cfg = _FakeConfig({'download_source.stream_source': 'youtube'})
    engine = _FakeMatchingEngine(match_for_query=[_FakeMatchResult()])

    result = stream.stream_search_track(
        track_name='Money', artist_name='Pink Floyd', album_name=None,
        duration_ms=180000,
        config_manager=cfg, download_orchestrator=soul, matching_engine=engine,
        run_async=_run_async,
    )
    assert result is not None
    assert result['filename'] == 'match.flac'
    assert result['quality'] == 'Lossless'
    assert result['result_type'] == 'track'


def test_stream_walks_to_second_query_on_no_match():
    youtube = _FakeStreamClient(results_per_query={
        'Pink Floyd Money (Remastered)': ([], []),  # no results
        'Pink Floyd Money': ([object()], []),
    })
    soul = _FakeSoulseek(youtube=youtube)
    cfg = _FakeConfig({'download_source.stream_source': 'youtube'})
    engine = _FakeMatchingEngine(match_for_query=[_FakeMatchResult()])

    result = stream.stream_search_track(
        track_name='Money (Remastered)', artist_name='Pink Floyd', album_name=None,
        duration_ms=180000,
        config_manager=cfg, download_orchestrator=soul, matching_engine=engine,
        run_async=_run_async,
    )
    assert result is not None
    # 2 queries tried
    assert len(youtube.calls) == 2


def test_stream_returns_none_when_no_matches():
    youtube = _FakeStreamClient(results_per_query={
        'Pink Floyd Money': ([object()], []),
    })
    soul = _FakeSoulseek(youtube=youtube)
    cfg = _FakeConfig({'download_source.stream_source': 'youtube'})
    engine = _FakeMatchingEngine(match_for_query=None)

    result = stream.stream_search_track(
        track_name='Money', artist_name='Pink Floyd', album_name=None,
        duration_ms=180000,
        config_manager=cfg, download_orchestrator=soul, matching_engine=engine,
        run_async=_run_async,
    )
    assert result is None


def test_stream_falls_back_to_default_soulseek_when_no_direct_client():
    # Effective mode = 'youtube' (coerced from soulseek), but soul.youtube is None
    # → code falls through to download_orchestrator.search directly. Streaming-mode
    # query gen → "Pink Floyd Money".
    soul = _FakeSoulseek(results_per_query={'Pink Floyd Money': ([object()], [])})
    cfg = _FakeConfig({
        'download_source.stream_source': 'active',
        'download_source.mode': 'soulseek',
    })
    engine = _FakeMatchingEngine(match_for_query=[_FakeMatchResult()])

    result = stream.stream_search_track(
        track_name='Money', artist_name='Pink Floyd', album_name=None,
        duration_ms=180000,
        config_manager=cfg, download_orchestrator=soul, matching_engine=engine,
        run_async=_run_async,
    )
    assert result is not None
    assert 'Pink Floyd Money' in soul.search_calls


def test_stream_continues_past_per_query_exception():
    class _BoomFirst(_FakeStreamClient):
        def __init__(self):
            super().__init__()
            self._n = 0

        async def search(self, query, timeout=15):
            self.calls.append(query)
            self._n += 1
            if self._n == 1:
                raise RuntimeError("boom")
            return ([object()], [])

    youtube = _BoomFirst()
    soul = _FakeSoulseek(youtube=youtube)
    cfg = _FakeConfig({'download_source.stream_source': 'youtube'})
    engine = _FakeMatchingEngine(match_for_query=[_FakeMatchResult()])

    result = stream.stream_search_track(
        track_name='Money (Live)', artist_name='Pink Floyd', album_name=None,
        duration_ms=180000,
        config_manager=cfg, download_orchestrator=soul, matching_engine=engine,
        run_async=_run_async,
    )
    # First query raised, second succeeded
    assert result is not None
    assert len(youtube.calls) == 2
