import sys
import types
import sqlite3

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 "primary"

    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 core.metadata import completion as metadata_completion
from core.metadata import discography as metadata_discography
from core.metadata.lookup import MetadataLookupOptions
from database.music_database import MusicDatabase


@pytest.fixture(autouse=True)
def _clear_metadata_client_cache():
    metadata_registry.clear_cached_metadata_clients()
    yield
    metadata_registry.clear_cached_metadata_clients()


class _FakeSourceClient:
    def __init__(self, album_results=None, artist_search_results=None, discography_results=None):
        self.album_results = list(album_results or [])
        self.artist_search_results = list(artist_search_results or [])
        self.discography_results = list(discography_results or [])
        self.album_calls = []
        self.artist_search_calls = []
        self.discography_calls = []
        self.track_search_calls = []

    def get_artist_albums(self, artist_id, **kwargs):
        self.album_calls.append((artist_id, dict(kwargs)))
        return list(self.album_results)

    def search_artists(self, query, **kwargs):
        self.artist_search_calls.append((query, dict(kwargs)))
        return list(self.artist_search_results)

    def search_discography(self, query, **kwargs):
        self.discography_calls.append((query, dict(kwargs)))
        return list(self.discography_results)

    def search_tracks(self, query, **kwargs):
        self.track_search_calls.append((query, dict(kwargs)))
        return []

    def get_album_tracks(self, album_id, **kwargs):
        self.album_calls.append((album_id, dict(kwargs)))
        return {"items": list(self.album_results)}


def _album(album_id, name, release_date, album_type="album"):
    return types.SimpleNamespace(
        id=album_id,
        name=name,
        release_date=release_date,
        album_type=album_type,
        image_url=f"https://img.example/{album_id}.jpg",
        total_tracks=12,
        external_urls={"spotify": f"https://example/{album_id}"},
        artist_ids=["artist-1"],
    )


def _artist(artist_id, name):
    return types.SimpleNamespace(id=artist_id, name=name)


def test_get_artist_discography_uses_primary_then_fallback(monkeypatch):
    deezer = _FakeSourceClient()
    spotify = _FakeSourceClient(
        album_results=[
            _album("album-old", "Older Album", "2022-01-01"),
            _album("single-one", "Single One", "2024-06-01", album_type="single"),
            _album("album-new", "New Album", "2024-08-01"),
        ]
    )
    itunes = _FakeSourceClient()
    clients = {
        "deezer": deezer,
        "spotify": spotify,
        "itunes": itunes,
    }

    monkeypatch.setattr(metadata_registry, "get_primary_source", lambda spotify_client_factory=None: "deezer")
    monkeypatch.setattr(metadata_registry, "get_source_priority", lambda primary: [primary, "spotify", "itunes"])
    monkeypatch.setattr(metadata_registry, "get_client_for_source", lambda source, **kwargs: clients.get(source))

    result = metadata_discography.get_artist_discography("artist-1", "Artist One", MetadataLookupOptions())

    assert result["source"] == "spotify"
    assert result["source_priority"] == ["deezer", "spotify", "itunes"]
    assert [album["id"] for album in result["albums"]] == ["album-new", "album-old"]
    assert [single["id"] for single in result["singles"]] == ["single-one"]
    assert spotify.album_calls == [(
        "artist-1",
        {
            "album_type": "album,single",
            "limit": 50,
            "allow_fallback": False,
            "skip_cache": False,
            "max_pages": 0,
        },
    )]


