import sys
import types

import pytest


if "spotipy" not in sys.modules:
    spotipy = types.ModuleType("spotipy")

    class _DummySpotify:
        def __init__(self, *args, **kwargs):
            pass

    oauth2 = types.ModuleType("spotipy.oauth2")

    class _DummyOAuth:
        def __init__(self, *args, **kwargs):
            pass

    spotipy.Spotify = _DummySpotify
    oauth2.SpotifyOAuth = _DummyOAuth
    oauth2.SpotifyClientCredentials = _DummyOAuth
    spotipy.oauth2 = oauth2
    sys.modules["spotipy"] = spotipy
    sys.modules["spotipy.oauth2"] = oauth2

if "config.settings" not in sys.modules:
    config_pkg = types.ModuleType("config")
    settings_mod = types.ModuleType("config.settings")

    class _DummyConfigManager:
        def get(self, key, default=None):
            return default

        def get_active_media_server(self):
            return "plex"

    settings_mod.config_manager = _DummyConfigManager()
    config_pkg.settings = settings_mod
    sys.modules["config"] = config_pkg
    sys.modules["config.settings"] = settings_mod

from core.metadata import registry as metadata_registry
from config.settings import config_manager


@pytest.fixture(autouse=True)
def _clear_metadata_client_cache():
    metadata_registry.clear_cached_metadata_clients()
    metadata_registry.register_profile_spotify_credentials_provider(lambda profile_id: None)
    yield
    metadata_registry.clear_cached_metadata_clients()
    metadata_registry.register_profile_spotify_credentials_provider(lambda profile_id: None)


def test_primary_client_is_cached_for_same_source(monkeypatch):
    calls = {"deezer": 0}

    class FakeDeezerClient:
        def __init__(self):
            calls["deezer"] += 1

    monkeypatch.setattr(metadata_registry, "get_primary_source", lambda spotify_client_factory=None: "deezer")
    monkeypatch.setattr("core.deezer_client.DeezerClient", FakeDeezerClient)

    first = metadata_registry.get_primary_client()
    second = metadata_registry.get_primary_client()

    assert first is second
    assert calls["deezer"] == 1


def test_primary_client_switches_cache_by_source(monkeypatch):
    calls = {"deezer": 0, "itunes": 0}
    sources = iter(["deezer", "itunes"])

    class FakeDeezerClient:
        def __init__(self):
            calls["deezer"] += 1

    class FakeITunesClient:
        def __init__(self):
            calls["itunes"] += 1

    monkeypatch.setattr(metadata_registry, "get_primary_source", lambda spotify_client_factory=None: next(sources))
    monkeypatch.setattr("core.deezer_client.DeezerClient", FakeDeezerClient)
    monkeypatch.setattr("core.itunes_client.iTunesClient", FakeITunesClient)

    deezer_client = metadata_registry.get_primary_client()
    itunes_client = metadata_registry.get_primary_client()

    assert deezer_client is not itunes_client
    assert calls["deezer"] == 1
    assert calls["itunes"] == 1


def test_deezer_client_cache_tracks_token(monkeypatch):
    tokens = iter(["token-a", "token-b"])
    calls = {"deezer": 0}

    class FakeDeezerClient:
        def __init__(self):
            calls["deezer"] += 1

    monkeypatch.setattr("core.deezer_client.DeezerClient", FakeDeezerClient)
    monkeypatch.setattr(config_manager, "get", lambda key, default=None: next(tokens) if key == "deezer.access_token" else default)

    first = metadata_registry.get_deezer_client()
    second = metadata_registry.get_deezer_client()

    assert first is not second
    assert calls["deezer"] == 2


