# -*- coding: utf-8 -*-

"""Main module."""
"""Main script for playlistfromsong.py, cf.
https://github.com/schollz/playlistfromsong/
"""
from sys import exit, platform, stderr, version_info
from os.path import join, isfile, abspath
from os import chdir, mkdir
from appdirs import user_data_dir
from subprocess import Popen, PIPE
import multiprocessing
import argparse
import json
import urllib

import appdirs
from requests import get
import yaml
from youtube_dl import YoutubeDL
from bs4 import BeautifulSoup

try:
    output = Popen(
        ['ffmpeg', '--help'],
        stdout=PIPE,
        stderr=PIPE
    )
except:
    print("Need to install ffmpeg (https://ffmpeg.org/download.html)")
    exit(-1)

programSuffix = ""
if platform.startswith('win'):
    programSuffix = ".exe"

"""Check python version
"""
pyVersion = version_info[0]

"""For available codec run following commands

    ffmpeg -codec
"""
FFMPEGDefaultCodec = 'mp3'
FFMPEGDefaultQuality = '192'
defaultConfigFile = join(
    user_data_dir(
        'playlistfromsong',
        'schollz'),
    'playlistfromsong.yaml')
defautlConfigValue = {
    'spotify_bearer_token': None,
    'ffmpeg_codec': FFMPEGDefaultCodec,
    'ffmpeg_quality': FFMPEGDefaultQuality,
}

DEBUG = False


def getYoutubeURLFromSearch(searchString):
    if pyVersion < 3:
        urlParse = urllib.quote_plus(searchString)
    else:
        urlParse = urllib.parse.quote_plus(searchString)
    urlToGet = "https://www.youtube.com/results?search_query=" + urlParse  # NOQA
    r = get(urlToGet)
    soup = BeautifulSoup(r.content, 'html.parser')
    videos = soup.find_all('h3', class_='yt-lockup-title')
    for video in videos:
        link = video.find_all('a')[0]
        url = "https://www.youtube.com" + link.get('href')
        if 'googleads' in url:
            continue
        title = link.text
        if 'doubleclick' in title:
            continue
        if 'list=' in url:
            continue
        if 'album review' in title.lower():
            continue
        return url
    return ""


def getCodecAndQuality(codec=None, quality=None):
    """Get preferred codec and quality.

    This function determine which codec and quality to use,
    depend on program config, user setting and hardcoded default.

    Args:
        codec: Codec from user
        quality: preferred quality from user

    Returns:
        Tuple of (codec, preferredQuality)

    """
    if codec is not None and quality is not None:
        return codec, quality

    # copy the default value
    defaultCodec = FFMPEGDefaultCodec
    defaultQuality = FFMPEGDefaultQuality

    # # load from default config file
    # try:
    #     configValue = loadConfig(configFilePath=defaultConfigFile)
    #     defaultCodec = configValue.get('ffmpeg_codec', FFMPEGDefaultCodec)
    #     defaultQuality = configValue.get(
    #         'ffmpeg_quality', FFMPEGDefaultQuality)
    # except Exception as e:  # pragma: no cover
    #     print('{}:{}'.format(type(e), e))
    #     print("Can't load codec and quality from config file.")

    codec = defaultCodec if codec is None else codec
    quality = defaultQuality if quality is None else quality

    return codec, quality


def downloadURL(url, preferredCodec=None, preferredQuality=None):
    """ Downloads song using youtube_dl and the song's youtube
    url.
    """
    codec, quality = getCodecAndQuality()

    ydl_opts = {
        'format': 'bestaudio/best',
        'quiet': True,
        'no_warnings': True,
        'postprocessors': [{
            'key': 'FFmpegExtractAudio',
            'preferredcodec': codec,
            'preferredquality': quality,
        },
            {'key': 'FFmpegMetadata'},
        ],
    }

    try:
        with YoutubeDL(ydl_opts) as ydl:
            return ydl.extract_info(url, download=True)
    except:
        print("Problem downloading " + url)
    return None


def getYoutubeAndRelatedLastFMTracks(lastfmURL):
    """find the YouTube URL for the last.fm URL and get the next recommendations

    Args:
        lastfmURL: the last.fm URL of the song

    Returns:
        tuple of YouTube URL for the current song and a
        list of the next recomendations
    """
    try:
        artistName = lastfmURL.split('/')[4].replace('+', ' ')
        songName = lastfmURL.split('/')[-1].replace('+', ' ')
    except:
        return "", []
    print('%s - %s' % (artistName, songName))
    youtubeURL = ""
    lastfmTracks = []

    r = get(lastfmURL)
    soup = BeautifulSoup(r.content, 'html.parser')

    try:
        link = soup.find_all('div', class_='video-preview')
        youtubeURL = link[0].find_all('a')[0].get('href')
    except:
        youtubeURL = getYoutubeURLFromSearch(
            '%s - %s official' % (artistName, songName))

    try:
        sections = soup.find_all(
            "section", class_="grid-items-section")[0].find_all('a')
        for track in sections:
            lastfmTracks.append('https://www.last.fm' + track.get('href'))
    except:
        youtubeURL = getYoutubeURLFromSearch(
            '%s - %s official' % (artistName, songName))

    lastfmTracks = list(set(lastfmTracks))
    return (youtubeURL, lastfmTracks)