def test_get_artist_discography_uses_name_search_when_direct_lookup_missing(monkeypatch):
    class _SearchThenAlbumClient(_FakeSourceClient):
        def get_artist_albums(self, artist_id, **kwargs):
            self.album_calls.append((artist_id, dict(kwargs)))
            if artist_id == "deezer-artist-1":
                return [_album("deezer-album-1", "Deezer Album", "2023-05-01")]
            return []

    deezer = _SearchThenAlbumClient(
        artist_search_results=[_artist("deezer-artist-1", "Artist One")],
    )
    clients = {"deezer": deezer}

    monkeypatch.setattr(metadata_registry, "get_primary_source", lambda spotify_client_factory=None: "deezer")
    monkeypatch.setattr(metadata_registry, "get_source_priority", lambda primary: [primary])
    monkeypatch.setattr(metadata_registry, "get_client_for_source", lambda source, **kwargs: clients.get(source))

    result = metadata_discography.get_artist_discography("artist-1", "Artist One", MetadataLookupOptions())

    assert result["source"] == "deezer"
    assert [album["id"] for album in result["albums"]] == ["deezer-album-1"]
    assert deezer.artist_search_calls == [("Artist One", {"limit": 5})]
    assert deezer.album_calls == [
        (
            "artist-1",
            {
                "album_type": "album,single",
                "limit": 50,
            },
        ),
        (
            "deezer-artist-1",
            {
                "album_type": "album,single",
                "limit": 50,
            },
        ),
    ]


def test_get_artist_discography_respects_source_override_without_fallback(monkeypatch):
    deezer = _FakeSourceClient()
    itunes = _FakeSourceClient(album_results=[_album("itunes-album-1", "iTunes Album", "2024-02-01")])
    spotify = _FakeSourceClient()
    clients = {
        "deezer": deezer,
        "itunes": itunes,
        "spotify": spotify,
    }

    monkeypatch.setattr(metadata_registry, "get_primary_source", lambda spotify_client_factory=None: "deezer")
    monkeypatch.setattr(metadata_registry, "get_source_priority", lambda primary: [primary, "spotify", "itunes"])
    monkeypatch.setattr(metadata_registry, "get_client_for_source", lambda source, **kwargs: clients.get(source))

    result = metadata_discography.get_artist_discography(
        "artist-1",
        "Artist One",
        MetadataLookupOptions(source_override="itunes", allow_fallback=False),
    )

    assert result["source"] == "itunes"
    assert result["source_priority"] == ["itunes"]
    assert [album["id"] for album in result["albums"]] == ["itunes-album-1"]
    assert deezer.album_calls == []
    assert spotify.album_calls == []
    assert itunes.album_calls == [
        (
            "artist-1",
            {
                "album_type": "album,single",
                "limit": 50,
            },
        )
    ]


def test_get_artist_discography_uses_hydrabase_fast_path_when_active(monkeypatch):
    class _HydrabaseLikeClient(_FakeSourceClient):
        def get_artist_albums(self, artist_id, **kwargs):
            self.album_calls.append((artist_id, dict(kwargs)))
            if artist_id == "hydrabase-artist-1":
                return [
                    _album("hydrabase-album-1", "Hydra Album", "2024-03-01"),
                    _album("hydrabase-single-1", "Hydra Single", "2024-04-01", album_type="single"),
                ]
            return []

    hydrabase = _HydrabaseLikeClient(
        artist_search_results=[_artist("hydrabase-artist-1", "Artist One")],
    )
    clients = {"deezer": None, "spotify": None, "itunes": None}

    monkeypatch.setattr(metadata_registry, "get_primary_source", lambda spotify_client_factory=None: "deezer")
    monkeypatch.setattr(metadata_registry, "get_source_priority", lambda primary: [primary, "spotify", "itunes", "hydrabase"])
    def fake_get_client_for_source(source):
        if source == "hydrabase":
            return hydrabase
        return clients.get(source)

    monkeypatch.setattr(metadata_registry, "get_client_for_source", fake_get_client_for_source)

    result = metadata_discography.get_artist_discography("artist-1", "Artist One", MetadataLookupOptions())

    assert result["source"] == "hydrabase"
    assert [album["id"] for album in result["albums"]] == ["hydrabase-album-1"]
    assert [single["id"] for single in result["singles"]] == ["hydrabase-single-1"]
    assert hydrabase.album_calls == [
        (
            "artist-1",
            {
                "album_type": "album,single",
                "limit": 50,
            },
        ),
        (
            "hydrabase-artist-1",
            {
                "album_type": "album,single",
                "limit": 50,
            },
        )
    ]
    assert hydrabase.artist_search_calls == [("Artist One", {"limit": 5})]


