from dotenv import dotenv_values
import requests
from enum import Enum
import json
import base64


class AuthenticationErrorCode(Enum):
    Unknown = 0
    BadAPIKey = 1
    MissingAuthenticationHeaders = 2
    RefreshTokenNeedsRenewal = 3
    AccessTokenNeedsRenewal = 4
    RenewalNonceExpired = 5
    TokenParserError = 6
    InvalidLoginCredentials = 7
    BannedAccount = 8
    BannedIP = 9
    BannedDevice = 10
    RequiresVerifiedEmailAddress = 11


class BigApiError(Exception):
    pass


class BigApi:
    refreshToken = ""
    accessToken = ""
    authApiUrl = ""
    adminApiUrl = ""
    apiBearerToken = ""
    adminEmail = ""
    adminPassword = ""
    accessToken = ""
    refreshToken = ""

    @staticmethod
    def init(pathToEnvironmentFile):
        config = dotenv_values(pathToEnvironmentFile)

        # The auth server is used for authentication and account management
        BigApi.authApiUrl = config['BIGSCREEN_API_URL']

        # The admin server has the fabricator endpoints
        BigApi.adminApiUrl = config['BIGSCREEN_ADMIN_API_URL']

        # Each http request needs a bearer token. This is a revokable token which is assigned to each external application that
        # uses the Bigscreen APIs (e.g. this script is an external application).
        BigApi.apiBearerToken = config['BIGSCREEN_API_KEY']

        # Finally, we need an account.  The account is necessary for us to identify who is using the various APIs. The account
        # must have access to the "Fabricator" endpoints to work.
        BigApi.adminEmail = config['ADMIN_ACCOUNT_EMAIL']
        BigApi.adminPassword = config['ADMIN_ACCOUNT_PASSWORD']

    @staticmethod
    def getAuthHeaders():
        return {
            "Content-Type": "application/json",
            "Authorization": f"Bearer {BigApi.apiBearerToken}"
        }

    @staticmethod
    def getAdminHeaders():
        return {
            "Content-Type": "application/json",
            "Authorization": f"Bearer {BigApi.apiBearerToken}",
            "x-access-token": BigApi.accessToken
        }

    @staticmethod
    def login(email, password):
        payload = {
            "email": email,
            "password": password
        }
        print(BigApi.authApiUrl)
        r = requests.post(f"{BigApi.authApiUrl}/login",
                          headers=BigApi.getAuthHeaders(), json=payload)
        print(r.status_code)
        BigApi.refreshToken = r.headers["x-refresh-token"]
        BigApi.accessToken = r.headers["x-access-token"]
        assert r.status_code == 200, f"status code should be 200 (actual: {r.status_code} {r.text})"
        r = requests.get(f"{BigApi.authApiUrl}/verify",
                         headers=BigApi.getAdminHeaders())
        assert r.status_code == 200, f"status code should be 200 (actual: {r.status_code} {r.text})"

    @staticmethod
    def adminLogin():
        payload = {
            "email": BigApi.adminEmail,
            "password": BigApi.adminPassword
        }

        r = requests.post(f"{BigApi.authApiUrl}/login",
                          headers=BigApi.getAuthHeaders(), json=payload)
        print(r.status_code)
        BigApi.refreshToken = r.headers["x-refresh-token"]
        BigApi.accessToken = r.headers["x-access-token"]
        r = requests.get(f"{BigApi.authApiUrl}/verify",
                         headers=BigApi.getAdminHeaders())

    @staticmethod
    def getSystemInfo():
        ip = "unknown"
        try:
            res = requests.get(f"{BigApi.authApiUrl}/auth/whoami",
                               headers=BigApi.getAuthHeaders())
            ip = res.json()["ip"]
        except:
            pass
        systemInfo = {
            "ip": ip,
            "deviceUniqueIdentifier": f"python_notebook_{ip}",
            "deviceName": "python_notebook"
        }
        return base64.b64encode(json.dumps(systemInfo).encode("utf-8")).decode(
            "utf-8")

    @staticmethod
    def renewAccessToken(nonce):
        if (BigApi.refreshToken == ""):
            raise BigApiError("Refresh token was not set. Login required.")

        headers = BigApi.getAuthHeaders()
        headers["x-bigscreen-nonce"] = nonce
        headers["x-refresh-token"] = BigApi.refreshToken
        headers["x-bigscreen-system-info"] = BigApi.getSystemInfo()
        res = requests.get(f"{BigApi.authApiUrl}/auth/renew", headers=headers)

        if (res.status_code == 200):
            BigApi.refreshToken = res.headers["x-refresh-token"]
            BigApi.accessToken = res.headers["x-access-token"]
            print(
                "SUCCESS THE TOKEN WAS RENEWED AND THE NEXT REQUEST SHOULD FUCKING WORK.")
        else:
            print(res.json())
            print("Error renewing the token. Weird.")

    @staticmethod
    def checkAccessTokenStatus():
        if BigApi.accessToken == "":
            return False

        res = requests.get(f"{BigApi.authApiUrl}/auth/verify",
                           headers=BigApi.getAdminHeaders())
        if (res.status_code == 200):
            return True

        if (res.status_code == 401):
            errorResponse = res.json()
            if (errorResponse["code"]):
                authenticationErrorCode = errorResponse["code"]
                if (
                        authenticationErrorCode == AuthenticationErrorCode.AccessTokenNeedsRenewal):
                    nonce = res.headers["x-bigscreen-nonce"]
                    if (nonce):
                        renewalResult = BigApi.renewAccessToken(nonce)
                        return renewalResult
                    else:
                        raise BigApiError("Nonce not found in response")
        else:
            print(res.json())
        return False

    @staticmethod
    def apiGet(url):
        BigApi.checkAccessTokenStatus()
        r = requests.get(f"{BigApi.authApiUrl}{url}",
                         headers=BigApi.getAdminHeaders())
        if r.status_code == 200:
            return r.json()
        else:
            print(f"status code should be 200 (actual: {r.status_code})")
            assert r.status_code == 200, f"status code should be 200 (actual: {r.status_code})"

    @staticmethod
    def apiPost(url, payload):
        BigApi.checkAccessTokenStatus()
        r = requests.post(f"{BigApi.authApiUrl}{url}",
                          headers=BigApi.getAdminHeaders(), json=payload)
        if r.status_code == 200:
            return r.json()
        else:
            print(f"status code should be 200 (actual: {r.status_code})")
            assert r.status_code == 200, f"status code should be 200 (actual: {r.status_code})"

    @staticmethod
    def adminGet(url):
        BigApi.checkAccessTokenStatus()
        r = requests.get(f"{BigApi.adminApiUrl}{url}",
                         headers=BigApi.getAdminHeaders())
        if r.status_code == 200:
            return r.json()
        else:
            if (r.status_code == 422):
                return r.json()
            print(f"status code should be 200 (actual: {r.status_code})")
            assert r.status_code == 200, f"status code should be 200 (actual: {r.status_code})"

    @staticmethod
    def adminPost(url, payload):
        BigApi.checkAccessTokenStatus()
        r = requests.post(f"{BigApi.adminApiUrl}{url}",
                          headers=BigApi.getAdminHeaders(), json=payload)
        if r.status_code == 200:
            return r.json()
        else:
            print(f"status code should be 200 (actual: {r.status_code})")
            assert r.status_code == 200, f"status code should be 200 (actual: {r.status_code})"

    @staticmethod
    def adminPut(url, payload):
        BigApi.checkAccessTokenStatus()
        r = requests.put(f"{BigApi.adminApiUrl}{url}",
                         headers=BigApi.getAdminHeaders(), json=payload)
        if r.status_code == 200:
            return r.json()
        else:
            print(f"status code should be 200 (actual: {r.status_code})")
            assert r.status_code == 200, f"status code should be 200 (actual: {r.status_code})"