from unittest.mock import AsyncMock, MagicMock

import pytest

from api.v1.schemas.album import AlbumInfo
from services.album_service import AlbumService


def _make_service() -> tuple[AlbumService, MagicMock, MagicMock]:
    lidarr_repo = MagicMock()
    mb_repo = MagicMock()
    library_db = MagicMock()
    memory_cache = MagicMock()
    disk_cache = MagicMock()
    preferences_service = MagicMock()
    audiodb_image_service = MagicMock()

    service = AlbumService(
        lidarr_repo=lidarr_repo,
        mb_repo=mb_repo,
        library_db=library_db,
        memory_cache=memory_cache,
        disk_cache=disk_cache,
        preferences_service=preferences_service,
        audiodb_image_service=audiodb_image_service,
    )
    return service, lidarr_repo, library_db


@pytest.mark.asyncio
async def test_revalidate_library_status_keeps_value_when_lidarr_details_unavailable():
    service, lidarr_repo, _ = _make_service()
    lidarr_repo.get_album_details = AsyncMock(return_value=None)
    lidarr_repo.get_library_mbids = AsyncMock(return_value={"should-not-be-used"})
    service._save_album_to_cache = AsyncMock()

    album_info = AlbumInfo(
        title="Test",
        musicbrainz_id="4549a80c-efe6-4386-b3a2-4b4a918eb31f",
        artist_name="Artist",
        artist_id="artist-id",
        in_library=True,
    )

    result = await service._revalidate_library_status(album_info.musicbrainz_id, album_info)

    assert result.in_library is True
    service._save_album_to_cache.assert_not_called()
    lidarr_repo.get_library_mbids.assert_not_called()


@pytest.mark.asyncio
async def test_revalidate_library_status_uses_lidarr_details_and_updates_cache_on_change():
    service, lidarr_repo, _ = _make_service()
    lidarr_repo.get_album_details = AsyncMock(
        return_value={"monitored": False, "statistics": {"trackFileCount": 0}}
    )
    service._save_album_to_cache = AsyncMock()

    album_info = AlbumInfo(
        title="Test",
        musicbrainz_id="8e1e9e51-38dc-4df3-8027-a0ada37d4674",
        artist_name="Artist",
        artist_id="artist-id",
        in_library=True,
    )

    result = await service._revalidate_library_status(album_info.musicbrainz_id, album_info)

    assert result.in_library is False
    service._save_album_to_cache.assert_awaited_once()


@pytest.mark.asyncio
async def test_get_album_basic_info_does_not_use_library_cache_when_lidarr_payload_exists():
    service, lidarr_repo, library_db = _make_service()
    service._get_cached_album_info = AsyncMock(return_value=None)
    service._fetch_release_group = AsyncMock(
        return_value={
            "title": "Album",
            "first-release-date": "2024-01-01",
            "primary-type": "Album",
            "disambiguation": "",
            "artist-credit": [],
        }
    )

    lidarr_repo.get_requested_mbids = AsyncMock(return_value=set())
    lidarr_repo.get_album_details = AsyncMock(
        return_value={"monitored": False, "statistics": {"trackFileCount": 20}}
    )
    library_db.get_album_by_mbid = AsyncMock(return_value={"mbid": "from-cache"})

    result = await service.get_album_basic_info("8e1e9e51-38dc-4df3-8027-a0ada37d4674")

    assert result.in_library is False
    library_db.get_album_by_mbid.assert_not_awaited()


@pytest.mark.asyncio
async def test_get_album_tracks_info_preserves_disc_numbers_from_lidarr():
    service, lidarr_repo, _ = _make_service()
    service._get_cached_album_info = AsyncMock(return_value=None)
    lidarr_repo.is_configured.return_value = True
    lidarr_repo.get_album_details = AsyncMock(return_value={"id": 42, "monitored": True})
    lidarr_repo.get_album_tracks = AsyncMock(
        return_value=[
            {
                "track_number": 1,
                "disc_number": 1,
                "title": "Disc One",
                "duration_ms": 1000,
            },
            {
                "track_number": 1,
                "disc_number": 2,
                "title": "Disc Two",
                "duration_ms": 2000,
            },
        ]
    )

    result = await service.get_album_tracks_info("8e1e9e51-38dc-4df3-8027-a0ada37d4674")

    assert [(track.disc_number, track.position, track.title) for track in result.tracks] == [
        (1, 1, "Disc One"),
        (2, 1, "Disc Two"),
    ]
    assert result.total_length == 3000


@pytest.mark.asyncio
async def test_get_album_tracks_info_multi_disc_same_track_numbers():
    """Verify tracks with same track_number but different disc_number are kept distinct."""
    service, lidarr_repo, _ = _make_service()
    service._get_cached_album_info = AsyncMock(return_value=None)
    lidarr_repo.is_configured.return_value = True
    lidarr_repo.get_album_details = AsyncMock(return_value={"id": 42, "monitored": True})
    lidarr_repo.get_album_tracks = AsyncMock(
        return_value=[
            {"track_number": 1, "disc_number": 1, "title": "Intro", "duration_ms": 1000},
            {"track_number": 2, "disc_number": 1, "title": "Main", "duration_ms": 2000},
            {"track_number": 1, "disc_number": 2, "title": "Intro II", "duration_ms": 1500},
            {"track_number": 2, "disc_number": 2, "title": "Finale", "duration_ms": 3000},
        ]
    )

    result = await service.get_album_tracks_info("8e1e9e51-38dc-4df3-8027-a0ada37d4674")

    assert len(result.tracks) == 4
    disc_track_pairs = [(track.disc_number, track.position, track.title) for track in result.tracks]
    assert (1, 1, "Intro") in disc_track_pairs
    assert (1, 2, "Main") in disc_track_pairs
    assert (2, 1, "Intro II") in disc_track_pairs
    assert (2, 2, "Finale") in disc_track_pairs
    assert result.total_length == 7500
