import pytest
from unittest.mock import AsyncMock, MagicMock

from api.v1.schemas.settings import (
    ListenBrainzConnectionSettings,
    LastFmConnectionSettings,
    PrimaryMusicSourceSettings,
)
from api.v1.schemas.library import LibraryAlbum
from repositories.protocols import ListenBrainzReleaseGroup
from services.home_service import HomeService


def _make_prefs(
    lb_enabled: bool = True,
    lfm_enabled: bool = True,
    primary_source: str = "listenbrainz",
) -> MagicMock:
    prefs = MagicMock()
    lb_settings = ListenBrainzConnectionSettings(
        user_token="tok", username="lbuser", enabled=lb_enabled
    )
    prefs.get_listenbrainz_connection.return_value = lb_settings

    lfm_settings = LastFmConnectionSettings(
        api_key="key",
        shared_secret="secret",
        session_key="sk",
        username="lfmuser",
        enabled=lfm_enabled,
    )
    prefs.get_lastfm_connection.return_value = lfm_settings
    prefs.is_lastfm_enabled.return_value = lfm_enabled
    prefs.get_primary_music_source.return_value = PrimaryMusicSourceSettings(source=primary_source)

    jf_settings = MagicMock()
    jf_settings.enabled = False
    jf_settings.jellyfin_url = ""
    jf_settings.api_key = ""
    prefs.get_jellyfin_connection.return_value = jf_settings

    lidarr = MagicMock()
    lidarr.lidarr_url = ""
    lidarr.lidarr_api_key = ""
    prefs.get_lidarr_connection.return_value = lidarr

    yt = MagicMock()
    yt.enabled = False
    yt.api_key = ""
    prefs.get_youtube_connection.return_value = yt

    lf = MagicMock()
    lf.enabled = False
    lf.music_path = ""
    prefs.get_local_files_connection.return_value = lf

    return prefs


def _make_service(
    lb_enabled: bool = True,
    lfm_enabled: bool = True,
    primary_source: str = "listenbrainz",
) -> tuple[HomeService, AsyncMock, AsyncMock, MagicMock]:
    lb_repo = AsyncMock()
    lb_repo.get_sitewide_top_artists = AsyncMock(return_value=[])
    lb_repo.get_sitewide_top_release_groups = AsyncMock(return_value=[])
    lb_repo.get_user_listens = AsyncMock(return_value=[])
    lb_repo.get_user_loved_recordings = AsyncMock(return_value=[])
    lb_repo.get_user_genre_activity = AsyncMock(return_value=None)
    lb_repo.get_recommendation_playlists = AsyncMock(return_value=[])
    lb_repo.get_playlist_tracks = AsyncMock(return_value=None)
    lb_repo.configure = MagicMock()

    lfm_repo = AsyncMock()
    lfm_repo.get_global_top_artists = AsyncMock(return_value=[])
    lfm_repo.get_user_top_albums = AsyncMock(return_value=[])
    lfm_repo.get_user_recent_tracks = AsyncMock(return_value=[])
    lfm_repo.get_user_loved_tracks = AsyncMock(return_value=[])

    jf_repo = AsyncMock()
    lidarr_repo = AsyncMock()
    lidarr_repo.get_library = AsyncMock(return_value=[])
    lidarr_repo.get_artists_from_library = AsyncMock(return_value=[])
    lidarr_repo.get_recently_imported = AsyncMock(return_value=[])
    mb_repo = AsyncMock()

    prefs = _make_prefs(
        lb_enabled=lb_enabled,
        lfm_enabled=lfm_enabled,
        primary_source=primary_source,
    )

    service = HomeService(
        listenbrainz_repo=lb_repo,
        jellyfin_repo=jf_repo,
        lidarr_repo=lidarr_repo,
        musicbrainz_repo=mb_repo,
        preferences_service=prefs,
        lastfm_repo=lfm_repo,
    )
    return service, lb_repo, lfm_repo, prefs


class TestHomeServiceResolveSource:
    def test_explicit_source_overrides_global(self):
        service, _, _, _ = _make_service(primary_source="lastfm")
        assert service._resolve_source("listenbrainz") == "listenbrainz"

    def test_none_uses_global_setting(self):
        service, _, _, _ = _make_service(primary_source="lastfm")
        assert service._resolve_source(None) == "lastfm"


