import pydbus
import requests
import hashlib
from gi.repository import GLib
import json
import traceback
import logging
import math
import time
from datetime import timedelta

logging.basicConfig(level=logging.INFO, format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s')
logger = logging.getLogger('scrobbler')

class LastFmScrobbler:
    def __init__(self):
        with open('lastfm_config.json', 'r') as f:
            self.config = json.load(f)

        if 'tokens' not in self.config:
            self.config['tokens'] = {}
        if 'sessions' not in self.config:
            self.config['sessions'] = {}

        self.api_key = self.config['api_key']
        self.api_secret = self.config['api_secret']
        self.loop = GLib.MainLoop()
        self.bus = pydbus.SystemBus()

        logger.info("Waiting for Muziekbeen on the bus...")
        while True:
            try:
                self.mb = self.bus.get('party.segfault.muziekbeen', '/party/segfault/muziekbeen')
            except:
                continue
            else:
                break
        logger.info("Got Muziekbeen bus proxy.")

    def _lastfm_api_call_signed(self, **kwargs):
        kwargs['format'] = 'json'
        kwargs['api_key'] = self.api_key

        # sorted alphabetically
        sig_params_sorted = sorted(kwargs.items(), key=lambda pair: pair[0])
        sig = ""
        for (k, v) in sig_params_sorted:
            # "You must not include the format and callback parameters"
            if (k == 'format' or k == 'callback'): continue
            sig += k
            sig += str(v)
        sig += self.api_secret
        sig_hash = hashlib.md5(sig.encode('utf-8')).hexdigest()

        kwargs['api_sig'] = sig_hash
        return kwargs

    def lastfm_api_call_signed(self, **kwargs):
        kwargs = self._lastfm_api_call_signed(**kwargs)
        return requests.get('https://ws.audioscrobbler.com/2.0/', params=kwargs).json()

    def post_lastfm_api_call_signed(self, **kwargs):
        kwargs = self._lastfm_api_call_signed(**kwargs)
        return requests.post('https://ws.audioscrobbler.com/2.0/', params=kwargs).json()

    def run(self):
        logger.info("Connecting to signals.")
        self.mb.trackStarted.connect(self.trackStarted)
        self.mb.trackStopped.connect(self.trackStopped)
        self.mb.trackFinished.connect(self.trackFinished)
        self.mb.command.connect(self.command)

        logger.info("Running event loop.")
        try:
            self.loop.run()
        except:
            traceback.print_exc()
            with open('lastfm_config.json', 'w') as f:
                json.dump(self.config, f)

    def trackStopped(self, s):
        (artist, album, title, duration) = s
        now = time.time()
        listened_secs = now - self.track_started_timestamp
        listened_td = timedelta(seconds=listened_secs)
        duration_td = timedelta(seconds=duration)
        logger.info(f"Track stopped: {artist} - {album} - {title} (stopped at: {listened_td}/{duration_td})")
        if listened_secs > (duration * 0.5):
            self.doScrobbles(artist, album, title)

    def trackFinished(self, s):
        (artist, album, title, duration) = s
        current_users = self.mb.getUsers()
        logger.info(f"Track finished: {artist} - {album} - {title}")
        if not hasattr(self, 'track_started_timestamp'):
            self.track_started_timestamp = math.floor(time.time() - duration)
        self.doScrobbles(artist, album, title)

    def trackStarted(self, s):
        (artist, album, title, duration) = s
        duration_td = timedelta(seconds=duration)
        current_users = self.mb.getUsers()
        logger.info(f"Track started: {artist} - {album} - {title} (duration: {duration_td})")
        self.track_started_timestamp = math.floor(time.time())
        self.doScrobbles(artist, album, title, now_playing=True)

    def doScrobbles(self, artist, album, title, now_playing=False):
        for user in self.mb.getUsers():
            session = self.config['sessions'].get(user)
            if session is not None:
                logger.info(f"Scrobbling track for user: {user} (which has username: {session['name']})")
                self.scrobble(user, session, artist, album, title, now_playing=now_playing)


    def scrobble(self, user, session, artist, album, title, now_playing=False):
        kwargs = {'artist': artist, 'track': title, 'sk': session['key']}
        if album:
            kwargs['album'] = album

        method = 'track.updateNowPlaying' if now_playing else 'track.scrobble'
        if not now_playing:
            kwargs['timestamp'] = self.track_started_timestamp

        resp = self.post_lastfm_api_call_signed(method=method, **kwargs)
        if ('error' in resp):
            logger.warning(f'Error while scrobbling track {kwargs}: {resp["message"]}')

    def command(self, user, command):
        logger.info(f"Non-core command run by {user}: {command}")
        if (command == ".lastfm-register"):
            self.register(user)
        if (command == '.lastfm-authorized'):
            self.authorized(user)

    def register(self, user):
        token = self.lastfm_api_call_signed(method='auth.getToken')['token']
        self.config['tokens'][user] = token
        logger.info(f'Got token for user {user}: {token}. Sending authorization request.')
        self.mb.sendMessage(f"Please go to https://www.last.fm/api/auth/?api_key={self.api_key}&token={token} to authorize Muziekbeen, and send .lastfm-authorized when that's done.")

    def authorized(self, user):
        logger.info(f"User {user} says they've authorized Muziekbeen.")
        token = self.config['tokens'].get(user)
        if not token:
            return self.mb.sendMessage(f"You haven't registered with Muziekbeen at all!")
        resp = self.lastfm_api_call_signed(method='auth.getSession', token=token)
        logger.info(f"Response to auth.getSession: {resp}")
        if 'error' in resp:
            return self.mb.sendMessage(f"Authorization failed: {resp['message']}")
        else:
            self.config['sessions'][user] = {'name': resp['session']['name'], 'key': resp['session']['key']}

scrobbler = LastFmScrobbler()
scrobbler.run()