def useLastFM(song, num):
    """get recommendations from last.fm and find links on YouTube

    Args:
        song: the artist + song to search for on Spotify
        num: number of songs to recommend (1-100)

    Returns:
        list of YouTube URLs that are recommended from last.fm
    """
    searchTrack = song
    r = get('https://www.last.fm/search?q=%s' %
            searchTrack.replace(' ', '+'))
    soup = BeautifulSoup(r.content, 'html.parser')
    firstURL = ""
    try:
        chartlist = soup.find_all('table', class_='chartlist')[0]
    except:
        return []
    for link in chartlist.find_all('a', class_='link-block-target'):
        firstURL = 'https://www.last.fm' + link.get('href')
        break

    youtubeLinks = []
    print("\nPLAYLIST: \n")
    data = getYoutubeAndRelatedLastFMTracks(firstURL)
    finishedLastFMTracks = [firstURL]
    youtubeLinks.append(data[0])
    lastfmTracksNext = data[1]

    tries = 0
    while len(youtubeLinks) < num:
        lastfmTracks = list(set(lastfmTracksNext) - set(finishedLastFMTracks))
        p = multiprocessing.Pool(multiprocessing.cpu_count())
        lastfmTracksNext = []
        for data in p.map(getYoutubeAndRelatedLastFMTracks, lastfmTracks):
            if len(data[0]) > 0:
                youtubeLinks.append(data[0])
                lastfmTracksNext += data[1]
            if len(youtubeLinks) >= num:
                break
        finishedLastFMTracks += lastfmTracks
        tries += 1
        if tries > 5:
            break

    return youtubeLinks


def useSpotify(song, num, bearer):
    """get recommendations from Spotify and find links on YouTube

    Args:
        song: the artist + song to search for on Spotify
        num: number of songs to recommend (1-100)
        bearer: bearer token

    Returns:
        list of YouTube URLs that are recommended from Spotify
    """
    headers = {
        'Accept': 'application/json',
        'Authorization': 'Bearer ' + bearer,
    }
    r = get('https://api.spotify.com/v1/search?q=%s&type=track,artist' % song.replace(' ', '+'), headers=headers)  # NOQA
    if r.status_code != 200:
        print(json.loads(r.text)['error']['message'])
        print("To get an autorization code, goto ")
        print("https://developer.spotify.com/web-api/console/get-track/")
        print("and click 'Get OAUTH TOKEN'")
        exit(-1)
    songJSON = json.loads(r.text)

    try:
        spotifyID = songJSON['tracks']['items'][0]['id']
        songName = songJSON['tracks']['items'][0]['name']
        artistName = songJSON['tracks']['items'][0]['artists'][0]['name']
        print("%s - %s (%s)" % (artistName, songName, spotifyID))
    except:
        return []
    r = get('https://api.spotify.com/v1/recommendations?seed_tracks=%s&limit=%d' % (spotifyID, num - 1), headers=headers)  # NOQA
    recommendationJSON = json.loads(r.text)
    linksToFindOnYoutube = []
    for track in recommendationJSON['tracks']:
        songName = track['name']
        artistName = track['artists'][0]['name']
        print("%s - %s" % (artistName, songName))
        linksToFindOnYoutube.append(
            "%s - %s official" % (artistName, songName))

    # Start downloading and print out progress
    p = multiprocessing.Pool(multiprocessing.cpu_count())
    print("\nSearching YouTube for links...")
    urlsToDownload = []
    for i, link in enumerate(p.imap_unordered(
            getYoutubeURLFromSearch, linksToFindOnYoutube), 1):
        urlsToDownload.append(link)
        stderr.write(
            '\r...{0:2.1%} complete'.format(i / len(linksToFindOnYoutube)))
    print("")
    return urlsToDownload


def run(song, num, bearer=None, folder=None):
    if song is None:
        song = input(
            "Enter the artist and song (e.g. The Beatles Let It Be): ")

    youtubeLinks = []
    if num == 1:
        youtubeLinks.append(getYoutubeURLFromSearch(song))
    elif bearer is None:
        youtubeLinks = useLastFM(song, num)
    else:
        print("Using spotify")
        youtubeLinks = useSpotify(song, num, bearer)

    if len(youtubeLinks) == 0:
        print("Could not find song recommendations for '%s'" % song)
        return

    # Start downloading and print out progress
    if folder is not None:
        chdir(folder)
    else:
        folder = abspath('.')
    p = multiprocessing.Pool(multiprocessing.cpu_count())
    print("\nStarting download...")
    for i, _ in enumerate(p.imap_unordered(downloadURL, youtubeLinks), 1):
        stderr.write(
            '\r...{0:2.1%} complete'.format(i / len(youtubeLinks)))

    print("\n\n%d tracks saved to %s\n" % (len(youtubeLinks), folder))
    return


def getTopFromLastFM(song):
    """ get top recommendation from last.fm

        Args:
            song: the artist + song to search for recommendation

        Returns:
            artist + song
    """
    searchTrack = song
    r = get('https://www.last.fm/search?q=%s' %
            searchTrack.replace(' ', '+'))
    soup = BeautifulSoup(r.content, 'html.parser')
    firstURL = ""
    try:
        chartlist = soup.find_all('table', class_='chartlist')[0]
    except:
        return []
    for link in chartlist.find_all('a', class_='link-block-target'):
        firstURL = link.get('href')
        break

    try:
        artistName = firstURL.split('/')[2].replace('+', ' ')
        songName = firstURL.split('/')[-1].replace('+', ' ')
    except:
        return "Could not find song recommendations for '%s'" % song
    return '%s - %s' % (artistName, songName)