from marshmallow import EXCLUDE, fields, post_dump, post_load, pre_dump, pre_load
from marshmallow.schema import Schema

from platypush.plugins.media import PlayerState
from platypush.schemas import DateTime


class MopidyTrackSchema(Schema):
    """
    Mopidy track schema.
    """

    uri = fields.String(required=True, metadata={"description": "Track URI"})
    file = fields.String(
        metadata={"description": "Track URI, for MPD compatibility purposes"}
    )
    artist = fields.String(load_default=None, metadata={"description": "Artist name"})
    title = fields.String(load_default=None, metadata={"description": "Track title"})
    album = fields.String(load_default=None, metadata={"description": "Album name"})
    artist_uri = fields.String(
        load_default=None, metadata={"description": "Artist URI (if available)"}
    )
    album_uri = fields.String(
        load_default=None, metadata={"description": "Album URI (if available)"}
    )
    time = fields.Float(
        load_default=None, metadata={"description": "Track length (in seconds)"}
    )
    playlist_pos = fields.Integer(
        load_default=None,
        metadata={"description": "Track position in the tracklist/playlist"},
    )
    track_id = fields.Integer(
        load_default=None, metadata={"description": "Track ID in the current tracklist"}
    )
    track_no = fields.Integer(
        load_default=None, metadata={"description": "Track number in the album"}
    )
    date = fields.String(
        load_default=None, metadata={"description": "Track release date"}
    )
    genre = fields.String(load_default=None, metadata={"description": "Track genre"})
    type = fields.Constant("track", metadata={"description": "Item type"})

    @pre_load
    def parse(self, track: dict, **_):
        from platypush.plugins.music.mopidy import EmptyTrackException

        uri = (track or {}).get("uri", (track or {}).get("track", {}).get("uri"))
        if not uri:
            raise EmptyTrackException("Empty track")

        tlid = track.get("tlid")
        playlist_pos = track.get("playlist_pos")
        if track.get("track"):
            track = track.get("track", {})

        length = track.get("length", track.get("time", track.get("duration")))
        return {
            "uri": uri,
            "artist": next(
                iter(item.get("name") for item in track.get("artists", [])),
                None,
            ),
            "title": track.get("name"),
            "album": track.get("album", {}).get("name"),
            "artist_uri": next(
                iter(item.get("uri") for item in track.get("artists", [])), None
            ),
            "album_uri": track.get("album", {}).get("uri"),
            "time": length / 1000 if length is not None else None,
            "playlist_pos": (
                track.get("playlist_pos") if playlist_pos is None else playlist_pos
            ),
            "date": track.get("date", track.get("album", {}).get("date")),
            "track_id": tlid,
            "track_no": track.get("track_no"),
            "genre": track.get("genre"),
        }

    @post_dump
    def to_dict(self, track: dict, **_):
        """
        Fill/move missing fields in the dictionary.
        """
        return {
            "file": track["uri"],
            **track,
        }


class MopidyStatusSchema(Schema):
    """
    Mopidy status schema.
    """

    state = fields.Enum(
        PlayerState,
        required=True,
        metadata={"description": "Player state"},
    )
    volume = fields.Float(metadata={"description": "Player volume (0-100)"})
    consume = fields.Boolean(metadata={"description": "Consume mode"})
    random = fields.Boolean(metadata={"description": "Random mode"})
    repeat = fields.Boolean(metadata={"description": "Repeat mode"})
    single = fields.Boolean(metadata={"description": "Single mode"})
    mute = fields.Boolean(metadata={"description": "Mute mode"})
    time = fields.Float(metadata={"description": "Current time (in seconds)"})
    playing_pos = fields.Integer(
        metadata={"description": "Index of the currently playing track"}
    )
    track = fields.Nested(
        MopidyTrackSchema, load_default=None, metadata={"description": "Current track"}
    )

    @post_dump
    def post_dump(self, data: dict, **_):
        """
        Post-dump hook.
        """
        state = data.get("state")
        if state:
            data["state"] = getattr(PlayerState, state).value
        return data


class MopidyPlaylistSchema(Schema):
    """
    Mopidy playlist schema.
    """

    # pylint: disable=too-few-public-methods
    class Meta:  # type: ignore
        """
        Mopidy playlist schema metadata.
        """

        unknown = EXCLUDE

    uri = fields.String(required=True, metadata={"description": "Playlist URI"})
    name = fields.String(required=True, metadata={"description": "Playlist name"})
    last_modified = DateTime(metadata={"description": "Last modified timestamp"})
    tracks = fields.List(
        fields.Nested(MopidyTrackSchema),
        load_default=None,
        metadata={"description": "Playlist tracks"},
    )
    type = fields.Constant("playlist", metadata={"description": "Item type"})

    @pre_dump
    def pre_dump(self, playlist, **_):
        """
        Pre-dump hook.
        """
        last_modified = (
            playlist.last_modified
            if hasattr(playlist, "last_modified")
            else playlist.get("last_modified")
        )

        if last_modified:
            last_modified /= 1000
        if hasattr(playlist, "last_modified"):
            playlist.last_modified = last_modified
        else:
            playlist["last_modified"] = last_modified

        return playlist


