# -*- coding: utf-8 -*-
#
"""
Python library for the Nokia Health API
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Nokia Health API
<http://developer.withings.com/oauth2/>

Uses Oauth 2.0 to authentify. You need to obtain a consumer key
and consumer secret from Nokia by creating an application
here: <https://account.withings.com/partner/add_oauth2>

Usage:

auth = NokiaAuth(CLIENT_ID, CONSUMER_SECRET, CALLBACK_URL)
authorize_url = auth.get_authorize_url()
print("Go to %s allow the app and copy the url you are redirected to." % authorize_url)
authorization_response = raw_input('Please enter your full authorization response url: ')
creds = auth.get_credentials(authorization_response)

client = NokiaApi(creds)
measures = client.get_measures(limit=1)
print("Your last measured weight: %skg" % measures[0].weight)

creds = client.get_credentials()

"""

from __future__ import unicode_literals

__title__ = 'nokia'
__version__ = '0.4.0'
__author__ = 'Maxime Bouroumeau-Fuseau, and ORCAS'
__license__ = 'MIT'
__copyright__ = 'Copyright 2012-2017 Maxime Bouroumeau-Fuseau, and ORCAS'

__all__ = [str('NokiaCredentials'), str('NokiaAuth'), str('NokiaApi'),
           str('NokiaMeasures'), str('NokiaMeasureGroup')]

import arrow
import datetime
import json

from arrow.parser import ParserError
from requests_oauthlib import OAuth2Session
from oauthlib.oauth2 import WebApplicationClient

class NokiaCredentials(object):
    def __init__(self, access_token=None, token_expiry=None, token_type=None,
                 refresh_token=None, user_id=None,
                 client_id=None, consumer_secret=None):
        self.access_token = access_token
        self.token_expiry = token_expiry
        self.token_type = token_type
        self.refresh_token = refresh_token
        self.user_id = user_id
        self.client_id = client_id
        self.consumer_secret = consumer_secret


class NokiaAuth(object):
    URL = 'https://account.withings.com'

    def __init__(self, client_id, consumer_secret, callback_uri,
                 scope='user.metrics'):
        self.client_id = client_id
        self.consumer_secret = consumer_secret
        self.callback_uri = callback_uri
        self.scope = scope

    def _oauth(self):
        return OAuth2Session(self.client_id,
                             redirect_uri=self.callback_uri,
                             scope=self.scope)

    def get_authorize_url(self):
        return self._oauth().authorization_url(
            '%s/oauth2_user/authorize2'%self.URL
        )[0]

    def get_credentials(self, code):
        tokens = self._oauth().fetch_token(
            '%s/oauth2/token' % self.URL,
            code=code,
            timeout=2,
            client_secret=self.consumer_secret)

        return NokiaCredentials(
            access_token=tokens['access_token'],
            token_expiry=str(ts()+int(tokens['expires_in'])),
            token_type=tokens['token_type'],
            refresh_token=tokens['refresh_token'],
            user_id=tokens['userid'],
            client_id=self.client_id,
            consumer_secret=self.consumer_secret,
        )


def is_date(key):
    return 'date' in key


def is_date_class(val):
    return isinstance(val, (datetime.date, datetime.datetime, arrow.Arrow, ))


# Calculate seconds since 1970-01-01 (timestamp) in a way that works in
# Python 2 and Python3
# https://docs.python.org/3/library/datetime.html#datetime.datetime.timestamp
def ts():
    return int((
        datetime.datetime.utcnow() - datetime.datetime(1970, 1, 1)
    ).total_seconds())