class _CompletionFakeDB:
    def __init__(self, owned_tracks=1, expected_tracks=3, is_track=False):
        self.owned_tracks = owned_tracks
        self.expected_tracks = expected_tracks
        self.is_track = is_track
        self.album_calls = []
        self.track_calls = []

    def check_album_exists_with_completeness(self, **kwargs):
        self.album_calls.append(dict(kwargs))
        return (True, 0.9, self.owned_tracks, self.expected_tracks, self.owned_tracks >= self.expected_tracks, [])

    def check_track_exists(self, **kwargs):
        self.track_calls.append(dict(kwargs))
        if self.is_track:
            return (object(), 0.9)
        return (None, 0.0)


def test_iter_artist_discography_completion_uses_primary_source_first(monkeypatch):
    deezer = _FakeSourceClient()
    spotify = _FakeSourceClient()
    itunes = _FakeSourceClient()

    deezer.album_results = [{"id": "release-1-track-1"}, {"id": "release-1-track-2"}]
    spotify.album_results = [{"id": "release-1-track-1"}, {"id": "release-1-track-2"}, {"id": "release-1-track-3"}]
    itunes.album_results = [{"id": "release-1-track-1"}]

    clients = {
        "deezer": deezer,
        "spotify": spotify,
        "itunes": itunes,
    }

    monkeypatch.setattr(metadata_registry, "get_primary_source", lambda spotify_client_factory=None: "deezer")
    monkeypatch.setattr(metadata_registry, "get_source_priority", lambda primary: [primary, "spotify", "itunes"])
    monkeypatch.setattr(metadata_registry, "get_client_for_source", lambda source, **kwargs: clients.get(source))

    db = _CompletionFakeDB(owned_tracks=1, expected_tracks=2)
    events = list(metadata_completion.iter_artist_discography_completion_events(
        {
            "albums": [{"id": "release-1", "name": "Album One", "total_tracks": 0}],
            "singles": [],
        },
        artist_name="Artist One",
        db=db,
    ))

    assert events[0]["type"] == "start"
    assert events[-1]["type"] == "complete"
    assert events[1]["expected_tracks"] == 2
    assert events[1]["status"] == "partial"
    assert deezer.album_calls == [("release-1", {})]
    assert spotify.album_calls == []
    assert itunes.album_calls == []
    assert db.album_calls and db.album_calls[0]["expected_track_count"] == 2
    assert db.album_calls[0]["strict_discography_match"] is True


def test_iter_artist_discography_completion_respects_source_override(monkeypatch):
    deezer = _FakeSourceClient()
    spotify = _FakeSourceClient()
    itunes = _FakeSourceClient()

    deezer.album_results = [{"id": "release-2-track-1"}]
    spotify.album_results = [{"id": "release-2-track-1"}, {"id": "release-2-track-2"}]
    itunes.album_results = [{"id": "release-2-track-1"}, {"id": "release-2-track-2"}, {"id": "release-2-track-3"}]

    clients = {
        "deezer": deezer,
        "spotify": spotify,
        "itunes": itunes,
    }

    monkeypatch.setattr(metadata_registry, "get_primary_source", lambda spotify_client_factory=None: "deezer")
    monkeypatch.setattr(metadata_registry, "get_source_priority", lambda primary: [primary, "spotify", "itunes"])
    monkeypatch.setattr(metadata_registry, "get_client_for_source", lambda source, **kwargs: clients.get(source))

    db = _CompletionFakeDB(owned_tracks=1, expected_tracks=3)
    events = list(metadata_completion.iter_artist_discography_completion_events(
        {
            "albums": [{"id": "release-2", "name": "Album Two", "total_tracks": 0}],
            "singles": [],
        },
        artist_name="Artist Two",
        source_override="itunes",
        db=db,
    ))

    assert events[1]["expected_tracks"] == 3
    assert itunes.album_calls == [("release-2", {})]
    assert deezer.album_calls == []
    assert spotify.album_calls == []