class MopidyArtistSchema(Schema):
    """
    Mopidy artist schema.
    """

    uri = fields.String(required=True, metadata={"description": "Artist URI"})
    file = fields.String(
        metadata={"description": "Artist URI, for MPD compatibility purposes"}
    )
    name = fields.String(load_default=None, metadata={"description": "Artist name"})
    artist = fields.String(
        load_default=None,
        metadata={"description": "Same as name - for MPD compatibility purposes"},
    )
    type = fields.Constant("artist", metadata={"description": "Item type"})

    @post_dump
    def to_dict(self, artist: dict, **_):
        """
        Fill/move missing fields in the dictionary.
        """
        return {
            "file": artist["uri"],
            "artist": artist["name"],
            **artist,
        }


class MopidyAlbumSchema(Schema):
    """
    Mopidy album schema.
    """

    uri = fields.String(required=True, metadata={"description": "Album URI"})
    file = fields.String(
        metadata={"description": "Artist URI, for MPD compatibility purposes"}
    )
    artist = fields.String(load_default=None, metadata={"description": "Artist name"})
    album = fields.String(
        load_default=None,
        metadata={"description": "Same as name - for MPD compatibility purposes"},
    )
    name = fields.String(load_default=None, metadata={"description": "Album name"})
    artist_uri = fields.String(
        load_default=None, metadata={"description": "Artist URI"}
    )
    date = fields.String(
        load_default=None, metadata={"description": "Album release date"}
    )
    genre = fields.String(load_default=None, metadata={"description": "Album genre"})

    def parse(self, data: dict, **_):
        if not (data.get("uri")):
            raise AssertionError("Album URI is required")
        return {
            "uri": data["uri"],
            "artist": data.get("artist")
            or next(
                iter(item.get("name") for item in data.get("artists", [])),
                None,
            ),
            "name": data.get("name"),
            "artist_uri": data.get("artist_uri")
            or next(iter(item.get("uri") for item in data.get("artists", [])), None),
            "album_uri": data.get("album_uri") or data.get("album", {}).get("uri"),
            "date": data.get("date", data.get("album", {}).get("date")),
            "genre": data.get("genre"),
        }

    @pre_load
    def pre_load(self, album: dict, **_):
        """
        Pre-load hook.
        """
        return self.parse(album)

    @pre_dump
    def pre_dump(self, album: dict, **_):
        """
        Pre-dump hook.
        """
        return self.parse(album)

    @post_dump
    def to_dict(self, album: dict, **_):
        """
        Fill/move missing fields in the dictionary.
        """
        return {
            "file": album["uri"],
            "album": album["name"],
            **album,
        }


class MopidyDirectorySchema(Schema):
    """
    Mopidy directory schema.
    """

    uri = fields.String(required=True, metadata={"description": "Directory URI"})
    name = fields.String(required=True, metadata={"description": "Directory name"})
    type = fields.Constant("directory", metadata={"description": "Item type"})


class MopidyFilterSchema(Schema):
    """
    Mopidy filter schema.
    """

    uris = fields.List(fields.String, metadata={"description": "Filter by URIs"})
    artist = fields.List(fields.String, metadata={"description": "Artist name(s)"})
    album = fields.List(fields.String, metadata={"description": "Album name(s)"})
    title = fields.List(fields.String, metadata={"description": "Track title(s)"})
    albumartist = fields.List(
        fields.String, metadata={"description": "Album artist name(s)"}
    )
    date = fields.List(fields.String, metadata={"description": "Track release date(s)"})
    genre = fields.List(fields.String, metadata={"description": "Genre(s)"})
    comment = fields.List(fields.String, metadata={"description": "Comment(s)"})
    disc_no = fields.List(fields.Integer, metadata={"description": "Disc number(s)"})
    musicbrainz_artistid = fields.List(
        fields.String, metadata={"description": "MusicBrainz artist ID(s)"}
    )
    musicbrainz_albumid = fields.List(
        fields.String, metadata={"description": "MusicBrainz album ID(s)"}
    )
    musicbrainz_trackid = fields.List(
        fields.String, metadata={"description": "MusicBrainz album artist ID(s)"}
    )
    any = fields.List(
        fields.String, metadata={"description": "Generic search string(s)"}
    )

    @pre_load
    def pre_load(self, data: dict, **_):
        """
        Pre-load hook.
        """
        for field_name, field in self.fields.items():
            value = data.get(field_name)

            # Back-compatibtility with MPD's single-value filters
            if (
                value is not None
                and isinstance(field, fields.List)
                and isinstance(value, str)
            ):
                data[field_name] = [value]

        return data

    @post_load
    def post_load(self, data: dict, **_):
        """
        Post-load hook.
        """
        title = data.pop("title", None)
        if title:
            data["track_name"] = title

        return data