class NokiaApi(object):
    URL = 'https://wbsapi.withings.net'

    def __init__(self, credentials):
        self.credentials = credentials
        self.token = {
            'access_token': credentials.access_token,
            'refresh_token': credentials.refresh_token,
            'token_type': credentials.token_type,
            'expires_in': str(int(credentials.token_expiry) - ts()),
        }
        oauth_client = WebApplicationClient(credentials.client_id,
            token=self.token, default_token_placement='query')
        self.client = OAuth2Session(
            credentials.client_id,
            token=self.token,
            client=oauth_client,
            auto_refresh_url='{}/oauth2/token'.format(NokiaAuth.URL),
            auto_refresh_kwargs={
                'client_id': credentials.client_id,
                'client_secret': credentials.consumer_secret,
            },
            token_updater=self.set_token
        )

    def get_credentials(self):
        return self.credentials

    def set_token(self, token):
        self.token = token
        self.credentials.token_expiry = str(
            ts() + int(self.token['expires_in'])
        )
        self.credentials.access_token = self.token['access_token']
        self.credentials.refresh_token = self.token['refresh_token']

    def request(self, service, action, params=None, method='GET',
                version=None):
        params = params or {}
        params['userid'] = self.credentials.user_id
        params['action'] = action
        for key, val in params.items():
            if is_date(key) and is_date_class(val):
                params[key] = arrow.get(val).timestamp
        url_parts = filter(None, [self.URL, version, service])
        r = self.client.request(method, '/'.join(url_parts), params=params,timeout=10)
        response = json.loads(r.content.decode())
        if response['status'] != 0:
            raise Exception("Error code %s" % response['status'])
        return response.get('body', None)

    def get_user(self):
        return self.request('user', 'getbyuserid')

    def get_activities(self, **kwargs):
        r = self.request('measure', 'getactivity', params=kwargs, version='v2')
        activities = r['activities'] if 'activities' in r else [r]
        return [NokiaActivity(act) for act in activities]

    def get_measures(self, **kwargs):
        r = self.request('measure', 'getmeas', kwargs)
        return NokiaMeasures(r)

    def get_sleep(self, **kwargs):
        r = self.request('sleep', 'get', params=kwargs, version='v2')
        return NokiaSleep(r)

    def subscribe(self, callback_url, comment, **kwargs):
        params = {'callbackurl': callback_url, 'comment': comment}
        params.update(kwargs)
        self.request('notify', 'subscribe', params)

    def unsubscribe(self, callback_url, **kwargs):
        params = {'callbackurl': callback_url}
        params.update(kwargs)
        self.request('notify', 'revoke', params)

    def is_subscribed(self, callback_url, appli=1):
        params = {'callbackurl': callback_url, 'appli': appli}
        try:
            self.request('notify', 'get', params)
            return True
        except:
            return False

    def list_subscriptions(self, appli=1):
        r = self.request('notify', 'list', {'appli': appli})
        return r['profiles']


class NokiaObject(object):
    def __init__(self, data):
        self.set_attributes(data)

    def set_attributes(self, data):
        self.data = data
        for key, val in data.items():
            try:
                setattr(self, key, arrow.get(val) if is_date(key) else val)
            except ParserError:
                setattr(self, key, val)


class NokiaActivity(NokiaObject):
    pass


class NokiaMeasures(list, NokiaObject):
    def __init__(self, data):
        super(NokiaMeasures, self).__init__(
            [NokiaMeasureGroup(g) for g in data['measuregrps']])
        self.set_attributes(data)


class NokiaMeasureGroup(NokiaObject):
    MEASURE_TYPES = (
        ('weight', 1),
        ('height', 4),
        ('fat_free_mass', 5),
        ('fat_ratio', 6),
        ('fat_mass_weight', 8),
        ('diastolic_blood_pressure', 9),
        ('systolic_blood_pressure', 10),
        ('heart_pulse', 11),
        ('temperature', 12),
        ('spo2', 54),
        ('body_temperature', 71),
        ('skin_temperature', 72),
        ('muscle_mass', 76),
        ('hydration', 77),
        ('bone_mass', 88),
        ('pulse_wave_velocity', 91)
    )

    def __init__(self, data):
        super(NokiaMeasureGroup, self).__init__(data)
        for n, t in self.MEASURE_TYPES:
            self.__setattr__(n, self.get_measure(t))

    def is_ambiguous(self):
        return self.attrib == 1 or self.attrib == 4

    def is_measure(self):
        return self.category == 1

    def is_target(self):
        return self.category == 2

    def get_measure(self, measure_type):
        for m in self.measures:
            if m['type'] == measure_type:
                return m['value'] * pow(10, m['unit'])
        return None


class NokiaSleepSeries(NokiaObject):
    def __init__(self, data):
        super(NokiaSleepSeries, self).__init__(data)
        self.timedelta = self.enddate - self.startdate


class NokiaSleep(NokiaObject):
    def __init__(self, data):
        super(NokiaSleep, self).__init__(data)
        self.series = [NokiaSleepSeries(series) for series in self.series]