import functools
import json
import logging

from requests import auth
from requests_futures import sessions

from lbrynet import conf
from lbrynet.analytics import utils


log = logging.getLogger(__name__)


def log_response(fn):
    def _log(future):
        if future.cancelled():
            log.warning('Request was unexpectedly cancelled')
        elif future.exception():
            exc, traceback = future.exception_info()
            log.warning('Failed to send an analytics event', exc_info=(type(exc), exc, traceback))
        else:
            response = future.result()
            log.debug('Response (%s): %s', response.status_code, response.content)

    @functools.wraps(fn)
    def wrapper(*args, **kwargs):
        future = fn(*args, **kwargs)
        future.add_done_callback(_log)
        return future
    return wrapper


class Api(object):
    def __init__(self, session, url, write_key):
        self.session = session
        self.url = url
        self.write_key = write_key

    def post(self, endpoint, data):
        # there is an issue with a timing condition with keep-alive
        # that is best explained here: https://github.com/mikem23/keepalive-race
        #
        #   If you make a request, wait just the right amount of time,
        #   then make another request, the requests module may opt to
        #   reuse the connection, but by the time the server gets it the
        #   timeout will have expired.
        #
        # by forcing the connection to close, we will disable the keep-alive.
        assert endpoint[0] == '/'
        headers = {"Connection": "close"}
        return self.session.post(
            self.url + endpoint, json=data, auth=self.auth, headers=headers)

    @property
    def auth(self):
        return auth.HTTPBasicAuth(self.write_key, '')

    @log_response
    def batch(self, events):
        """Send multiple events in one request.

        Each event needs to have its type specified.
        """
        data = json.dumps({
            'batch': events,
            'sentAt': utils.now(),
        })
        log.debug('sending %s events', len(events))
        log.debug('Data: %s', data)
        return self.post('/batch', data)

    @log_response
    def track(self, event):
        """Send a single tracking event"""
        log.debug('Sending track event: %s', event)
        return self.post('/track', event)

    @classmethod
    def new_instance(cls, session=None):
        """Initialize an instance using values from the configuration"""
        if not session:
            session = sessions.FuturesSession()
        return cls(
            session,
            conf.settings.ANALYTICS_ENDPOINT,
            utils.deobfuscate(conf.settings.ANALYTICS_TOKEN)
        )