import re
from datetime import datetime

from deezer.utils import map_track, map_album
from deezer.errors import APIError, GWAPIError
from deemix.errors import NoDataToParse, AlbumDoesntExists

from deemix.utils import (
    removeFeatures,
    andCommaConcat,
    removeDuplicateArtists,
    generateReplayGainString,
    changeCase,
)

from deemix.types.Album import Album
from deemix.types.Artist import Artist
from deemix.types.Date import Date
from deemix.types.Picture import Picture
from deemix.types.Playlist import Playlist
from deemix.types.Lyrics import Lyrics
from deemix.types import VARIOUS_ARTISTS

from deemix.settings import FeaturesOption


class Track:
    def __init__(self, sng_id="0", name=""):
        self.id = sng_id
        self.title = name
        self.MD5 = ""
        self.mediaVersion = ""
        self.trackToken = ""
        self.trackTokenExpiration = 0
        self.duration = 0
        self.fallbackID = "0"
        self.albumsFallback = []
        self.filesizes = {}
        self.local = False
        self.mainArtist = None
        self.artist = {"Main": []}
        self.artists = []
        self.album = None
        self.trackNumber = "0"
        self.discNumber = "0"
        self.date = Date()
        self.lyrics = None
        self.bpm = 0
        self.contributors = {}
        self.copyright = ""
        self.explicit = False
        self.ISRC = ""
        self.replayGain = ""
        self.rank = 0
        self.playlist = None
        self.position = None
        self.searched = False
        self.selectedFormat = 0
        self.singleDownload = False
        self.dateString = ""
        self.artistsString = ""
        self.mainArtistsString = ""
        self.featArtistsString = ""
        self.urls = {}

    def parseEssentialData(self, trackAPI):
        self.id = str(trackAPI["id"])
        self.duration = trackAPI["duration"]
        self.trackToken = trackAPI["track_token"]
        self.trackTokenExpiration = trackAPI["track_token_expire"]
        self.MD5 = trackAPI.get("md5_origin")
        self.mediaVersion = trackAPI["media_version"]
        self.filesizes = trackAPI["filesizes"]
        self.fallbackID = "0"
        if "fallback_id" in trackAPI:
            self.fallbackID = trackAPI["fallback_id"]
        self.local = int(self.id) < 0
        self.urls = {}

    def parseData(
        self, dz, track_id=None, trackAPI=None, albumAPI=None, playlistAPI=None
    ):
        if track_id and (not trackAPI or trackAPI and not trackAPI.get("track_token")):
            trackAPI_new = dz.gw.get_track_with_fallback(track_id)
            trackAPI_new = map_track(trackAPI_new)
            if not trackAPI:
                trackAPI = {}
            trackAPI_new.update(trackAPI)
            trackAPI = trackAPI_new
        elif not trackAPI:
            raise NoDataToParse

        self.parseEssentialData(trackAPI)

        # only public api has bpm
        if not trackAPI.get("bpm") and not self.local:
            try:
                trackAPI_new = dz.api.get_track(trackAPI["id"])
                trackAPI_new["release_date"] = trackAPI["release_date"]
                trackAPI.update(trackAPI_new)
            except APIError:
                pass

        if self.local:
            self.parseLocalTrackData(trackAPI)
        else:
            self.parseTrack(trackAPI)

            # Get Lyrics data
            if not trackAPI.get("lyrics") and self.lyrics.id != "0":
                try:
                    trackAPI["lyrics"] = dz.gw.get_track_lyrics(self.id)
                except GWAPIError:
                    self.lyrics.id = "0"
            if self.lyrics.id != "0":
                self.lyrics.parseLyrics(trackAPI["lyrics"])

            # Parse Album Data
            self.album = Album(
                alb_id=trackAPI["album"]["id"],
                title=trackAPI["album"]["title"],
                pic_md5=trackAPI["album"].get("md5_origin"),
            )

            # Get album Data
            if not albumAPI:
                try:
                    albumAPI = dz.api.get_album(self.album.id)
                except APIError:
                    albumAPI = None

            # Get album_gw Data
            # Only gw has disk number
            if not albumAPI or albumAPI and not albumAPI.get("nb_disk"):
                try:
                    albumAPI_gw = dz.gw.get_album(self.album.id)
                    albumAPI_gw = map_album(albumAPI_gw)
                except GWAPIError:
                    albumAPI_gw = {}
                if not albumAPI:
                    albumAPI = {}
                albumAPI_gw.update(albumAPI)
                albumAPI = albumAPI_gw

            if not albumAPI:
                raise AlbumDoesntExists

            self.album.parseAlbum(albumAPI)
            # albumAPI_gw doesn't contain the artist cover
            # Getting artist image ID
            # ex: https://e-cdns-images.dzcdn.net/images/artist/f2bc007e9133c946ac3c3907ddc5d2ea/56x56-000000-80-0-0.jpg
            if not self.album.mainArtist.pic.md5 or self.album.mainArtist.pic.md5 == "":
                artistAPI = dz.api.get_artist(self.album.mainArtist.id)
                self.album.mainArtist.pic.md5 = artistAPI["picture_small"][
                    artistAPI["picture_small"].find("artist/") + 7 : -24
                ]

            # Fill missing data
            if self.album.date and not self.date:
                self.date = self.album.date
            if "genres" in trackAPI:
                for genre in trackAPI["genres"]:
                    if genre not in self.album.genre:
                        self.album.genre.append(genre)

        # Remove unwanted charaters in track name
        # Example: track/127793
        self.title = " ".join(self.title.split())

        # Make sure there is at least one artist
        if len(self.artist["Main"]) == 0:
            self.artist["Main"] = [self.mainArtist.name]

        self.position = trackAPI.get("position")

        # Add playlist data if track is in a playlist
        if playlistAPI:
            self.playlist = Playlist(playlistAPI)

        self.generateMainFeatStrings()
        return self

    def parseLocalTrackData(self, trackAPI):
        # Local tracks has only the trackAPI_gw page and
        # contains only the tags provided by the file
        self.title = trackAPI["title"]
        self.album = Album(title=trackAPI["album"]["title"])
        self.album.pic = Picture(md5=trackAPI.get("md5_image", ""), pic_type="cover")
        self.mainArtist = Artist(name=trackAPI["artist"]["name"], role="Main")
        self.artists = [trackAPI["artist"]["name"]]
        self.artist = {"Main": [trackAPI["artist"]["name"]]}
        self.album.artist = self.artist
        self.album.artists = self.artists
        self.album.date = self.date
        self.album.mainArtist = self.mainArtist

    def parseTrack(self, trackAPI):
        self.title = trackAPI["title"]

        self.discNumber = trackAPI.get("disk_number")
        self.explicit = trackAPI.get("explicit_lyrics", False)
        self.copyright = trackAPI.get("copyright")
        if "gain" in trackAPI:
            self.replayGain = generateReplayGainString(trackAPI["gain"])
        self.ISRC = trackAPI.get("isrc")
        self.trackNumber = trackAPI["track_position"]
        self.contributors = trackAPI.get("song_contributors")
        self.rank = trackAPI["rank"]
        self.bpm = trackAPI["bpm"]

        self.lyrics = Lyrics(trackAPI.get("lyrics_id", "0"))

        self.mainArtist = Artist(
            art_id=trackAPI["artist"]["id"],
            name=trackAPI["artist"]["name"],
            role="Main",
            pic_md5=trackAPI["artist"].get("md5_image"),
        )

        if trackAPI.get("physical_release_date"):
            self.date.day = trackAPI["physical_release_date"][8:10]
            self.date.month = trackAPI["physical_release_date"][5:7]
            self.date.year = trackAPI["physical_release_date"][0:4]
            self.date.fixDayMonth()

        for artist in trackAPI.get("contributors", []):
            isVariousArtists = str(artist["id"]) == VARIOUS_ARTISTS
            isMainArtist = artist["role"] == "Main"

            if len(trackAPI["contributors"]) > 1 and isVariousArtists:
                continue

            if artist["name"] not in self.artists:
                self.artists.append(artist["name"])

            if (
                isMainArtist
                or artist["name"] not in self.artist["Main"]
                and not isMainArtist
            ):
                if not artist["role"] in self.artist:
                    self.artist[artist["role"]] = []
                self.artist[artist["role"]].append(artist["name"])

        if trackAPI.get("alternative_albums"):
            for album in trackAPI["alternative_albums"]["data"]:
                if (
                    "RIGHTS" in album
                    and album["RIGHTS"].get("STREAM_ADS_AVAILABLE")
                    or album["RIGHTS"].get("STREAM_SUB_AVAILABLE")
                ):
                    self.albumsFallback.append(album["ALB_ID"])

    def removeDuplicateArtists(self):
        (self.artist, self.artists) = removeDuplicateArtists(self.artist, self.artists)

    # Removes featuring from the title
    def getCleanTitle(self):
        return removeFeatures(self.title)

    def getFeatTitle(self):
        if self.featArtistsString and "feat." not in self.title.lower():
            return f"{self.title} ({self.featArtistsString})"
        return self.title

    def generateMainFeatStrings(self):
        self.mainArtistsString = andCommaConcat(self.artist["Main"])
        self.featArtistsString = ""
        if "Featured" in self.artist:
            self.featArtistsString = "feat. " + andCommaConcat(self.artist["Featured"])

    def checkAndRenewTrackToken(self, dz):
        now = datetime.now()
        expiration = datetime.fromtimestamp(self.trackTokenExpiration)
        if now > expiration:
            newTrack = dz.gw.get_track_with_fallback(self.id)
            self.trackToken = newTrack["TRACK_TOKEN"]
            self.trackTokenExpiration = newTrack["TRACK_TOKEN_EXPIRE"]

    def applySettings(self, settings):

        # Check if should save the playlist as a compilation
        if self.playlist and settings["tags"]["savePlaylistAsCompilation"]:
            self.trackNumber = self.position
            self.discNumber = "1"
            self.album.makePlaylistCompilation(self.playlist)
        else:
            if self.album.date:
                self.date = self.album.date

        self.dateString = self.date.format(settings["dateFormat"])
        self.album.dateString = self.album.date.format(settings["dateFormat"])
        if self.playlist:
            self.playlist.dateString = self.playlist.date.format(settings["dateFormat"])

        # Check various artist option
        if settings["albumVariousArtists"] and self.album.variousArtists:
            artist = self.album.variousArtists
            isMainArtist = artist.role == "Main"

            if artist.name not in self.album.artists:
                self.album.artists.insert(0, artist.name)

            if (
                isMainArtist
                or artist.name not in self.album.artist["Main"]
                and not isMainArtist
            ):
                if artist.role not in self.album.artist:
                    self.album.artist[artist.role] = []
                self.album.artist[artist.role].insert(0, artist.name)
        self.album.mainArtist.save = (
            not self.album.mainArtist.isVariousArtists()
            or settings["albumVariousArtists"]
            and self.album.mainArtist.isVariousArtists()
        )

        # Check removeDuplicateArtists
        if settings["removeDuplicateArtists"]:
            self.removeDuplicateArtists()

        # Check if user wants the feat in the title
        if str(settings["featuredToTitle"]) == FeaturesOption.REMOVE_TITLE:
            self.title = self.getCleanTitle()
        elif str(settings["featuredToTitle"]) == FeaturesOption.MOVE_TITLE:
            self.title = self.getFeatTitle()
        elif str(settings["featuredToTitle"]) == FeaturesOption.REMOVE_TITLE_ALBUM:
            self.title = self.getCleanTitle()
            self.album.title = self.album.getCleanTitle()

        # Remove (Album Version) from tracks that have that
        if settings["removeAlbumVersion"] and "Album Version" in self.title:
            self.title = re.sub(r" ?\(Album Version\)", "", self.title).strip()

        # Change Title and Artists casing if needed
        if settings["titleCasing"] != "nothing":
            self.title = changeCase(self.title, settings["titleCasing"])
        if settings["artistCasing"] != "nothing":
            self.mainArtist.name = changeCase(
                self.mainArtist.name, settings["artistCasing"]
            )
            for i, artist in enumerate(self.artists):
                self.artists[i] = changeCase(artist, settings["artistCasing"])
            for art_type in self.artist:
                for i, artist in enumerate(self.artist[art_type]):
                    self.artist[art_type][i] = changeCase(
                        artist, settings["artistCasing"]
                    )
            self.generateMainFeatStrings()

        # Generate artist tag
        if settings["tags"]["multiArtistSeparator"] == "default":
            if str(settings["featuredToTitle"]) == FeaturesOption.MOVE_TITLE:
                self.artistsString = ", ".join(self.artist["Main"])
            else:
                self.artistsString = ", ".join(self.artists)
        elif settings["tags"]["multiArtistSeparator"] == "andFeat":
            self.artistsString = self.mainArtistsString
            if (
                self.featArtistsString
                and str(settings["featuredToTitle"]) != FeaturesOption.MOVE_TITLE
            ):
                self.artistsString += " " + self.featArtistsString
        else:
            separator = settings["tags"]["multiArtistSeparator"]
            if str(settings["featuredToTitle"]) == FeaturesOption.MOVE_TITLE:
                self.artistsString = separator.join(self.artist["Main"])
            else:
                self.artistsString = separator.join(self.artists)