class TestHomeServiceSourceSelection:
    @pytest.mark.asyncio
    async def test_lb_trending_called_when_source_is_lb(self):
        service, lb_repo, lfm_repo, _ = _make_service(
            lb_enabled=True, lfm_enabled=True, primary_source="listenbrainz"
        )
        await service.get_home_data("listenbrainz")
        lb_repo.get_sitewide_top_artists.assert_awaited_once()
        lfm_repo.get_global_top_artists.assert_not_awaited()

    @pytest.mark.asyncio
    async def test_lfm_trending_called_when_source_is_lastfm(self):
        service, lb_repo, lfm_repo, _ = _make_service(
            lb_enabled=True, lfm_enabled=True, primary_source="lastfm"
        )
        await service.get_home_data("lastfm")
        lfm_repo.get_global_top_artists.assert_awaited_once()
        lb_repo.get_sitewide_top_artists.assert_not_awaited()

    @pytest.mark.asyncio
    async def test_source_field_in_response(self):
        service, _, _, _ = _make_service(
            lb_enabled=True, lfm_enabled=True, primary_source="listenbrainz"
        )
        response = await service.get_home_data("listenbrainz")
        assert response.integration_status is not None
        assert response.integration_status.lastfm is True
        assert response.integration_status.listenbrainz is True

    @pytest.mark.asyncio
    async def test_popular_album_in_library_uses_album_mbids_not_artist_mbids(self):
        service, lb_repo, _, prefs = _make_service(
            lb_enabled=True, lfm_enabled=True, primary_source="listenbrainz"
        )
        lidarr_conn = MagicMock()
        lidarr_conn.lidarr_url = "http://lidarr.local"
        lidarr_conn.lidarr_api_key = "apikey"
        prefs.get_lidarr_connection.return_value = lidarr_conn
        service._lidarr_repo.get_library.return_value = [
            LibraryAlbum(
                artist="Artist",
                album="Album",
                monitored=True,
                musicbrainz_id="rg-123",
                artist_mbid="artist-123",
            )
        ]
        service._lidarr_repo.get_artists_from_library.return_value = [{"mbid": "artist-123"}]
        lb_repo.get_sitewide_top_release_groups.return_value = [
            ListenBrainzReleaseGroup(
                release_group_name="Album",
                artist_name="Artist",
                listen_count=99,
                release_group_mbid="rg-123",
                artist_mbids=["artist-other"],
            )
        ]

        response = await service.get_home_data("listenbrainz")

        assert response.popular_albums is not None
        assert len(response.popular_albums.items) == 1
        assert response.popular_albums.items[0].in_library is True

    @pytest.mark.asyncio
    async def test_lastfm_source_skips_user_top_albums_when_username_missing(self):
        service, _, lfm_repo, prefs = _make_service(
            lb_enabled=False, lfm_enabled=True, primary_source="lastfm"
        )
        prefs.get_lastfm_connection.return_value = LastFmConnectionSettings(
            api_key="key",
            shared_secret="secret",
            session_key="sk",
            username="",
            enabled=True,
        )

        await service.get_home_data("lastfm")

        lfm_repo.get_global_top_artists.assert_awaited_once()
        lfm_repo.get_user_top_albums.assert_not_awaited()

    @pytest.mark.asyncio
    async def test_listenbrainz_source_includes_weekly_exploration(self):
        service, lb_repo, _, _ = _make_service(
            lb_enabled=True, lfm_enabled=True, primary_source="listenbrainz"
        )
        lb_repo.get_recommendation_playlists.return_value = [
            {
                "playlist_id": "weekly-123",
                "source_patch": "weekly-exploration",
                "identifier": "https://listenbrainz.org/playlist/weekly-123",
            }
        ]
        lb_repo.get_playlist_tracks.return_value = MagicMock(
            title="Weekly Exploration for lbuser",
            date="2026-03-30T00:00:00+00:00",
            tracks=[
                MagicMock(
                    title="Song",
                    creator="Artist",
                    album="Album",
                    recording_mbid="recording-1",
                    artist_mbids=["artist-1"],
                    caa_release_mbid="release-1",
                    duration_ms=123000,
                )
            ],
        )
        service._mb_repo.get_release_group_id_from_release.return_value = "release-group-1"

        response = await service.get_home_data("listenbrainz")

        assert response.weekly_exploration is not None
        assert response.weekly_exploration.source_url == "https://listenbrainz.org/playlist/weekly-123"
        assert len(response.weekly_exploration.tracks) == 1
        assert response.weekly_exploration.tracks[0].release_group_mbid == "release-group-1"

    @pytest.mark.asyncio
    async def test_lastfm_source_skips_weekly_exploration(self):
        service, lb_repo, _, _ = _make_service(
            lb_enabled=True, lfm_enabled=True, primary_source="lastfm"
        )

        response = await service.get_home_data("lastfm")

        assert response.weekly_exploration is None
        lb_repo.get_recommendation_playlists.assert_not_awaited()


class TestHomeServiceCacheKeySourceAware:
    def test_different_sources_produce_different_keys(self):
        service, _, _, _ = _make_service()
        key_lb = service._get_home_cache_key("listenbrainz")
        key_lfm = service._get_home_cache_key("lastfm")
        assert key_lb != key_lfm


class TestBuildServicePrompts:
    def test_source_prompts_hidden_when_one_source_enabled(self):
        service, _, _, _ = _make_service()
        prompts = service._build_service_prompts(
            lb_enabled=True, lidarr_configured=True, lfm_enabled=False
        )
        services = [p.service for p in prompts]
        assert "lastfm" not in services
        assert "listenbrainz" not in services

    def test_source_prompts_hidden_when_lastfm_enabled(self):
        service, _, _, _ = _make_service()
        prompts = service._build_service_prompts(
            lb_enabled=False, lidarr_configured=True, lfm_enabled=True
        )
        services = [p.service for p in prompts]
        assert "listenbrainz" not in services
        assert "lastfm" not in services

    def test_no_prompts_when_all_enabled(self):
        service, _, _, _ = _make_service()
        prompts = service._build_service_prompts(
            lb_enabled=True, lidarr_configured=True, lfm_enabled=True
        )
        assert prompts == []

    def test_all_prompts_when_nothing_enabled(self):
        service, _, _, _ = _make_service()
        prompts = service._build_service_prompts(
            lb_enabled=False, lidarr_configured=False, lfm_enabled=False
        )
        services = {p.service for p in prompts}
        assert services == {"lidarr-connection", "listenbrainz", "lastfm"}

    def test_lb_prompt_mentions_lastfm(self):
        service, _, _, _ = _make_service()
        prompts = service._build_service_prompts(
            lb_enabled=False, lidarr_configured=True, lfm_enabled=False
        )
        lb_prompt = next(p for p in prompts if p.service == "listenbrainz")
        assert "last.fm" in lb_prompt.description.lower()
