from __future__ import annotations

import json
from copy import deepcopy
from typing import TYPE_CHECKING

import pytest

from beets.autotag.distance import Distance
from beets.autotag.hooks import AlbumInfo, AlbumMatch, TrackInfo
from beets.library import Item
from beets.test.helper import PluginMixin
from beetsplug.mbpseudo import (
    _STATUS_PSEUDO,
    MusicBrainzPseudoReleasePlugin,
    PseudoAlbumInfo,
)

if TYPE_CHECKING:
    import pathlib

    from beetsplug._typing import JSONDict
    from beetsplug._utils.musicbrainz import Release


@pytest.fixture(scope="module")
def rsrc_dir(pytestconfig: pytest.Config):
    return pytestconfig.rootpath / "test" / "rsrc" / "mbpseudo"


@pytest.fixture
def official_release(rsrc_dir: pathlib.Path) -> Release:
    info_json = (rsrc_dir / "official_release.json").read_text(encoding="utf-8")
    return json.loads(info_json)


@pytest.fixture
def pseudo_release(rsrc_dir: pathlib.Path) -> Release:
    info_json = (rsrc_dir / "pseudo_release.json").read_text(encoding="utf-8")
    return json.loads(info_json)


@pytest.fixture
def official_release_info() -> AlbumInfo:
    return AlbumInfo(
        tracks=[TrackInfo(title="百花繚乱")],
        album_id="official",
        album="百花繚乱",
    )


@pytest.fixture
def pseudo_release_info() -> AlbumInfo:
    return AlbumInfo(
        tracks=[TrackInfo(title="In Bloom")],
        album_id="pseudo",
        album="In Bloom",
    )


@pytest.mark.usefixtures("config")
class TestPseudoAlbumInfo:
    def test_album_id_always_from_pseudo(
        self, official_release_info: AlbumInfo, pseudo_release_info: AlbumInfo
    ):
        info = PseudoAlbumInfo(pseudo_release_info, official_release_info)
        info.use_official_as_ref()
        assert info.album_id == "pseudo"

    def test_get_attr_from_pseudo(
        self, official_release_info: AlbumInfo, pseudo_release_info: AlbumInfo
    ):
        info = PseudoAlbumInfo(pseudo_release_info, official_release_info)
        assert info.album == "In Bloom"

    def test_get_attr_from_official(
        self, official_release_info: AlbumInfo, pseudo_release_info: AlbumInfo
    ):
        info = PseudoAlbumInfo(pseudo_release_info, official_release_info)
        info.use_official_as_ref()
        assert info.album == info.get_official_release().album

    def test_determine_best_ref(
        self, official_release_info: AlbumInfo, pseudo_release_info: AlbumInfo
    ):
        info = PseudoAlbumInfo(
            pseudo_release_info, official_release_info, data_source="test"
        )
        item = Item(title="百花繚乱")

        assert info.determine_best_ref([item]) == "official"

        info.use_pseudo_as_ref()
        assert info.data_source == "test"


class TestMBPseudoMixin(PluginMixin):
    plugin = "mbpseudo"

    @pytest.fixture(autouse=True)
    def patch_get_release(self, monkeypatch, pseudo_release: JSONDict):
        monkeypatch.setattr(
            "beetsplug._utils.musicbrainz.MusicBrainzAPI.get_release",
            lambda _, album_id: deepcopy(
                {pseudo_release["id"]: pseudo_release}[album_id]
            ),
        )

    @pytest.fixture(scope="class")
    def plugin_config(self):
        return {"scripts": ["Latn", "Dummy"]}

    @pytest.fixture
    def mbpseudo_plugin(self, plugin_config) -> MusicBrainzPseudoReleasePlugin:
        self.config[self.plugin].set(plugin_config)
        return MusicBrainzPseudoReleasePlugin()


