import csv
import json
import logging
import os
import sys
from datetime import datetime

import spotipy
from spotipy.oauth2 import SpotifyClientCredentials


def prepare_logging() -> logging.Logger:
    LOG_FORMAT = '[%(levelname)-7s] - %(asctime)s - %(message)s'
    DATE_FORMAT = '%Y-%m-%d %H:%M:%S'

    LOG_FORMATTER = logging.Formatter(fmt=LOG_FORMAT, datefmt=DATE_FORMAT)

    logger = logging.getLogger('SpotifyBackup')
    logger.setLevel(logging.DEBUG)

    outHandler = logging.StreamHandler(sys.stdout)
    outHandler.setFormatter(LOG_FORMATTER)
    outHandler.setLevel(logging.DEBUG)
    outHandler.addFilter(lambda record: record.levelno <= logging.INFO)
    logger.addHandler(outHandler)

    errHandler = logging.StreamHandler(sys.stderr)
    errHandler.setFormatter(LOG_FORMATTER)
    errHandler.setLevel(logging.WARNING)
    logger.addHandler(errHandler)
    return logger


LOGGER = prepare_logging()


class SpotifyBackup:
    DATE_FORMAT = '%Y-%m-%d_%H-%M-%S'
    INVALID_CHARS = [' ', '\t', '\n']
    REPLACE_CHAR = '_'

    def __init__(self, clientID: str, clientSecret: str, exportFolder: str):
        self._exportFolder = exportFolder
        if self._exportFolder:
            os.makedirs(self._exportFolder, exist_ok=True)

        client_credentials_manager = SpotifyClientCredentials(client_id=clientID, client_secret=clientSecret)
        self._spotify = spotipy.Spotify(client_credentials_manager=client_credentials_manager)

    def backup_playlist(self, username: str, playlistID: str):
        playlist = self.__get_playlist(username, playlistID)
        tracks = self.__get_tracks(playlist)

        exportPath = self.__determine_export_path(playlist['name'])

        LOGGER.info(f'Exporting tracks to "{exportPath}"...')
        self.__export_csv(exportPath, tracks)
        LOGGER.info('Exporting DONE')

    def __get_playlist(self, username: str, playlistID: str):
        LOGGER.info(f'Fetching playlist with ID: {playlistID} by {username}...')
        identifier = f'spotify:user:{username}:playlist:{playlistID}'
        playlist = self._spotify.playlist(identifier)
        LOGGER.info(f'Found playlist "{playlist["name"]}"')
        return playlist

    def __get_tracks(self, playlist):
        tracks = playlist['tracks']
        results = tracks['items']
        while tracks['next']:
            tracks = self._spotify.next(tracks)
            results.extend(tracks['items'])

        LOGGER.info(f'Fetched {len(results)} tracks')
        return results

    def __determine_export_path(self, playlistName):
        for item in self.INVALID_CHARS:
            playlistName = playlistName.replace(item, self.REPLACE_CHAR)
        fileName = f'{datetime.strftime(datetime.now(), self.DATE_FORMAT)}_{playlistName}.csv'
        exportPath = os.path.join(self._exportFolder, fileName)
        return exportPath

    def __export_csv(self, exportPath, tracks):
        with open(exportPath, 'w', newline='', encoding='utf-8') as f:
            writer = csv.writer(f, delimiter=';', quotechar='|', quoting=csv.QUOTE_MINIMAL)
            writer.writerow(('title', 'artists', 'album', 'addedBy', 'addedAt', 'url'))

            for track in tracks:
                try:
                    trackInfo = track['track']
                    title = trackInfo['name']
                    artists = trackInfo['artists']
                    artists = ' & '.join(artist['name'] for artist in artists)
                    album = trackInfo['album']['name']
                    author = track['added_by']['id']
                    addedAt = track['added_at']
                    url = trackInfo['external_urls'].get('spotify', '')

                    writer.writerow((title, artists, album, author, addedAt, url))
                except Exception:
                    LOGGER.exception(f'Error while exporting track "{track}"')


if __name__ == '__main__':
    with open('settings.json', 'r', encoding='utf-8') as f:
        SETTINGS = json.load(f)

    spotifyBackup = SpotifyBackup(SETTINGS['spotifyAPI']['clientID'],
                                  SETTINGS['spotifyAPI']['clientSecret'],
                                  SETTINGS['exportFolder'])

    for playlist in SETTINGS['playlists']:
        spotifyBackup.backup_playlist(playlist['user'], playlist['id'])

    LOGGER.info('### DONE ###')