from __future__ import division
from __future__ import print_function
from __future__ import absolute_import
from __future__ import unicode_literals
import base64

import os
import time
import json
import platform
from uuid import uuid4
from functools import wraps
from fsbc.Application import app
from fsbc.http import HTTPConnection, HTTPError, urlencode
from fsbc.task import Task


class NonRetryableHTTPError(HTTPError):
    pass


class BadRequestError(NonRetryableHTTPError):
    pass


class UnauthorizedError(NonRetryableHTTPError):
    pass


class ForbiddenError(NonRetryableHTTPError):
    pass


class NotFoundError(NonRetryableHTTPError):
    pass


def retry(f):
    @wraps(f)
    def wrapper(*args, **kwargs):
        for i in range(10):
            if i > 0:
                print("retrying...")
            try:
                return f(*args, **kwargs)
            except NonRetryableHTTPError as e:
                raise e
            except Exception as e:
                print(repr(e))
                time.sleep(i * 0.5)
        return f(*args, **kwargs)
    return wrapper


class OGDClient(object):

    HTTPError = HTTPError
    BadRequestError = BadRequestError
    UnauthorizedError = UnauthorizedError
    ForbiddenError = ForbiddenError
    NotFoundError = NotFoundError
    NonRetryableHTTPError = NonRetryableHTTPError

    def __init__(self):
        pass

    @staticmethod
    def is_logged_in():
        # non-empty ogd_auth means we are logged in (probably, the
        # authorization can in theory have been invalidated on the server
        return bool(app.settings["database_auth"])

    def login_task(self, username, password):
        return LoginTask(self, username, password)

    def logout_task(self, auth_token):
        return LogoutTask(self, auth_token)

    @retry
    def auth(self, username, password, device_id, device_name):
        result = self.post(
            "/api/auth", {"username": username,
                          "password": password,
                          "device_id": device_id,
                          "device_name": device_name},
            auth=False)
        return result

    @retry
    def deauth(self, auth_token):
        result = self.post(
            "/api/deauth", {"auth_token": auth_token},
            auth=False)
        return result

    @staticmethod
    def get_server():
        server = app.settings["database_server"]
        if not server:
            server = "oagd.net"
        return server

    @staticmethod
    def get_credentials():
        auth_token = app.settings["database_auth"]
        return "auth_token", auth_token

    def post(self, path, params=None, data=None, auth=True):
        headers = {
        }
        if auth:
            credentials = self.get_credentials()
            headers[str("Authorization")] = str("Basic " + base64.b64encode(
                "{0}:{1}".format(*credentials).encode("UTF-8")))

        # if data is None:
        #     data = "{}"
        server = self.get_server()
        connection = HTTPConnection(server, timeout=30)
        url = "http://{0}{1}".format(server, path)
        # if params:
        #     url += "?" + urlencode(params)
        if not data and params:
            data = urlencode(params)
            headers[str("Content-Type")] = \
                str("application/x-www-form-urlencoded")

        print(url, headers)
        if isinstance(data, dict):
            data = json.dumps(data)
        # print(data)
        connection.request(str("POST"), str(url), data, headers=headers)
        response = connection.getresponse()
        if response.status not in [200]:
            print(response.status, response.reason)
            if response.status == 400:
                class_ = BadRequestError
            elif response.status == 401:
                class_ = UnauthorizedError
            elif response.status == 403:
                class_ = ForbiddenError
            elif response.status == 404:
                class_ = NotFoundError
            else:
                class_ = HTTPError
            raise class_(url, response.status, response.reason,
                         response.getheaders(), None)
        data = response.read()
        if len(data) > 0 and data[0] == b"{":
            doc = json.loads(data.decode("UTF-8"))
            return doc
        return data


def get_device_name():
    try:
        return platform.node() or "Unknown Computer"
    except Exception:
        return "Unknown Computer"


class LoginTask(Task):

    def __init__(self, client, username, password):
        Task.__init__(self, "Login Task")
        self.client = client
        self.username = username
        self.password = password

    def run(self):
        self.progressed("Logging into oagd.net...")
        if not app.settings["device_id"]:
            app.settings["device_id"] = str(uuid4())
        try:
            result = self.client.auth(
                self.username, self.password, app.settings["device_id"],
                get_device_name())
        except UnauthorizedError:
            raise Task.Failure("Wrong e-mail address or password")

        app.settings["database_username"] = result["username"]
        app.settings["database_email"] = result["email"]
        app.settings["database_auth"] = result["auth_token"]
        app.settings["database_password"] = ""


class LogoutTask(Task):

    def __init__(self, client, auth_token):
        Task.__init__(self, "Logout Task")
        self.client = client
        self.auth_token = auth_token

    def run(self):
        self.progressed("Logging out from oagd.net...")
        if not app.settings["device_id"]:
            app.settings["device_id"] = str(uuid4())
        self.client.deauth(self.auth_token)

        app.settings["database_username"] = ""
        # app.settings["database_email"] = ""
        app.settings["database_auth"] = ""
        app.settings["database_password"] = ""