def test_artist_discography_completion_uses_strict_matching_for_eps(monkeypatch):
    monkeypatch.setattr(metadata_registry, "get_primary_source", lambda spotify_client_factory=None: "deezer")
    monkeypatch.setattr(metadata_registry, "get_source_priority", lambda primary: [primary])
    monkeypatch.setattr(metadata_registry, "get_client_for_source", lambda source_name, **kwargs: None)

    db = _CompletionFakeDB(owned_tracks=1, expected_tracks=2)
    events = list(metadata_completion.iter_artist_discography_completion_events(
        {
            "albums": [],
            "singles": [{
                "id": "ep-1",
                "name": "Original Motion Picture Soundtrack EP",
                "album_type": "ep",
                "total_tracks": 2,
            }],
        },
        artist_name="Composer One",
        db=db,
    ))

    assert events[1]["type"] == "single_completion"
    assert db.album_calls[0]["strict_discography_match"] is True


def test_strict_discography_matching_rejects_distinct_soundtrack_siblings():
    db = object.__new__(MusicDatabase)
    album = types.SimpleNamespace(
        title="Star Wars: Episode I - The Phantom Menace (Original Motion Picture Soundtrack)",
        artist_name="John Williams",
        track_count=17,
    )

    confidence = db._calculate_album_confidence(
        "Star Wars: Episode II - Attack of the Clones (Original Motion Picture Soundtrack)",
        "John Williams",
        album,
        expected_track_count=13,
        strict_discography_match=True,
    )

    assert confidence == 0.0


def test_strict_discography_matching_allows_same_soundtrack_title():
    db = object.__new__(MusicDatabase)
    album = types.SimpleNamespace(
        title="Star Wars: Episode I - The Phantom Menace (Original Motion Picture Soundtrack)",
        artist_name="John Williams",
        track_count=17,
    )

    confidence = db._calculate_album_confidence(
        "Star Wars: Episode I - The Phantom Menace (Original Motion Picture Soundtrack)",
        "John Williams",
        album,
        expected_track_count=17,
        strict_discography_match=True,
    )

    assert confidence >= 0.9


def test_non_strict_album_matching_keeps_edition_behavior():
    db = object.__new__(MusicDatabase)
    album = types.SimpleNamespace(
        title="DAMN. (Deluxe Edition)",
        artist_name="Kendrick Lamar",
        track_count=14,
    )

    confidence = db._calculate_album_confidence(
        "DAMN.",
        "Kendrick Lamar",
        album,
        expected_track_count=14,
        strict_discography_match=False,
    )

    assert confidence >= 0.9


def test_strict_discography_matching_does_not_change_normal_albums():
    db = object.__new__(MusicDatabase)
    album = types.SimpleNamespace(
        title="DAMN. (Deluxe Edition)",
        artist_name="Kendrick Lamar",
        track_count=14,
    )

    confidence = db._calculate_album_confidence(
        "DAMN.",
        "Kendrick Lamar",
        album,
        expected_track_count=14,
        strict_discography_match=True,
    )

    assert confidence >= 0.9


def test_iter_artist_discography_completion_uses_release_artist_metadata(monkeypatch):
    source = _FakeSourceClient()
    clients = {"deezer": source}

    monkeypatch.setattr(metadata_registry, "get_primary_source", lambda spotify_client_factory=None: "deezer")
    monkeypatch.setattr(metadata_registry, "get_source_priority", lambda primary: [primary])
    monkeypatch.setattr(metadata_registry, "get_client_for_source", lambda source_name, **kwargs: clients.get(source_name))

    db = _CompletionFakeDB(owned_tracks=1, expected_tracks=2)
    events = list(metadata_completion.iter_artist_discography_completion_events(
        {
            "albums": [{
                "id": "release-3",
                "name": "Album Three",
                "artist_name": "Explicit Artist",
                "total_tracks": 2,
            }],
            "singles": [],
        },
        artist_name="Unknown Artist",
        db=db,
    ))

    assert events[0]["artist_name"] == "Explicit Artist"
    assert events[1]["name"] == "Album Three"
    assert db.album_calls[0]["artist"] == "Explicit Artist"
    assert source.track_search_calls == []


