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 = ""
    cloudApiUrl = ""
    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']

        BigApi.cloudApiUrl = config['BIGSCREEN_CLOUD_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 handleExpiredToken(res):
        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 checkAccessTokenStatus():
        if BigApi.accessToken == "":
            return False

        res = requests.get(f"{BigApi.authApiUrl}/auth/verify", headers=BigApi.getAdminHeaders())
        if (res.status_code == 200):
            return True
        
        return BigApi.handleExpiredToken(res)


    @staticmethod
    def apiGet(url):
        BigApi.checkAccessTokenStatus()
        r = requests.get(f"{BigApi.authApiUrl}{url}", headers=BigApi.getAdminHeaders())
        if r.status_code == 200:
            return r.json()
        elif r.status_code == 401:
            if (BigApi.handleExpiredToken(r)):
                return BigApi.apiGet(url)
            else:
                raise BigApiError("Access token expired and could not be renewed")
        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()
        elif r.status_code == 401:
            if (BigApi.handleExpiredToken(r)):
                return BigApi.apiPost(url, payload)
            else:
                raise BigApiError("Access token expired and could not be renewed")
        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 apiPut(url, payload):
        BigApi.checkAccessTokenStatus()
        r = requests.put(f"{BigApi.authApiUrl}{url}", headers=BigApi.getAdminHeaders(), json=payload)
        if r.status_code == 200:
            return r.json()
        elif r.status_code == 401:
            if (BigApi.handleExpiredToken(r)):
                return BigApi.apiPut(url, payload)
            else:
                raise BigApiError("Access token expired and could not be renewed")
        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()
        elif r.status_code == 401:
            if (BigApi.handleExpiredToken(r)):
                return BigApi.adminGet(url)
            else:
                raise BigApiError("Access token expired and could not be renewed")
        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()
        elif r.status_code == 401:
            if (BigApi.handleExpiredToken(r)):
                return BigApi.adminPost(url, payload)
            else:
                raise BigApiError("Access token expired and could not be renewed")
        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:
            try:
                return r.json()
            except:
                return r.text
        elif r.status_code == 401:
            if (BigApi.handleExpiredToken(r)):
                return BigApi.adminPut(url, payload)
            else:
                raise BigApiError("Access token expired and could not be renewed")
        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 adminDelete(url):
        BigApi.checkAccessTokenStatus()
        r = requests.delete(f"{BigApi.adminApiUrl}{url}", headers=BigApi.getAdminHeaders())
        if r.status_code == 200:
            return r.json()
        elif r.status_code == 401:
            if (BigApi.handleExpiredToken(r)):
                return BigApi.adminDelete(url)
            else:
                raise BigApiError("Access token expired and could not be renewed")
        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 cloudGet(url):
        BigApi.checkAccessTokenStatus()
        r = requests.get(f"{BigApi.cloudApiUrl}{url}", headers=BigApi.getAdminHeaders())
        if r.status_code == 200:
            return r.json()
        elif r.status_code == 401:
            if (BigApi.handleExpiredToken(r)):
                return BigApi.cloudGet(url)
            else:
                raise BigApiError("Access token expired and could not be renewed")
        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})"