import os
from http import HTTPStatus
from pathlib import Path
from typing import Any, Dict, Optional

import pytest
from flask.testing import Client

from beets.test.helper import TestHelper


@pytest.fixture(scope="session", autouse=True)
def helper():
    helper = TestHelper()
    helper.setup_beets()
    yield helper
    helper.teardown_beets()


@pytest.fixture(scope="session")
def app(helper):
    from beetsplug.aura import create_app

    app = create_app()
    app.config["lib"] = helper.lib
    return app


@pytest.fixture(scope="session")
def item(helper):
    return helper.add_item_fixture(
        album="Album",
        title="Title",
        artist="Artist",
        albumartist="Album Artist",
    )


@pytest.fixture(scope="session")
def album(helper, item):
    return helper.lib.add_album([item])


@pytest.fixture(scope="session", autouse=True)
def _other_album_and_item(helper):
    """Add another item and album to prove that filtering works."""
    item = helper.add_item_fixture(
        album="Other Album",
        title="Other Title",
        artist="Other Artist",
        albumartist="Other Album Artist",
    )
    helper.lib.add_album([item])


class TestAuraResponse:
    @pytest.fixture
    def get_response_data(self, client: Client, item):
        """Return a callback accepting `endpoint` and `params` parameters."""

        def get(
            endpoint: str, params: Dict[str, str]
        ) -> Optional[Dict[str, Any]]:
            """Add additional `params` and GET the given endpoint.

            `include` parameter is added to every call to check that the
            functionality that fetches related entities works.

            Before returning the response data, ensure that the request is
            successful.
            """
            response = client.get(
                endpoint,
                query_string={"include": "tracks,artists,albums", **params},
            )

            assert response.status_code == HTTPStatus.OK

            return response.json

        return get

    @pytest.fixture(scope="class")
    def track_document(self, item, album):
        return {
            "type": "track",
            "id": str(item.id),
            "attributes": {
                "album": item.album,
                "albumartist": item.albumartist,
                "artist": item.artist,
                "size": Path(os.fsdecode(item.path)).stat().st_size,
                "title": item.title,
                "track": 1,
            },
            "relationships": {
                "albums": {"data": [{"id": str(album.id), "type": "album"}]},
                "artists": {"data": [{"id": item.artist, "type": "artist"}]},
            },
        }

    @pytest.fixture(scope="class")
    def artist_document(self, item):
        return {
            "type": "artist",
            "id": item.artist,
            "attributes": {"name": item.artist},
            "relationships": {
                "tracks": {"data": [{"id": str(item.id), "type": "track"}]}
            },
        }

    @pytest.fixture(scope="class")
    def album_document(self, album):
        return {
            "type": "album",
            "id": str(album.id),
            "attributes": {"artist": album.albumartist, "title": album.album},
            "relationships": {
                "tracks": {"data": [{"id": str(album.id), "type": "track"}]}
            },
        }

    def test_tracks(
        self,
        get_response_data,
        item,
        album_document,
        artist_document,
        track_document,
    ):
        data = get_response_data("/aura/tracks", {"filter[title]": item.title})

        assert data == {
            "data": [track_document],
            "included": [artist_document, album_document],
        }

    def test_artists(
        self, get_response_data, item, artist_document, track_document
    ):
        data = get_response_data(
            "/aura/artists", {"filter[artist]": item.artist}
        )

        assert data == {"data": [artist_document], "included": [track_document]}

    def test_albums(
        self, get_response_data, album, album_document, track_document
    ):
        data = get_response_data("/aura/albums", {"filter[album]": album.album})

        assert data == {"data": [album_document], "included": [track_document]}