class TestMBPseudoPlugin(TestMBPseudoMixin):
    def test_scripts_init(
        self, mbpseudo_plugin: MusicBrainzPseudoReleasePlugin
    ):
        assert mbpseudo_plugin._scripts == ["Latn", "Dummy"]

    @pytest.mark.parametrize(
        "album_id",
        [
            "a5ce1d11-2e32-45a4-b37f-c1589d46b103",
            "-5ce1d11-2e32-45a4-b37f-c1589d46b103",
        ],
    )
    def test_extract_id_uses_music_brainz_pattern(
        self,
        mbpseudo_plugin: MusicBrainzPseudoReleasePlugin,
        album_id: str,
    ):
        if album_id.startswith("-"):
            assert mbpseudo_plugin._extract_id(album_id) is None
        else:
            assert mbpseudo_plugin._extract_id(album_id) == album_id

    def test_album_info_for_pseudo_release(
        self,
        mbpseudo_plugin: MusicBrainzPseudoReleasePlugin,
        pseudo_release: Release,
    ):
        album_info = mbpseudo_plugin.album_info(pseudo_release)
        assert not isinstance(album_info, PseudoAlbumInfo)
        assert album_info.data_source == "MusicBrainzPseudoRelease"
        assert album_info.albumstatus == _STATUS_PSEUDO

    @pytest.mark.parametrize(
        "json_key",
        [
            "type",
            "direction",
            "release",
        ],
    )
    def test_interception_skip_when_rel_values_dont_match(
        self,
        mbpseudo_plugin: MusicBrainzPseudoReleasePlugin,
        official_release: Release,
        json_key: str,
    ):
        del official_release["release_relations"][0][json_key]  # type: ignore[misc]

        album_info = mbpseudo_plugin.album_info(official_release)
        assert not isinstance(album_info, PseudoAlbumInfo)
        assert album_info.data_source == "MusicBrainzPseudoRelease"

    def test_interception_skip_when_script_doesnt_match(
        self,
        mbpseudo_plugin: MusicBrainzPseudoReleasePlugin,
        official_release: Release,
    ):
        official_release["release_relations"][0]["release"][
            "text_representation"
        ]["script"] = "Null"

        album_info = mbpseudo_plugin.album_info(official_release)
        assert not isinstance(album_info, PseudoAlbumInfo)
        assert album_info.data_source == "MusicBrainzPseudoRelease"

    def test_interception(
        self,
        mbpseudo_plugin: MusicBrainzPseudoReleasePlugin,
        official_release: Release,
    ):
        album_info = mbpseudo_plugin.album_info(official_release)
        assert isinstance(album_info, PseudoAlbumInfo)
        assert album_info.data_source == "MusicBrainzPseudoRelease"

    def test_final_adjustment_skip(
        self,
        mbpseudo_plugin: MusicBrainzPseudoReleasePlugin,
    ):
        match = AlbumMatch(
            distance=Distance(),
            info=AlbumInfo(tracks=[], data_source="mb"),
            mapping={},
            extra_items=[],
            extra_tracks=[],
        )

        mbpseudo_plugin._adjust_final_album_match(match)
        assert match.info.data_source == "mb"

    def test_final_adjustment(
        self,
        mbpseudo_plugin: MusicBrainzPseudoReleasePlugin,
        official_release_info: AlbumInfo,
        pseudo_release_info: AlbumInfo,
    ):
        pseudo_album_info = PseudoAlbumInfo(
            pseudo_release=pseudo_release_info,
            official_release=official_release_info,
            data_source=mbpseudo_plugin.data_source,
        )
        pseudo_album_info.use_official_as_ref()

        item = Item()
        item["title"] = "百花繚乱"

        match = AlbumMatch(
            distance=Distance(),
            info=pseudo_album_info,
            mapping={item: pseudo_album_info.tracks[0]},
            extra_items=[],
            extra_tracks=[],
        )

        mbpseudo_plugin._adjust_final_album_match(match)

        assert match.info.data_source == "MusicBrainz"
        assert match.info.album_id == "pseudo"
        assert match.info.album == "In Bloom"


class TestMBPseudoPluginCustomTagsOnly(TestMBPseudoMixin):
    @pytest.fixture(scope="class")
    def plugin_config(self):
        return {"scripts": ["Latn", "Dummy"], "custom_tags_only": True}

    def test_custom_tags(
        self,
        config,
        mbpseudo_plugin: MusicBrainzPseudoReleasePlugin,
        official_release: Release,
    ):
        config["import"]["languages"] = ["en", "jp"]
        album_info = mbpseudo_plugin.album_info(official_release)
        assert not isinstance(album_info, PseudoAlbumInfo)
        assert album_info.data_source == "MusicBrainzPseudoRelease"
        assert album_info["album_transl"] == "In Bloom"
        assert album_info["album_artist_transl"] == "Lilas Ikuta"
        assert album_info.tracks[0]["title_transl"] == "In Bloom"
        assert album_info.tracks[0]["artist_transl"] == "Lilas Ikuta"

    def test_custom_tags_with_import_languages(
        self,
        config,
        mbpseudo_plugin: MusicBrainzPseudoReleasePlugin,
        official_release: Release,
    ):
        config["import"]["languages"] = []
        album_info = mbpseudo_plugin.album_info(official_release)
        assert not isinstance(album_info, PseudoAlbumInfo)
        assert album_info.data_source == "MusicBrainzPseudoRelease"
        assert album_info["album_transl"] == "In Bloom"
        assert album_info["album_artist_transl"] == "Lilas Ikuta"
        assert album_info.tracks[0]["title_transl"] == "In Bloom"
        assert album_info.tracks[0]["artist_transl"] == "Lilas Ikuta"