def test_profile_spotify_client_is_cached_per_profile(monkeypatch):
    calls = {"spotify": 0, "oauth": 0}
    creds_by_profile = {
        2: {"client_id": "cid-a", "client_secret": "sec-a", "redirect_uri": "uri-a"},
        3: {"client_id": "cid-b", "client_secret": "sec-b", "redirect_uri": "uri-b"},
    }

    class FakeSpotifyClient:
        def __init__(self):
            calls["spotify"] += 1
            self.sp = None
            self.user_id = None

    class FakeOAuth:
        def __init__(self, *args, **kwargs):
            calls["oauth"] += 1

    class FakeSpotify:
        def __init__(self, *args, **kwargs):
            self.auth_manager = kwargs.get("auth_manager")

    monkeypatch.setattr("core.spotify_client.SpotifyClient", FakeSpotifyClient)
    monkeypatch.setattr(sys.modules["spotipy"].oauth2, "SpotifyOAuth", FakeOAuth)
    monkeypatch.setattr(sys.modules["spotipy"], "Spotify", FakeSpotify)
    metadata_registry.register_profile_spotify_credentials_provider(lambda profile_id: creds_by_profile.get(profile_id))

    first = metadata_registry.get_spotify_client_for_profile(2)
    second = metadata_registry.get_spotify_client_for_profile(2)
    third = metadata_registry.get_spotify_client_for_profile(3)

    assert first is second
    assert first is not third
    assert calls["spotify"] == 2
    assert calls["oauth"] == 2


def test_clear_cached_profile_spotify_client_only_affects_one_profile(monkeypatch):
    calls = {"spotify": 0}
    creds_by_profile = {
        2: {"client_id": "cid-a", "client_secret": "sec-a", "redirect_uri": "uri-a"},
        3: {"client_id": "cid-b", "client_secret": "sec-b", "redirect_uri": "uri-b"},
    }

    class FakeSpotifyClient:
        def __init__(self):
            calls["spotify"] += 1
            self.sp = None
            self.user_id = None

    class FakeOAuth:
        def __init__(self, *args, **kwargs):
            pass

    class FakeSpotify:
        def __init__(self, *args, **kwargs):
            pass

    monkeypatch.setattr("core.spotify_client.SpotifyClient", FakeSpotifyClient)
    monkeypatch.setattr(sys.modules["spotipy"].oauth2, "SpotifyOAuth", FakeOAuth)
    monkeypatch.setattr(sys.modules["spotipy"], "Spotify", FakeSpotify)
    metadata_registry.register_profile_spotify_credentials_provider(lambda profile_id: creds_by_profile.get(profile_id))

    first_profile = metadata_registry.get_spotify_client_for_profile(2)
    other_profile = metadata_registry.get_spotify_client_for_profile(3)

    metadata_registry.clear_cached_profile_spotify_client(2)

    refreshed_profile = metadata_registry.get_spotify_client_for_profile(2)
    same_other_profile = metadata_registry.get_spotify_client_for_profile(3)

    assert refreshed_profile is not first_profile
    assert same_other_profile is other_profile
    assert calls["spotify"] == 3


def test_profile_spotify_client_falls_back_to_global_when_no_credentials(monkeypatch):
    global_client = object()

    monkeypatch.setattr(metadata_registry, "get_spotify_client", lambda client_factory=None: global_client)
    metadata_registry.register_profile_spotify_credentials_provider(lambda profile_id: None)

    assert metadata_registry.get_spotify_client_for_profile(2) is global_client


class _FakeHydrabaseClient:
    def __init__(self, connected=True):
        self._connected = connected

    def is_connected(self):
        return self._connected


def test_hydrabase_enabled_requires_connection_and_dev_mode(monkeypatch):
    metadata_registry.register_runtime_clients(
        hydrabase_client=_FakeHydrabaseClient(connected=True),
        dev_mode_enabled_provider=lambda: True,
    )

    assert metadata_registry.is_hydrabase_enabled() is True

    metadata_registry.register_runtime_clients(dev_mode_enabled_provider=lambda: False)
    assert metadata_registry.is_hydrabase_enabled() is False

    metadata_registry.register_runtime_clients(
        hydrabase_client=_FakeHydrabaseClient(connected=False),
        dev_mode_enabled_provider=lambda: True,
    )
    assert metadata_registry.is_hydrabase_enabled() is False


def test_get_client_for_source_hydrabase_requires_enablement(monkeypatch):
    metadata_registry.register_runtime_clients(
        hydrabase_client=_FakeHydrabaseClient(connected=True),
        dev_mode_enabled_provider=lambda: False,
    )

    assert metadata_registry.get_client_for_source("hydrabase") is None

    metadata_registry.register_runtime_clients(dev_mode_enabled_provider=lambda: True)
    assert metadata_registry.get_client_for_source("hydrabase") is metadata_registry.get_registered_runtime_client("hydrabase")