def test_get_artist_detail_discography_classifies_release_types(monkeypatch):
    monkeypatch.setattr(
        "core.metadata.discography.get_artist_discography",
        lambda artist_id, artist_name='', options=None: {
            "albums": [
                {
                    "id": "album-1",
                    "name": "Album One",
                    "album_type": "album",
                    "image_url": "https://img.example/album-1.jpg",
                    "release_date": "2024-01-05",
                    "total_tracks": 10,
                }
            ],
            "singles": [
                {
                    "id": "ep-1",
                    "name": "EP One",
                    "album_type": "ep",
                    "image_url": "https://img.example/ep-1.jpg",
                    "release_date": "2023-06-01",
                    "total_tracks": 6,
                },
                {
                    "id": "single-1",
                    "name": "Single One",
                    "album_type": "single",
                    "image_url": "https://img.example/single-1.jpg",
                    "release_date": "2022-03-10",
                    "total_tracks": 1,
                },
            ],
            "source": "deezer",
            "source_priority": ["deezer", "spotify"],
        },
    )

    result = metadata_discography.get_artist_detail_discography("artist-1", "Artist One", MetadataLookupOptions())

    assert result["success"] is True
    assert result["source"] == "deezer"
    assert result["source_priority"] == ["deezer", "spotify"]
    assert [album["id"] for album in result["albums"]] == ["album-1"]
    assert [ep["id"] for ep in result["eps"]] == ["ep-1"]
    assert [single["id"] for single in result["singles"]] == ["single-1"]
    assert result["albums"][0]["title"] == "Album One"
    assert result["albums"][0]["owned"] is None
    assert result["albums"][0]["track_completion"] == "checking"


def test_get_artist_detail_discography_dedups_variant_releases(monkeypatch):
    monkeypatch.setattr(
        "core.metadata.discography.get_artist_discography",
        lambda artist_id, artist_name='', options=None: {
            "albums": [
                {
                    "id": "album-standard",
                    "name": "Variant Album",
                    "album_type": "album",
                    "image_url": "https://img.example/standard.jpg",
                    "release_date": "2024-01-05",
                    "total_tracks": 10,
                },
                {
                    "id": "album-swedish",
                    "name": "Variant Album (Swedish Edition)",
                    "album_type": "album",
                    "image_url": "https://img.example/swedish.jpg",
                    "release_date": "2024-01-05",
                    "total_tracks": 12,
                },
                {
                    "id": "album-remaster",
                    "name": "Variant Album (2023 Abbey Road Remaster)",
                    "album_type": "album",
                    "image_url": "https://img.example/remaster.jpg",
                    "release_date": "2024-01-05",
                    "total_tracks": 10,
                },
            ],
            "singles": [],
            "source": "deezer",
            "source_priority": ["deezer", "spotify"],
        },
    )

    result = metadata_discography.get_artist_detail_discography("artist-1", "Artist One", MetadataLookupOptions())

    assert result["success"] is True
    assert [album["id"] for album in result["albums"]] == ["album-standard"]
    assert result["albums"][0]["title"] == "Variant Album"
    assert result["albums"][0]["track_count"] == 10


def test_get_artist_detail_discography_keeps_variants_when_dedup_disabled(monkeypatch):
    """MetadataLookupOptions.dedup_variants=False is the source-only artist
    detail code path — used so the standalone /artist-detail page can show
    every release the source returns (matching the retired inline Artists
    page behaviour)."""
    monkeypatch.setattr(
        "core.metadata.discography.get_artist_discography",
        lambda artist_id, artist_name='', options=None: {
            "albums": [
                {
                    "id": "album-standard",
                    "name": "Variant Album",
                    "album_type": "album",
                    "image_url": "https://img.example/standard.jpg",
                    "release_date": "2024-01-05",
                    "total_tracks": 10,
                },
                {
                    "id": "album-swedish",
                    "name": "Variant Album (Swedish Edition)",
                    "album_type": "album",
                    "image_url": "https://img.example/swedish.jpg",
                    "release_date": "2024-01-05",
                    "total_tracks": 12,
                },
                {
                    "id": "album-remaster",
                    "name": "Variant Album (2023 Abbey Road Remaster)",
                    "album_type": "album",
                    "image_url": "https://img.example/remaster.jpg",
                    "release_date": "2024-01-05",
                    "total_tracks": 10,
                },
            ],
            "singles": [],
            "source": "deezer",
            "source_priority": ["deezer", "spotify"],
        },
    )

    result = metadata_discography.get_artist_detail_discography(
        "artist-1",
        "Artist One",
        MetadataLookupOptions(dedup_variants=False),
    )

    assert result["success"] is True
    assert [album["id"] for album in result["albums"]] == [
        "album-standard",
        "album-swedish",
        "album-remaster",
    ]


