#!/usr/bin/env python

"""
Note: This file was adapted from the unoffiicial Python Fitbit client Git repo:
https://raw.githubusercontent.com/orcasgit/python-fitbit/master/gather_keys_oauth2.py
"""
import cherrypy
import os
import sys
import threading
import traceback
import webbrowser
import json

from urllib.parse import urlparse
# import urllib.parse as urlparse
from base64 import b64encode
from fitbit.api import Fitbit, FitbitOauth2Client
from oauthlib.oauth2.rfc6749.errors import MismatchingStateError, MissingTokenError

CLIENT_DETAILS_FILE = 'client_details.json'  # configuration for for the client
USER_DETAILS_FILE = 'user_details.json'  # user details file


class OAuth2Server:
    def __init__(self, client_id, client_secret,
                 redirect_uri='http://127.0.0.1:8080/'):
        """ Initialize the FitbitOauth2Client """
        self.success_html = """
            <h1>You are now authorized to access the Fitbit API!</h1>
            <br/><h3>You can close this window</h3>"""
        self.failure_html = """
            <h1>ERROR: %s</h1><br/><h3>You can close this window</h3>%s"""

        self.fitbit = Fitbit(
            client_id,
            client_secret,
            redirect_uri=redirect_uri,
            timeout=10,
        )

        self.redirect_uri = redirect_uri
        self.oauth = FitbitOauth2Client(client_id, client_secret)


    def headless_authorize(self):
        """
        Authorize without a display using only TTY.
        """
        url, _ = self.oauth.authorize_token_url(redirect_uri=self.redirect_uri)
        # Ask the user to open this url on a system with browser
        print('\n-------------------------------------------------------------------------')
        print('\t\tOpen the below URL in your browser\n')
        print(url)
        print('\n-------------------------------------------------------------------------\n')
        print('NOTE: After authenticating on Fitbit website, you will redirected to a URL which ')
        print('throws an ERROR. This is expected! Just copy the full redirected here.\n')
        redirected_url = input('Full redirected URL: ')
        params = urlparse.parse_qs(urlparse.urlparse(redirected_url).query)
        print(params['code'][0])
        self.authenticate_code(code=params['code'][0])

    def browser_authorize(self):
        """
        Open a browser to the authorization url and spool up a CherryPy
        server to accept the response
        """
        url, _ = self.fitbit.client.authorize_token_url()
        # Open the web browser in a new thread for command-line browser support
        threading.Timer(1, webbrowser.open, args=(url,)).start()
        print()
        print('URL for authenticating is:')
        print(url)
        print()

        # Same with redirect_uri hostname and port.
        urlparams = urlparse(self.redirect_uri)
        cherrypy.config.update({'server.socket_host': '0.0.0.0',
                                'server.socket_port': urlparams.port})

        cherrypy.quickstart(self)

    def authenticate_code(self, code=None):
        """
        Final stage of authentication using the code from Fitbit.
        """
        try:
            self.oauth.fetch_access_token(code, self.redirect_uri)
        except MissingTokenError:
            error = self._fmt_failure(
                'Missing access token parameter.</br>Please check that '
                'you are using the correct client_secret'
            )
        except MismatchingStateError:
            error = self._fmt_failure('CSRF Warning! Mismatching state')

    @cherrypy.expose

    def index(self, state, code=None, error=None):
        """
        Receive a Fitbit response containing a verification code. Use the code
        to fetch the access_token.
        """
        error = None
        if code:
            try:
                self.fitbit.client.fetch_access_token(code)
            except MissingTokenError:
                error = self._fmt_failure(
                    'Missing access token parameter.</br>Please check that '
                    'you are using the correct client_secret')
            except MismatchingStateError:
                error = self._fmt_failure('CSRF Warning! Mismatching state')
        else:
            error = self._fmt_failure('Unknown error while authenticating')
        # Use a thread to shutdown cherrypy so we can return HTML first
        self._shutdown_cherrypy()
        return error if error else self.success_html

    def _fmt_failure(self, message):
        tb = traceback.format_tb(sys.exc_info()[2])
        tb_html = '<pre>%s</pre>' % ('\n'.join(tb)) if tb else ''
        return self.failure_html % (message, tb_html)

    def _shutdown_cherrypy(self):
        """ Shutdown cherrypy in one second, if it's running """
        if cherrypy.engine.state == cherrypy.engine.states.STARTED:
            threading.Timer(1, cherrypy.engine.exit).start()


if __name__ == '__main__':
    if not (len(sys.argv) == 3):
        print("Arguments: client_id and client_secret")
        sys.exit(1)

    client_id = sys.argv[1]
    client_secret = sys.argv[2]
    server = OAuth2Server(client_id, client_secret)
    # server.headless_authorize()
    server.browser_authorize()

    profile = server.fitbit.user_profile_get()
    print('You are authorized to access data for the user: {}'.format(
        profile['user']['fullName']))

    print('TOKEN\n=====\n')
    for key, value in server.fitbit.client.session.token.items():
        print('{} = {}'.format(key, value))

    print("Writing client details to file for usage on next collection.")
    client_details = {'client_id': client_id, 'client_secret': client_secret}  # Details of application
    with open(CLIENT_DETAILS_FILE, 'w') as f:
        json.dump(client_details, f)

    print("Writing user details to file for usage on next collection.")
    with open(USER_DETAILS_FILE, 'w') as f:
        json.dump(server.fitbit.client.session.token, f)

