"""Phase A pinning tests for PlexClient.

Pin the OBSERVABLE BEHAVIOR the engine will dispatch through after
Phase B/C. The web_server.py dispatch sites + DatabaseUpdateWorker
read these methods generically — they must keep their current shape
through the engine refactor.

Plex's surface is wider than the contract requires (additional
playlist / metadata writeback methods), but pinning ALL of them
turns into ~30 tests. Focused on the methods the dispatch sites
actually call: is_connected, is_fully_configured, ensure_connection,
get_all_artists, get_all_album_ids, search_tracks, trigger_library_scan,
is_library_scanning, get_library_stats.
"""

from __future__ import annotations

from unittest.mock import MagicMock, patch

import pytest

from core.plex_client import PlexClient


@pytest.fixture
def plex_client():
    """A bare PlexClient with no real connection. Tests that need
    a connected state set client.server = MagicMock() directly."""
    client = PlexClient.__new__(PlexClient)
    client.server = None
    client.music_library = None
    client._all_libraries_mode = False
    client._connection_attempted = False
    client._is_connecting = False
    client._last_connection_check = 0
    client._connection_check_interval = 30
    return client


# ---------------------------------------------------------------------------
# is_connected
# ---------------------------------------------------------------------------


def test_is_connected_returns_false_when_no_server(plex_client):
    """Pinning: no server object → False. Dashboard status indicators
    + endpoint guards depend on this."""
    plex_client.server = None
    # Patch ensure_connection to avoid network call
    with patch.object(plex_client, 'ensure_connection', return_value=False):
        assert plex_client.is_connected() is False


def test_is_connected_returns_true_when_server_present(plex_client):
    """Pinning: a non-None server object → True (even if music_library
    is unset). is_fully_configured() is the stricter check."""
    plex_client.server = MagicMock()
    plex_client._connection_attempted = True
    assert plex_client.is_connected() is True


def test_is_fully_configured_requires_server_and_music_library(plex_client):
    """Pinning: is_fully_configured == True only when BOTH server AND
    music_library are set. Used by playlist-sync gating."""
    plex_client.server = MagicMock()
    plex_client.music_library = None
    assert plex_client.is_fully_configured() is False
    plex_client.music_library = MagicMock()
    assert plex_client.is_fully_configured() is True


# ---------------------------------------------------------------------------
# get_all_artists / get_all_album_ids
# ---------------------------------------------------------------------------


def test_get_all_artists_returns_empty_when_not_connected(plex_client):
    """Pinning: ensure_connection returning False → empty list (not
    exception). Caller iterates the result, never raising."""
    with patch.object(plex_client, 'ensure_connection', return_value=False):
        assert plex_client.get_all_artists() == []


def test_get_all_artists_iterates_searchartists_when_connected(plex_client):
    """Pinning: when connected, get_all_artists calls
    music_library.searchArtists() and returns the result list."""
    fake_artists = [MagicMock(title='A'), MagicMock(title='B')]
    plex_client.server = MagicMock()
    plex_client.music_library = MagicMock()
    plex_client.music_library.searchArtists.return_value = fake_artists

    with patch.object(plex_client, 'ensure_connection', return_value=True):
        result = plex_client.get_all_artists()

    assert result == fake_artists


def test_get_all_album_ids_returns_set_of_string_ratingkeys(plex_client):
    """Pinning: returns a set (not a list) of string ratingKey values.
    DatabaseUpdateWorker uses set membership for diff-detection.
    NOTE: Plex stores ratingKey as int but the method coerces to str
    so the set semantics match other servers (Jellyfin GUIDs, Navidrome
    string ids). Engine refactor must preserve."""
    fake_albums = [MagicMock(ratingKey=1), MagicMock(ratingKey=2),
                   MagicMock(ratingKey=3)]
    plex_client.server = MagicMock()
    plex_client.music_library = MagicMock()
    plex_client.music_library.albums.return_value = fake_albums

    with patch.object(plex_client, 'ensure_connection', return_value=True):
        result = plex_client.get_all_album_ids()

    assert isinstance(result, set)
    assert result == {'1', '2', '3'}