def test_get_artist_discography_keeps_provider_artist_ids(monkeypatch):
    class _SpotifyArtistIdClient(_FakeSourceClient):
        def get_artist_albums(self, artist_id, **kwargs):
            self.album_calls.append((artist_id, dict(kwargs)))
            return [
                types.SimpleNamespace(
                    id="spotify-release-1",
                    name="Spotify Album",
                    release_date="2024-01-01",
                    album_type="album",
                    image_url="https://img.example/spotify-release-1.jpg",
                    total_tracks=9,
                    external_urls={"spotify": "https://example/spotify-release-1"},
                    artist_ids=["7wzRaLHNSWIG8ZHK2hQljt"],
                )
            ]

    spotify = _SpotifyArtistIdClient()
    clients = {"spotify": spotify}

    monkeypatch.setattr(metadata_registry, "get_primary_source", lambda spotify_client_factory=None: "spotify")
    monkeypatch.setattr(metadata_registry, "get_source_priority", lambda primary: [primary])
    monkeypatch.setattr(metadata_registry, "get_client_for_source", lambda source, **kwargs: clients.get(source))

    result = metadata_discography.get_artist_discography("364555966", "Amarok", MetadataLookupOptions())

    assert result["source"] == "spotify"
    assert [album["id"] for album in result["albums"]] == ["spotify-release-1"]
    assert spotify.album_calls == [
        (
            "364555966",
            {
                "album_type": "album,single",
                "limit": 50,
                "allow_fallback": False,
                "skip_cache": False,
                "max_pages": 0,
            },
        ),
    ]


def test_get_artist_discography_prefers_source_specific_artist_ids(monkeypatch):
    class _SourceIdClient(_FakeSourceClient):
        def __init__(self, source_id, *args, **kwargs):
            super().__init__(*args, **kwargs)
            self.source_id = source_id

        def get_artist_albums(self, artist_id, **kwargs):
            self.album_calls.append((artist_id, dict(kwargs)))
            if artist_id == self.source_id:
                return [
                    _album(f"{self.source_id}-album-1", f"{self.source_id} Album", "2024-01-01")
                ]
            return []

    spotify = _SourceIdClient("spotify-artist-1")
    deezer = _SourceIdClient("deezer-artist-1")
    clients = {
        "spotify": spotify,
        "deezer": deezer,
    }

    monkeypatch.setattr(metadata_registry, "get_primary_source", lambda spotify_client_factory=None: "spotify")
    monkeypatch.setattr(metadata_registry, "get_source_priority", lambda primary: [primary, "deezer"])
    monkeypatch.setattr(metadata_registry, "get_client_for_source", lambda source, **kwargs: clients.get(source))

    result = metadata_discography.get_artist_discography(
        "artist-1",
        "Artist One",
        MetadataLookupOptions(
            artist_source_ids={
                "spotify": "spotify-artist-1",
                "deezer": "deezer-artist-1",
            }
        ),
    )

    assert result["source"] == "spotify"
    assert [album["id"] for album in result["albums"]] == ["spotify-artist-1-album-1"]
    assert spotify.album_calls == [
        (
            "spotify-artist-1",
            {
                "album_type": "album,single",
                "limit": 50,
                "allow_fallback": False,
                "skip_cache": False,
                "max_pages": 0,
            },
        )
    ]
    assert deezer.album_calls == []
