from dataclasses import asdict
from datetime import timedelta
from pathlib import Path
from typing import Any, Generator, Tuple

import pytest

from sublime.adapters import CacheMissError
from sublime.adapters.filesystem import FilesystemAdapter
from sublime.adapters.subsonic import api_objects as SubsonicAPI

MOCK_DATA_FILES = Path(__file__).parent.joinpath("mock_data")


@pytest.fixture
def adapter(tmp_path: Path):
    adapter = FilesystemAdapter({}, tmp_path)
    yield adapter
    adapter.shutdown()


@pytest.fixture
def cache_adapter(tmp_path: Path):
    adapter = FilesystemAdapter({}, tmp_path, is_cache=True)
    yield adapter
    adapter.shutdown()


def mock_data_files(
    request_name: str, mode: str = "r",
) -> Generator[Tuple[Path, Any], None, None]:
    """
    Yields all of the files in the mock_data directory that start with ``request_name``.
    """
    for file in MOCK_DATA_FILES.iterdir():
        if file.name.split("-")[0] in request_name:
            with open(file, mode) as f:
                yield file, f.read()


def test_caching_get_playlists(cache_adapter: FilesystemAdapter):
    with pytest.raises(CacheMissError):
        cache_adapter.get_playlists()

    # Ingest an empty list (for example, no playlists added yet to server).
    cache_adapter.ingest_new_data(FilesystemAdapter.FunctionNames.GET_PLAYLISTS, (), [])

    # After the first cache miss of get_playlists, even if an empty list is
    # returned, the next one should not be a cache miss.
    cache_adapter.get_playlists()

    # Ingest two playlists.
    cache_adapter.ingest_new_data(
        FilesystemAdapter.FunctionNames.GET_PLAYLISTS,
        (),
        [
            SubsonicAPI.Playlist("1", "test1", comment="comment"),
            SubsonicAPI.Playlist("2", "test2"),
        ],
    )

    playlists = cache_adapter.get_playlists()
    assert len(playlists) == 2
    assert (playlists[0].id, playlists[0].name, playlists[0].comment) == (
        "1",
        "test1",
        "comment",
    )
    assert (playlists[1].id, playlists[1].name) == ("2", "test2")


def test_no_caching_get_playlists(adapter: FilesystemAdapter):
    adapter.get_playlists()

    # TODO: Create a playlist (that should be allowed only if this is acting as
    # a ground truth adapter)
    # cache_adapter.create_playlist()

    adapter.get_playlists()
    # TODO: verify playlist


def test_caching_get_playlist_details(cache_adapter: FilesystemAdapter):
    with pytest.raises(CacheMissError):
        cache_adapter.get_playlist_details("1")

    # Simulate the playlist being retrieved from Subsonic.
    songs = [
        SubsonicAPI.Song(
            "2",
            "Song 2",
            parent="foo",
            album="foo",
            artist="foo",
            duration=timedelta(seconds=20.8),
            path="/foo/song2.mp3",
        ),
        SubsonicAPI.Song(
            "1",
            "Song 1",
            parent="foo",
            album="foo",
            artist="foo",
            duration=timedelta(seconds=10.2),
            path="/foo/song1.mp3",
        ),
    ]
    cache_adapter.ingest_new_data(
        FilesystemAdapter.FunctionNames.GET_PLAYLIST_DETAILS,
        ("1",),
        SubsonicAPI.PlaylistWithSongs("1", "test1", songs=songs),
    )

    playlist = cache_adapter.get_playlist_details("1")
    assert playlist.id == "1"
    assert playlist.name == "test1"
    assert playlist.song_count == 2
    assert playlist.duration == timedelta(seconds=31)
    for actual, song in zip(playlist.songs, songs):
        for k, v in asdict(song).items():
            assert getattr(actual, k, None) == v

    # "Force refresh" the playlist
    songs = [
        SubsonicAPI.Song(
            "3",
            "Song 3",
            parent="foo",
            album="foo",
            artist="foo",
            duration=timedelta(seconds=10.2),
            path="/foo/song3.mp3",
        ),
        SubsonicAPI.Song(
            "1",
            "Song 1",
            parent="foo",
            album="foo",
            artist="foo",
            duration=timedelta(seconds=21.8),
            path="/foo/song1.mp3",
        ),
        SubsonicAPI.Song(
            "1",
            "Song 1",
            parent="foo",
            album="foo",
            artist="foo",
            duration=timedelta(seconds=21.8),
            path="/foo/song1.mp3",
        ),
    ]
    cache_adapter.ingest_new_data(
        FilesystemAdapter.FunctionNames.GET_PLAYLIST_DETAILS,
        ("1",),
        SubsonicAPI.PlaylistWithSongs("1", "foo", songs=songs),
    )

    playlist = cache_adapter.get_playlist_details("1")
    assert playlist.id == "1"
    assert playlist.name == "foo"
    assert playlist.song_count == 3
    assert playlist.duration == timedelta(seconds=53.8)
    for actual, song in zip(playlist.songs, songs):
        for k, v in asdict(song).items():
            assert getattr(actual, k, None) == v

    with pytest.raises(CacheMissError):
        cache_adapter.get_playlist_details("2")


def test_no_caching_get_playlist_details(adapter: FilesystemAdapter):
    with pytest.raises(Exception):
        adapter.get_playlist_details("1")

    # TODO: Create a playlist (that should be allowed only if this is acting as
    # a ground truth adapter)
    # cache_adapter.create_playlist()

    # adapter.get_playlist_details('1')
    # TODO: verify playlist details


def test_caching_get_playlist_then_details(cache_adapter: FilesystemAdapter):
    # Ingest a list of playlists (like the sidebar, without songs)
    cache_adapter.ingest_new_data(
        FilesystemAdapter.FunctionNames.GET_PLAYLISTS,
        (),
        [SubsonicAPI.Playlist("1", "test1"), SubsonicAPI.Playlist("2", "test2")],
    )

    # Trying to get playlist details should generate a cache miss, but should
    # include the data that we know about.
    try:
        cache_adapter.get_playlist_details("1")
        assert 0, "DID NOT raise CacheMissError"
    except CacheMissError as e:
        assert e.partial_data
        assert e.partial_data.id == "1"
        assert e.partial_data.name == "test1"

    # Simulate getting playlist details for id=1, then id=2
    songs = [
        SubsonicAPI.Song(
            "3",
            "Song 3",
            parent="foo",
            album="foo",
            artist="foo",
            duration=timedelta(seconds=10.2),
            path="/foo/song3.mp3",
        ),
    ]
    cache_adapter.ingest_new_data(
        FilesystemAdapter.FunctionNames.GET_PLAYLIST_DETAILS,
        ("1",),
        SubsonicAPI.PlaylistWithSongs("1", "test1", songs=songs),
    )

    cache_adapter.ingest_new_data(
        FilesystemAdapter.FunctionNames.GET_PLAYLIST_DETAILS,
        ("2",),
        SubsonicAPI.PlaylistWithSongs("2", "test2", songs=songs),
    )

    # Going back and getting playlist details for the first one should not
    # cache miss.
    playlist = cache_adapter.get_playlist_details("1")
    assert playlist.id == "1"
    assert playlist.name == "test1"
