import logging
from concurrent.futures import ThreadPoolExecutor
from datetime import datetime, timedelta

from tqdm import tqdm

from deemon.cmd.download import QueueItem, Download
from deemon.core import db, api, notifier
from deemon.core.config import Config as config
from deemon.utils import dates, ui, performance

logger = logging.getLogger(__name__)


class Refresh:
    def __init__(self, time_machine: list = None, skip_download: bool = False, ignore_filters: bool = False):
        self.db = db.Database()
        self.refresh_date = datetime.now()
        self.max_refresh_date = None
        self.api = api.PlatformAPI("deezer-gw")
        self.new_releases = []
        self.new_releases_alert = []
        self.new_playlist_releases = []
        self.time_machine = False
        self.total_new_releases = 0
        self.queue_list = []
        self.skip_download = skip_download
        self.download_all = ignore_filters
        
        if not time_machine: time_machine = []
        if len(time_machine):
            if len(time_machine) == 2:
                self.refresh_date = time_machine[1]
                self.max_refresh_date = time_machine[0]
                logger.info(":: Time Machine active: "
                            f"{datetime.strftime(self.max_refresh_date, '%b %d, %Y')}"
                            f" - {datetime.strftime(self.refresh_date, '%b %d, %Y')}!")
            else:
                self.refresh_date = time_machine[0]
                logger.info(f":: Time Machine active: {datetime.strftime(self.refresh_date, '%b %d, %Y')}!")
            self.time_machine = True
            config.set('by_release_date', False)

    def debugger(self, message: str, payload = None):
        if config.debug_mode():
            if not payload:
                payload = ""
            logger.debug(f"DEBUG_MODE: {message} {str(payload)}")

    def remove_existing_releases(self, payload: dict) -> list:
        """
        Return list of releases that have not been stored in the database
        """
        new_releases = []

        if payload.get('artist_id'):
            seen_releases = self.db.get_artist_releases()
            if seen_releases:
                seen_releases = [v for x in seen_releases for k, v in x.items()]
                new_releases = [x for x in payload['releases'] if type(x) == dict for k, v in x.items() if
                                k == "id" and v not in seen_releases]
                return new_releases
            return [x for x in payload['releases']]

        if payload.get('tracks'):
            playlist_id = payload['id']
            seen_releases = self.db.get_playlist_tracks(playlist_id)
            if seen_releases:
                seen_releases = [v for x in seen_releases for k, v in x.items()]
                new_releases = [x for x in payload['tracks']
                                if type(x) == dict for k, v in x.items()
                                if k == "id" and v not in seen_releases]
                return new_releases
            return [x for x in payload['tracks']]

        return new_releases

    def filter_artist_releases(self, payload: dict):
        """ Inspect artist releases and decide what to do with each release """
        
        self.debugger("FilterReleases", {'artist': payload['artist_id'],
                                         'releases': len(payload['releases'])})
        for release in payload['releases']:
            release['artist_id'] = payload['artist_id']
            release['artist_name'] = payload['artist_name']
            release['future'] = self.is_future_release(release['release_date'])
            
            if release['explicit_lyrics'] != 1:
                release['explicit_lyrics'] = 0
            
            self.append_database_release(release)
            
            if release['future']:
                continue
            
            explicit_album_id = self.explicit_id(release['title'], payload['releases'])
            if explicit_album_id:
                if explicit_album_id == release['id']:
                    logger.debug(f"An explicit release was found for {release['title']}")
                else:
                    continue
            
            if self.download_all:
                self.queue_release(release)
                continue
            
            if not self.allowed_record_type(payload['record_type'],
                                            release['record_type']):
                logger.debug(f"Record type \"{release['record_type']}\" "
                                "has been filtered out, skipping release "
                                f"{release['id']}")
                continue
            
            if self.release_too_old(release['release_date']):
                logger.debug(f"Release {release['id']} is too old, "
                                "skipping it.")
                continue
            
            if not payload['refreshed']:
                if not self.time_machine:
                    continue
            
            self.queue_release(release)
            
    def append_database_release(self, new_release: dict):
        self.new_releases.append(new_release)
                
    @staticmethod
    def explicit_id(release_title: str, payload: list):
        explicit_found = {}
        for release in payload:
            if release['title'] == release_title:
                if release['explicit_lyrics'] == 1:
                    return release['id']
        
    
    def release_too_old(self, release_date: str):
        release_date_dt = dates.str_to_datetime_obj(release_date)
        if self.time_machine:
            if self.max_refresh_date:
                if release_date_dt < self.max_refresh_date:
                    return True
                if release_date_dt > self.refresh_date:
                    return True
            elif release_date_dt < self.refresh_date:
                return True
        elif config.release_by_date():
            if release_date_dt < (self.refresh_date - timedelta(config.release_max_age())):
                return True
                
                
    def is_future_release(self, release_date: str):
        """ Return 1 if release date is in future, otherwise return 0 """
        
        release_date_dt = dates.str_to_datetime_obj(release_date)
        if release_date_dt > datetime.now():
            return 1
        else:
            return 0
    
    def allowed_record_type(self, artist_rec_type, release_rec_type: str):
        """ Compare actual record_type against allowable """
        
        if artist_rec_type:
            if artist_rec_type == release_rec_type:
                return True
            else:
                return
        elif config.record_type() == release_rec_type:
            return True
        elif config.record_type() == "all":
            return True

    def queue_release(self, release: dict):
        """ Add release to download queue and create alert notification """
        
        self.create_notification(release)
        self.queue_list.append(QueueItem(release_full=release))

    def filter_playlist_releases(self, payload: dict):
        self.debugger(f"Filtering {len(payload['tracks'])} tracks for playlist {payload['title']}")
        if len(payload['tracks']):
            for track in payload['tracks']:
                new_track = track.copy()
                new_track['playlist_id'] = payload['id']
                self.new_playlist_releases.append(new_track)
                
                if payload['refreshed'] == 0:
                    continue
                
                queue_obj = QueueItem(playlist=payload, bitrate=payload['bitrate'],
                                        download_path=payload['download_path'])
                self.debugger("QueuePlaylistItem", queue_obj)
                self.queue_list.append(queue_obj)

    def waiting_for_refresh(self):
        playlists = self.db.get_unrefreshed_playlists()
        artists = self.db.get_unrefreshed_artists()
        if len(playlists) or len(artists):
            return {'artists': artists, 'playlists': playlists}

    # @performance.timeit
    def run(self, artists: list = None, playlists: list = None):
        if artists:
            self.debugger("ManualRefresh", artists)
            monitored_artists = [x for x in (self.db.get_monitored_artist_by_name(a) for a in artists) if x]
            if not len(monitored_artists):
                return logger.warning("Specified artist(s) were not found")
            api_result = self.get_release_data({'artists': monitored_artists})
        elif playlists:
            self.debugger("ManualRefresh", playlists)
            monitored_playlists = [x for x in (self.db.get_monitored_playlist_by_name(p) for p in playlists) if x]
            if not len(monitored_playlists):
                return logger.warning("Specified playlist(s) were not found")
            api_result = self.get_release_data({'playlists': monitored_playlists})
        else:
            waiting = self.waiting_for_refresh()
            if waiting:
                logger.debug(f"There are {len(waiting['playlists'])} playlist(s) and "
                             f"{len(waiting['artists'])} artist(s) waiting to be refreshed.")
                api_result = self.get_release_data(waiting)
            else:
                self.debugger("FullRefresh")
                monitored_playlists = self.db.get_all_monitored_playlists()
                monitored_artists = self.db.get_all_monitored_artists()
                if not len(monitored_playlists) and not len(monitored_artists):
                    return logger.warning("No artists found to refresh")
                api_result = self.get_release_data({'artists': monitored_artists, 'playlists': monitored_playlists})

        artist_processor = tqdm(api_result['artists'], total=len(api_result['artists']), desc="Checking for new releases...",
                                ascii=" #", bar_format=ui.TQDM_FORMAT)
        for payload in artist_processor:
            if len(payload):
                payload['releases'] = self.remove_existing_releases(payload)
                self.filter_artist_releases(payload)

        for payload in api_result['playlists']:
            if len(payload):
                payload['tracks'] = self.remove_existing_releases(payload)
                self.filter_playlist_releases(payload)

        if self.skip_download:
            logger.info(f"You have opted to skip downloads, emptying {len(self.queue_list)} item(s) from queue...")
            self.queue_list.clear()
            self.new_releases_alert.clear()

        if len(self.queue_list):
            dl = Download()
            dl.download_queue(self.queue_list)

        if len(self.new_playlist_releases) or len(self.new_releases):
            if len(self.new_playlist_releases):
                logger.debug("Updating playlist releases in database...")
                self.db.add_new_playlist_releases(self.new_playlist_releases)
            if len(self.new_releases):
                logger.debug("Updating artist releases in database...")
                self.db.add_new_releases(self.new_releases)
            self.db.commit()
            self.db_stats()
            performance.operation_time(config.get('start_time'))
            logger.info("Database is up-to-date.")
        else:
            self.db_stats()
            performance.operation_time(config.get('start_time'))
            logger.info("Database is up-to-date. No new releases were found.")

        if len(self.new_releases_alert) > 0:
            notification = notifier.Notify(self.new_releases_alert)
            notification.send()

    def db_stats(self):
        artists = len(self.db.get_all_monitored_artist_ids())
        playlists = len(self.db.get_all_monitored_playlist_ids())
        releases = len(self.db.get_artist_releases())
        future = len(self.db.get_future_releases())

        print("")
        print(f"+ Artists monitored: {artists:,}")
        print(f"+ Playlists monitored: {playlists:,}")
        print(f"+ Releases seen: {releases:,}")
        print(f"+ Pending future releases: {future:,}")
        print("")

    # @performance.timeit
    def get_release_data(self, to_refresh: dict) -> dict:
        """
        Generate a list of dictionaries containing artist (DB) and release (API)
        information.
        """
        if self.time_machine:
            logger.debug("Time machine has been detected; clearing future releases...")
            artist_ids = [{'id': artist['artist_id']} for artist in to_refresh['artists']]
            playlist_ids = [{'id': playlist['id']} for playlist in to_refresh['playlists']]
            self.db.remove_specific_releases(artist_ids)
            self.db.remove_specific_playlist_tracks(playlist_ids)

        api_result = {'artists': [], 'playlists': []}

        logger.debug(f"Standby, starting refresh...")

        if to_refresh.get('playlists') and len(to_refresh.get('playlists')):
            logger.debug("Fetching playlist track data...")
            self.debugger("SpawningThreads", self.api.max_threads)
            with ThreadPoolExecutor(max_workers=self.api.max_threads) as ex:
                api_result['playlists'] = list(
                    tqdm(ex.map(self.api.get_playlist_tracks,
                                to_refresh['playlists']),
                         total=len(to_refresh['playlists']),
                         desc=f"Fetching playlist track data for "
                              f"{len(to_refresh['playlists'])} playlist(s), "
                              "please wait...",
                         ascii=" #",
                         bar_format=ui.TQDM_FORMAT)
                )

        if to_refresh.get('artists') and len(to_refresh['artists']):
            logger.debug("Fetching artist release data...")
            self.debugger("SpawningThreads", self.api.max_threads)
            with ThreadPoolExecutor(max_workers=self.api.max_threads) as ex:
                api_result['artists'] = list(
                    tqdm(ex.map(self.api.get_artist_albums, to_refresh['artists']),
                         total=len(to_refresh['artists']), desc=f"Fetching artist release data for {len(to_refresh['artists']):,} artist(s), please wait...", ascii=" #",
                         bar_format=ui.TQDM_FORMAT)
                )
        return api_result

    def create_notification(self, release: dict):
        for days in self.new_releases_alert:
            for key in days:
                if key == "release_date":
                    if release['release_date'] in days[key]:
                        days["releases"].append({'artist': release['artist_name'], 'album': release['title']})
                        return

        self.new_releases_alert.append({'release_date': release['release_date'], 'releases': [{'artist': release['artist_name'], 'album': release['title']}]})
