from deemix.app.downloadjob import DownloadJob
from deemix.utils import getIDFromLink, getTypeFromLink, getBitrateInt
from deemix.api.deezer import APIError, LyricsStatus
from spotipy.exceptions import SpotifyException
from deemix.app.queueitem import QueueItem, QISingle, QICollection, QIConvertable
import logging
from pathlib import Path
import json
from os import remove
import eventlet
urlopen = eventlet.import_patched('urllib.request').urlopen

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger('deemix')

class QueueManager:
    def __init__(self, spotifyHelper=None):
        self.queue = []
        self.queueList = {}
        self.queueComplete = []
        self.currentItem = ""
        self.sp = spotifyHelper

    def generateTrackQueueItem(self, dz, id, settings, bitrate, trackAPI=None, albumAPI=None):
        if str(id).startswith("isrc"):
            try:
                trackAPI = dz.get_track(id)
            except APIError as e:
                e = json.loads(str(e))
                return QueueError("https://deezer.com/track/"+str(id), f"Wrong URL: {e['type']+': ' if 'type' in e else ''}{e['message'] if 'message' in e else ''}")
            if 'id' in trackAPI and 'title' in trackAPI:
                id = trackAPI['id']
            else:
                return QueueError("https://deezer.com/track/"+str(id), "Track ISRC is not available on deezer", "ISRCnotOnDeezer")
        try:
            trackAPI_gw = dz.get_track_gw(id)
        except APIError as e:
            e = json.loads(str(e))
            message = "Wrong URL"
            if "DATA_ERROR" in e:
                message += f": {e['DATA_ERROR']}"
            return QueueError("https://deezer.com/track/"+str(id), message)

        if albumAPI:
            trackAPI_gw['_EXTRA_ALBUM'] = albumAPI
        if trackAPI:
            trackAPI_gw['_EXTRA_TRACK'] = trackAPI

        if settings['createSingleFolder']:
            trackAPI_gw['FILENAME_TEMPLATE'] = settings['albumTracknameTemplate']
        else:
            trackAPI_gw['FILENAME_TEMPLATE'] = settings['tracknameTemplate']

        trackAPI_gw['SINGLE_TRACK'] = True

        title = trackAPI_gw['SNG_TITLE']
        if 'VERSION' in trackAPI_gw and trackAPI_gw['VERSION']:
            title += " " + trackAPI_gw['VERSION']
        explicit = int(trackAPI_gw['EXPLICIT_LYRICS']) > 0

        return QISingle(
            id=id,
            bitrate=bitrate,
            title=title,
            artist=trackAPI_gw['ART_NAME'],
            cover=f"https://e-cdns-images.dzcdn.net/images/cover/{trackAPI_gw['ALB_PICTURE']}/75x75-000000-80-0-0.jpg",
            explicit=explicit,
            type='track',
            settings=settings,
            single=trackAPI_gw,
        )

    def generateAlbumQueueItem(self, dz, id, settings, bitrate):
        try:
            albumAPI = dz.get_album(id)
        except APIError as e:
            e = json.loads(str(e))
            return QueueError("https://deezer.com/album/"+str(id), f"Wrong URL: {e['type']+': ' if 'type' in e else ''}{e['message'] if 'message' in e else ''}")

        if str(id).startswith('upc'):
            id = albumAPI['id']
        albumAPI_gw = dz.get_album_gw(id)
        albumAPI['nb_disk'] = albumAPI_gw['NUMBER_DISK']
        albumAPI['copyright'] = albumAPI_gw['COPYRIGHT']

        if albumAPI['nb_tracks'] == 1:
            return self.generateTrackQueueItem(dz, albumAPI['tracks']['data'][0]['id'], settings, bitrate, albumAPI=albumAPI)

        tracksArray = dz.get_album_tracks_gw(id)
        if albumAPI['nb_tracks'] == 255:
            albumAPI['nb_tracks'] = len(tracksArray)

        if albumAPI['cover_small'] != None:
            cover = albumAPI['cover_small'][:-24] + '/75x75-000000-80-0-0.jpg'
        else:
            cover = f"https://e-cdns-images.dzcdn.net/images/cover/{albumAPI_gw['ALB_PICTURE']}/75x75-000000-80-0-0.jpg"

        totalSize = len(tracksArray)
        collection = []
        for pos, trackAPI in enumerate(tracksArray, start=1):
            trackAPI['_EXTRA_ALBUM'] = albumAPI
            trackAPI['POSITION'] = pos
            trackAPI['SIZE'] = totalSize
            trackAPI['FILENAME_TEMPLATE'] = settings['albumTracknameTemplate']
            collection.append(trackAPI)

        explicit = albumAPI_gw['EXPLICIT_ALBUM_CONTENT']['EXPLICIT_LYRICS_STATUS'] in [LyricsStatus.EXPLICIT, LyricsStatus.PARTIALLY_EXPLICIT]

        return QICollection(
            id=id,
            bitrate=bitrate,
            title=albumAPI['title'],
            artist=albumAPI['artist']['name'],
            cover=cover,
            explicit=explicit,
            size=totalSize,
            type='album',
            settings=settings,
            collection=collection,
        )

    def generatePlaylistQueueItem(self, dz, id, settings, bitrate):
        try:
            playlistAPI = dz.get_playlist(id)
        except:
            playlistAPI = None
        if not playlistAPI:
            try:
                playlistAPI = dz.get_playlist_gw(id)
            except APIError as e:
                e = json.loads(str(e))
                message = "Wrong URL"
                if "DATA_ERROR" in e:
                    message += f": {e['DATA_ERROR']}"
                return QueueError("https://deezer.com/playlist/"+str(id), message)

        if not playlistAPI['public'] and playlistAPI['creator']['id'] != str(dz.user['id']):
            logger.warn("You can't download others private playlists.")
            return QueueError("https://deezer.com/playlist/"+str(id), "You can't download others private playlists.", "notYourPrivatePlaylist")

        playlistTracksAPI = dz.get_playlist_tracks_gw(id)
        playlistAPI['various_artist'] = dz.get_artist(5080)

        totalSize = len(playlistTracksAPI)
        collection = []
        for pos, trackAPI in enumerate(playlistTracksAPI, start=1):
            if trackAPI['EXPLICIT_TRACK_CONTENT'].get('EXPLICIT_LYRICS_STATUS', LyricsStatus.UNKNOWN) in [LyricsStatus.EXPLICIT, LyricsStatus.PARTIALLY_EXPLICIT]:
                playlistAPI['explicit'] = True
            trackAPI['_EXTRA_PLAYLIST'] = playlistAPI
            trackAPI['POSITION'] = pos
            trackAPI['SIZE'] = totalSize
            trackAPI['FILENAME_TEMPLATE'] = settings['playlistTracknameTemplate']
            collection.append(trackAPI)
        if not 'explicit' in playlistAPI:
            playlistAPI['explicit'] = False

        return QICollection(
            id=id,
            bitrate=bitrate,
            title=playlistAPI['title'],
            artist=playlistAPI['creator']['name'],
            cover=playlistAPI['picture_small'][:-24] + '/75x75-000000-80-0-0.jpg',
            explicit=playlistAPI['explicit'],
            size=totalSize,
            type='playlist',
            settings=settings,
            collection=collection,
        )

    def generateArtistQueueItem(self, dz, id, settings, bitrate, interface=None):
        try:
            artistAPI = dz.get_artist(id)
        except APIError as e:
            e = json.loads(str(e))
            return QueueError("https://deezer.com/artist/"+str(id), f"Wrong URL: {e['type']+': ' if 'type' in e else ''}{e['message'] if 'message' in e else ''}")

        if interface: interface.send("startAddingArtist", {'name': artistAPI['name'], 'id': artistAPI['id']})

        artistAPITracks = dz.get_artist_albums(id)
        albumList = []
        for album in artistAPITracks['data']:
            albumList.append(self.generateAlbumQueueItem(dz, album['id'], settings, bitrate))

        if interface: interface.send("finishAddingArtist", {'name': artistAPI['name'], 'id': artistAPI['id']})

        return albumList

    def generateArtistDiscographyQueueItem(self, dz, id, settings, bitrate, interface=None):
        try:
            artistAPI = dz.get_artist(id)
        except APIError as e:
            e = json.loads(str(e))
            return QueueError("https://deezer.com/artist/"+str(id)+"/discography", f"Wrong URL: {e['type']+': ' if 'type' in e else ''}{e['message'] if 'message' in e else ''}")

        if interface: interface.send("startAddingArtist", {'name': artistAPI['name'], 'id': artistAPI['id']})

        artistDiscographyAPI = dz.get_artist_discography_gw(id, 100)
        albumList = []
        for type in artistDiscographyAPI:
            if type != 'all':
                for album in artistDiscographyAPI[type]:
                    albumList.append(self.generateAlbumQueueItem(dz, album['id'], settings, bitrate))

        if interface: interface.send("finishAddingArtist", {'name': artistAPI['name'], 'id': artistAPI['id']})

        return albumList

    def generateArtistTopQueueItem(self, dz, id, settings, bitrate, interface=None):
        try:
            artistAPI = dz.get_artist(id)
        except APIError as e:
            e = json.loads(str(e))
            return QueueError("https://deezer.com/artist/"+str(id)+"/top_track", f"Wrong URL: {e['type']+': ' if 'type' in e else ''}{e['message'] if 'message' in e else ''}")

        playlistAPI = {
            'id': str(artistAPI['id'])+"_top_track",
            'title': artistAPI['name']+" - Top Tracks",
            'description': "Top Tracks for "+artistAPI['name'],
            'duration': 0,
            'public': True,
            'is_loved_track': False,
            'collaborative': False,
            'nb_tracks': 0,
            'fans': artistAPI['nb_fan'],
            'link': "https://www.deezer.com/artist/"+str(artistAPI['id'])+"/top_track",
            'share': None,
            'picture': artistAPI['picture'],
            'picture_small': artistAPI['picture_small'],
            'picture_medium': artistAPI['picture_medium'],
            'picture_big': artistAPI['picture_big'],
            'picture_xl': artistAPI['picture_xl'],
            'checksum': None,
            'tracklist': "https://api.deezer.com/artist/"+str(artistAPI['id'])+"/top",
            'creation_date': "XXXX-00-00",
            'creator': {
                'id': "art_"+str(artistAPI['id']),
                'name': artistAPI['name'],
                'type': "user"
            },
            'type': "playlist"
        }

        artistTopTracksAPI_gw = dz.get_artist_toptracks_gw(id)
        playlistAPI['various_artist'] = dz.get_artist(5080)
        playlistAPI['nb_tracks'] = len(artistTopTracksAPI_gw)

        totalSize = len(artistTopTracksAPI_gw)
        collection = []
        for pos, trackAPI in enumerate(artistTopTracksAPI_gw, start=1):
            if trackAPI['EXPLICIT_TRACK_CONTENT'].get('EXPLICIT_LYRICS_STATUS', LyricsStatus.UNKNOWN) in [LyricsStatus.EXPLICIT, LyricsStatus.PARTIALLY_EXPLICIT]:
                playlistAPI['explicit'] = True
            trackAPI['_EXTRA_PLAYLIST'] = playlistAPI
            trackAPI['POSITION'] = pos
            trackAPI['SIZE'] = totalSize
            trackAPI['FILENAME_TEMPLATE'] = settings['playlistTracknameTemplate']
            collection.append(trackAPI)
        if not 'explicit' in playlistAPI:
            playlistAPI['explicit'] = False

        return QICollection(
            id=id,
            bitrate=bitrate,
            title=playlistAPI['title'],
            artist=playlistAPI['creator']['name'],
            cover=playlistAPI['picture_small'][:-24] + '/75x75-000000-80-0-0.jpg',
            explicit=playlistAPI['explicit'],
            size=totalSize,
            type='playlist',
            settings=settings,
            collection=collection,
        )

    def generateQueueItem(self, dz, url, settings, bitrate=None, interface=None):
        bitrate = getBitrateInt(bitrate) or settings['maxBitrate']
        if 'deezer.page.link' in url: url = urlopen(url).url

        type = getTypeFromLink(url)
        id = getIDFromLink(url, type)
        if type == None or id == None:
            logger.warn("URL not recognized")
            return QueueError(url, "URL not recognized", "invalidURL")

        if type == "track":
            return self.generateTrackQueueItem(dz, id, settings, bitrate)
        elif type == "album":
            return self.generateAlbumQueueItem(dz, id, settings, bitrate)
        elif type == "playlist":
            return self.generatePlaylistQueueItem(dz, id, settings, bitrate)
        elif type == "artist":
            return self.generateArtistQueueItem(dz, id, settings, bitrate, interface=interface)
        elif type == "artistdiscography":
            return self.generateArtistDiscographyQueueItem(dz, id, settings, bitrate, interface=interface)
        elif type == "artisttop":
            return self.generateArtistTopQueueItem(dz, id, settings, bitrate, interface=interface)
        elif type.startswith("spotify") and self.sp:
            if not self.sp.spotifyEnabled:
                logger.warn("Spotify Features is not setted up correctly.")
                return QueueError(url, "Spotify Features is not setted up correctly.", "spotifyDisabled")

            if type == "spotifytrack":
                try:
                    (track_id, trackAPI, _) = self.sp.get_trackid_spotify(dz, id, settings['fallbackSearch'])
                except SpotifyException as e:
                    return QueueError(url, "Wrong URL: "+e.msg[e.msg.find('\n')+2:])
                except Exception as e:
                    return QueueError(url, "Something went wrong: "+str(e))

                if track_id != "0":
                    return self.generateTrackQueueItem(dz, track_id, settings, bitrate, trackAPI=trackAPI)
                else:
                    logger.warn("Track not found on deezer!")
                    return QueueError(url, "Track not found on deezer!", "trackNotOnDeezer")

            elif type == "spotifyalbum":
                try:
                    album_id = self.sp.get_albumid_spotify(dz, id)
                except SpotifyException as e:
                    return QueueError(url, "Wrong URL: "+e.msg[e.msg.find('\n')+2:])
                except Exception as e:
                    return QueueError(url, "Something went wrong: "+str(e))

                if album_id != "0":
                    return self.generateAlbumQueueItem(dz, album_id, settings, bitrate)
                else:
                    logger.warn("Album not found on deezer!")
                    return QueueError(url, "Album not found on deezer!", "albumNotOnDeezer")

            elif type == "spotifyplaylist":
                try:
                    return self.sp.generate_playlist_queueitem(dz, id, bitrate, settings)
                except SpotifyException as e:
                    return QueueError(url, "Wrong URL: "+e.msg[e.msg.find('\n')+2:])
                except Exception as e:
                    return QueueError(url, "Something went wrong: "+str(e))
        else:
            logger.warn("URL not supported yet")
            return QueueError(url, "URL not supported yet", "unsupportedURL")

    def addToQueue(self, dz, url, settings, bitrate=None, interface=None, ack=None):
        if not dz.logged_in:
            if interface: interface.send("loginNeededToDownload")
            return False

        def parseLink(link):
            link = link.strip()
            if link == "":
                return False
            logger.info("Generating queue item for: "+link)
            item = self.generateQueueItem(dz, link, settings, bitrate, interface=interface)

            # Add ack to all items
            if type(item) is list:
                for i in item:
                    if isinstance(i, QueueItem):
                        i.ack = ack
            elif isinstance(item, QueueItem):
                item.ack = ack
            return item

        if type(url) is list:
            queueItem = []
            for link in url:
                item = parseLink(link)
                if not item:
                    continue
                elif type(item) is list:
                    queueItem += item
                else:
                    queueItem.append(item)
            if not len(queueItem):
                return False
        else:
            queueItem = parseLink(url)
            if not queueItem:
                return False

        def processQueueItem(item, silenced=False):
            if isinstance(item, QueueError):
                logger.error(f"[{item.link}] {item.message}")
                if interface and not silenced: interface.send("queueError", item.toDict())
                return False
            if item.uuid in list(self.queueList.keys()):
                logger.warn(f"[{item.uuid}] Already in queue, will not be added again.")
                if interface and not silenced: interface.send("alreadyInQueue", {'uuid': item.uuid, 'title': item.title})
                return False
            self.queue.append(item.uuid)
            self.queueList[item.uuid] = item
            logger.info(f"[{item.uuid}] Added to queue.")
            return True

        if type(queueItem) is list:
            slimmedItems = []
            for item in queueItem:
                if processQueueItem(item, silenced=True):
                    slimmedItems.append(item.getSlimmedItem())
                else:
                    continue
            if not len(slimmedItems):
                return False
            if interface: interface.send("addedToQueue", slimmedItems)
        else:
            if processQueueItem(queueItem):
                if interface: interface.send("addedToQueue", queueItem.getSlimmedItem())
            else:
                return False
        self.nextItem(dz, interface)
        return True

    def nextItem(self, dz, interface=None):
        if self.currentItem != "":
            return None
        else:
            if len(self.queue) > 0:
                self.currentItem = self.queue.pop(0)
            else:
                return None

            if isinstance(self.queueList[self.currentItem], QIConvertable) and self.queueList[self.currentItem].extra:
                logger.info(f"[{self.currentItem}] Converting tracks to deezer.")
                self.sp.convert_spotify_playlist(dz, self.queueList[self.currentItem], interface=interface)
                logger.info(f"[{self.currentItem}] Tracks converted.")

            if interface: interface.send("startDownload", self.currentItem)
            logger.info(f"[{self.currentItem}] Started downloading.")
            DownloadJob(dz, self.queueList[self.currentItem], interface).start()
            if self.queueList[self.currentItem].cancel:
                del self.queueList[self.currentItem]
            else:
                self.queueComplete.append(self.currentItem)
            logger.info(f"[{self.currentItem}] Finished downloading.")
            self.currentItem = ""
            self.nextItem(dz, interface)

    def getQueue(self):
        return (self.queue, self.queueComplete, self.slimQueueList(), self.currentItem)

    def saveQueue(self, configFolder):
        if len(self.queueList) > 0:
            if self.currentItem != "":
                self.queue.insert(0, self.currentItem)
            with open(Path(configFolder) / 'queue.json', 'w') as f:
                json.dump({
                    'queue': self.queue,
                    'queueComplete': self.queueComplete,
                    'queueList': self.exportQueueList()
                }, f)

    def exportQueueList(self):
        queueList = {}
        for uuid in self.queueList:
            if uuid in self.queue:
                queueList[uuid] = self.queueList[uuid].getResettedItem()
            else:
                queueList[uuid] = self.queueList[uuid].toDict()
        return queueList

    def slimQueueList(self):
        queueList = {}
        for uuid in self.queueList:
            queueList[uuid] = self.queueList[uuid].getSlimmedItem()
        return queueList

    def loadQueue(self, configFolder, settings, interface=None):
        configFolder = Path(configFolder)
        if (configFolder / 'queue.json').is_file() and not len(self.queue):
            if interface:
                interface.send('restoringQueue')
            with open(configFolder / 'queue.json', 'r') as f:
                qd = json.load(f)
            remove(configFolder / 'queue.json')
            self.restoreQueue(qd['queue'], qd['queueComplete'], qd['queueList'], settings)
            if interface:
                interface.send('init_downloadQueue', {
                    'queue': self.queue,
                    'queueComplete': self.queueComplete,
                    'queueList': self.slimQueueList(),
                    'restored': True
                })

    def restoreQueue(self, queue, queueComplete, queueList, settings):
        self.queue = queue
        self.queueComplete = queueComplete
        self.queueList = {}
        for uuid in queueList:
            if 'single' in queueList[uuid]:
                self.queueList[uuid] = QISingle(queueItemDict = queueList[uuid])
            if 'collection' in queueList[uuid]:
                self.queueList[uuid] = QICollection(queueItemDict = queueList[uuid])
            if '_EXTRA' in queueList[uuid]:
                self.queueList[uuid] = QIConvertable(queueItemDict = queueList[uuid])
            self.queueList[uuid].settings = settings

    def removeFromQueue(self, uuid, interface=None):
        if uuid == self.currentItem:
            if interface:
                interface.send("cancellingCurrentItem", uuid)
            self.queueList[uuid].cancel = True
        elif uuid in self.queue:
            self.queue.remove(uuid)
            del self.queueList[uuid]
            if interface:
                interface.send("removedFromQueue", uuid)
        elif uuid in self.queueComplete:
            self.queueComplete.remove(uuid)
            del self.queueList[uuid]
            if interface:
                interface.send("removedFromQueue", uuid)


    def cancelAllDownloads(self, interface=None):
        self.queue = []
        self.queueComplete = []
        if self.currentItem != "":
            if interface:
                interface.send("cancellingCurrentItem", self.currentItem)
            self.queueList[self.currentItem].cancel = True
        for uuid in list(self.queueList.keys()):
            if uuid != self.currentItem:
                del self.queueList[uuid]
        if interface:
            interface.send("removedAllDownloads", self.currentItem)


    def removeFinishedDownloads(self, interface=None):
        for uuid in self.queueComplete:
            del self.queueList[uuid]
        self.queueComplete = []
        if interface:
            interface.send("removedFinishedDownloads")

class QueueError:
    def __init__(self, link, message, errid=None):
        self.link = link
        self.message = message
        self.errid = errid

    def toDict(self):
        return {
            'link': self.link,
            'error': self.message,
            'errid': self.errid
        }
