from http.client import responses
import requests

USER_AGENT = "Circle K B2C app"
GAME_API_URL = "https://api.mgame.nu/server-2022-summer-eu-circlek/server.php"
COUNTRY_CODE = "dk" # Only tested with Denmark
OTLINK_RETURN_URI = "fb2050160281848445" # Not sure what this means
APP_CLIENT_ID = "fb0adc92-ee2d-3a79-8636-afa044ae0ac6" # Client Id for the CircleK app
SKRAB_CLIENT_ID = "e2df6fa9-01a2-4714-860f-e3b7246ddd87" # Client ID for CircleK Skrab service

# Sprites are stored on the server like this: https://skrab.circlek.one/lib/gfx/sprite/<SPRITE_ID>.png

# Circle K Skrab API Class
class SkrabClient:
    def __init__(
        self,
        refresh_token=None
    ):
        self.refresh_token = refresh_token

        self.headers = {
            "User-Agent": USER_AGENT,
            "bm-country": COUNTRY_CODE,
        }
        self.session = requests.Session()


    def _Refresh(self):
        response = self.session.post("https://id.circlekeurope.com/api/v2/oauth/token/refresh", headers=self.headers, params={"client_id": APP_CLIENT_ID, "grant_type": "refresh_token", "refresh_token": self.refresh_token})

        if response.status_code == 200:
            self.access_token = response.json()["access_token"]
            self.headers["Authorization"] = "Bearer " + self.access_token
        else:
            raise Exception("Failed to refresh access token", response.status_code)


    def Login(self, email, password):
        # Attempt OAuth with email and password
        response = self.session.post("https://id.circlekeurope.com/api/v3/oauth/authorize/password", headers=self.headers, data={"client_id": APP_CLIENT_ID, "grant_type": "password", "password": password, "username": email, "scope": "USER", "refuseScopesFromExistingGrant": True, "deviceName": "Totally an iPhone"})

        if response.status_code != 200:
            raise Exception("Failed to login", response.status_code)
        
        # Check if mfa is required
        if "mfaRequired" in response.json() and response.json()["mfaRequired"]:
            return MFASession(response.json()["mfaSessionId"], self)
        else:
            self.access_token = response.json()["accessToken"]
            self.device_token = response.json()["deviceToken"]
            self.headers["Authorization"] = "Bearer " + self.skrabClient.access_token
            self.refresh_token = response.json()["refreshToken"]
            return response.json()


    def GetGameLink(self):
        # Make sure we have an access token, if not, refresh it
        if not hasattr(self, "access_token"):
            self._Refresh()

        # Get the weird ot-link thing idk
        response = self.session.post("https://id.circlekeurope.com/api/v2/ot-link", headers=self.headers, json={"screenName": "AUTHORIZE", "returnUri": OTLINK_RETURN_URI})
        if response.status_code == 401:
            self._Refresh()
            return self.GetGameLink()
        
        self.ot_link = response.json()["otLink"]

        # Request a redirect with ot-link as cktoken
        response = self.session.get("https://redirect.circlek.one/app/dk/?cktoken=" + self.ot_link, headers=self.headers)
        return response.url


    def _GameRequest(self, route, data):
        response = self.session.post(GAME_API_URL, headers=self.headers, json={"route": route, "data": data})
        if response.status_code != 200:
            raise Exception("Failed to request game api", response.status_code)
        else:
            return response.json()


    def _GameLogin(self):
        # OT Link is one time use only, we must request a new one every login
        self.GetGameLink()

        # Send weird request to ot-link stuff again and let it redirect us a million times
        response = self.session.get(self.ot_link, headers=self.headers, params={"response_type": "code", "client_id": SKRAB_CLIENT_ID, "scope": "USER", "state": COUNTRY_CODE, "redirect_uri": "https://api.mgame.nu/circlek-ckid-handle-login/"})
        
        # Get ckcode from response url
        self.ckcode = response.url.split("?")[1].split("&")[0].split("=")[1]

        # Get game token using ckcode
        response = self._GameRequest("ckid_user", {"code": self.ckcode})
        self.game_token = response["access"]

        # Login with game token and get session token
        response = self._GameRequest("login", {"access": self.game_token, "permission_primary": "false", "pushMessageToken": "false"}) # Idk what permission_primary is
        self.session_token = response["session"]

        return response


    def Scratch(self, recursive=True, retryOnError=True):
        # Make sure we have a session token
        if not hasattr(self, "session_token"):
            self._GameLogin()

        # Attempt scratch
        scratchResult = self._GameRequest("game_start", {"session": self.session_token, "type": "scratch", "name": "default"})
        
        # Try again if error occured (Mabye we need to login again)
        if 'error' in scratchResult:
            if retryOnError:
                self._GameLogin()
                return self.Scratch(recursive=recursive, retryOnError=False) # Don't end up in a loop of retries
            else:
                raise Exception("Failed to scratch", scratchResult["error"])
        
        # scratchResult is a dictionary with the following keys: completed (bool), game_id (string), items (list), prize (string), time (timestamp), turn (dict with current and max tries) 
        
        # Make sure game is not completed
        if 'completed' not in scratchResult:
            # Tell the server that we've scratched
            self._GameRequest("game_end", {"session": self.session_token, "game_id": scratchResult["game_id"], "playtime": 10, "type": "scratch", "name": "default"})
            
            # If the user had more scratches left and recursive is true, then try again
            if scratchResult["turn"]["current"] < scratchResult["turn"]["max"] and recursive:
                return self.Scratch()

        return scratchResult

class MFASession:
    def __init__(self, mfaSessionId, skrabClient):
        self.mfa_session_id = mfaSessionId
        self.skrabClient = skrabClient


    def Send(self):
        response = self.skrabClient.session.post("https://id.circlekeurope.com/api/v3/oauth/authorize/password/mfa/send", headers=self.skrabClient.headers, json={"mfaSessionId": self.mfa_session_id, "languageCode": "en"})
        if response.status_code != 200:
            raise Exception("Failed to send mfa code", response.status_code)
        else:
            return response.json()


    def Verify(self, code):
        response = self.skrabClient.session.post("https://id.circlekeurope.com/api/v3/oauth/authorize/password/mfa/verify", headers=self.skrabClient.headers, json={"client-id": APP_CLIENT_ID, "mfaSessionId": self.mfa_session_id, "markDeviceAsTrusted": True, "otp": code, "deviceName": "Totally an iPhone"})
        if response.status_code != 200:
            raise Exception("Failed to verify mfa code", response.status_code)
        else:
            if response.json()["tcAccepted"]:
                self.skrabClient.access_token = response.json()["access_token"]
                self.skrabClient.device_token = response.json()["deviceToken"] # Not sure if this is needed
                self.skrabClient.headers["Authorization"] = "Bearer " + self.skrabClient.access_token
                self.skrabClient.refresh_token = response.json()["refresh_token"]
                return response.json()
            else:
                raise Exception("Failed to verify mfa code. Most likely wrong code", response.status_code)
    
from http.client import responses
import requests

USER_AGENT = "Circle K B2C app"
GAME_API_URL = "https://api.mgame.nu/server-2022-summer-eu-circlek/server.php"
COUNTRY_CODE = "dk" # Only tested with Denmark
OTLINK_RETURN_URI = "fb2050160281848445" # Not sure what this means
APP_CLIENT_ID = "fb0adc92-ee2d-3a79-8636-afa044ae0ac6" # Client Id for the CircleK app
SKRAB_CLIENT_ID = "e2df6fa9-01a2-4714-860f-e3b7246ddd87" # Client ID for CircleK Skrab service

# Sprites are stored on the server like this: https://skrab.circlek.one/lib/gfx/sprite/<SPRITE_ID>.png

# Circle K Skrab API Class
class SkrabClient:
    def __init__(
        self,
        refresh_token=None
    ):
        self.refresh_token = refresh_token

        self.headers = {
            "User-Agent": USER_AGENT,
            "bm-country": COUNTRY_CODE,
        }
        self.session = requests.Session()


    def _Refresh(self):
        response = self.session.post("https://id.circlekeurope.com/api/v2/oauth/token/refresh", headers=self.headers, params={"client_id": APP_CLIENT_ID, "grant_type": "refresh_token", "refresh_token": self.refresh_token})

        if response.status_code == 200:
            self.access_token = response.json()["access_token"]
            self.headers["Authorization"] = "Bearer " + self.access_token
        else:
            raise Exception("Failed to refresh access token", response.status_code)


    def Login(self, email, password):
        # Attempt OAuth with email and password
        response = self.session.post("https://id.circlekeurope.com/api/v3/oauth/authorize/password", headers=self.headers, data={"client_id": APP_CLIENT_ID, "grant_type": "password", "password": password, "username": email, "scope": "USER", "refuseScopesFromExistingGrant": True, "deviceName": "Totally an iPhone"})

        if response.status_code != 200:
            raise Exception("Failed to login", response.status_code)
        
        # Check if mfa is required
        if "mfaRequired" in response.json() and response.json()["mfaRequired"]:
            return MFASession(response.json()["mfaSessionId"], self)
        else:
            self.access_token = response.json()["accessToken"]
            self.device_token = response.json()["deviceToken"]
            self.headers["Authorization"] = "Bearer " + self.skrabClient.access_token
            self.refresh_token = response.json()["refreshToken"]
            return response.json()


    def GetGameLink(self):
        # Make sure we have an access token, if not, refresh it
        if not hasattr(self, "access_token"):
            self._Refresh()

        # Get the weird ot-link thing idk
        response = self.session.post("https://id.circlekeurope.com/api/v2/ot-link", headers=self.headers, json={"screenName": "AUTHORIZE", "returnUri": OTLINK_RETURN_URI})
        if response.status_code == 401:
            self._Refresh()
            return self.GetGameLink()
        
        self.ot_link = response.json()["otLink"]

        # Request a redirect with ot-link as cktoken
        response = self.session.get("https://redirect.circlek.one/app/dk/?cktoken=" + self.ot_link, headers=self.headers)
        return response.url


    def _GameRequest(self, route, data):
        response = self.session.post(GAME_API_URL, headers=self.headers, json={"route": route, "data": data})
        if response.status_code != 200:
            raise Exception("Failed to request game api", response.status_code)
        else:
            return response.json()


    def _GameLogin(self):
        # OT Link is one time use only, we must request a new one every login
        self.GetGameLink()

        # Send weird request to ot-link stuff again and let it redirect us a million times
        response = self.session.get(self.ot_link, headers=self.headers, params={"response_type": "code", "client_id": SKRAB_CLIENT_ID, "scope": "USER", "state": COUNTRY_CODE, "redirect_uri": "https://api.mgame.nu/circlek-ckid-handle-login/"})
        
        # Get ckcode from response url
        self.ckcode = response.url.split("?")[1].split("&")[0].split("=")[1]

        # Get game token using ckcode
        response = self._GameRequest("ckid_user", {"code": self.ckcode})
        self.game_token = response["access"]

        # Login with game token and get session token
        response = self._GameRequest("login", {"access": self.game_token, "permission_primary": "false", "pushMessageToken": "false"}) # Idk what permission_primary is
        self.session_token = response["session"]

        return response


    def Scratch(self, recursive=True, retryOnError=True):
        # Make sure we have a session token
        if not hasattr(self, "session_token"):
            self._GameLogin()

        # Attempt scratch
        scratchResult = self._GameRequest("game_start", {"session": self.session_token, "type": "scratch", "name": "default"})
        
        # Try again if error occured (Mabye we need to login again)
        if 'error' in scratchResult:
            if retryOnError:
                self._GameLogin()
                return self.Scratch(recursive=recursive, retryOnError=False) # Don't end up in a loop of retries
            else:
                raise Exception("Failed to scratch", scratchResult["error"])
        
        # scratchResult is a dictionary with the following keys: completed (bool), game_id (string), items (list), prize (string), time (timestamp), turn (dict with current and max tries) 
        
        # Make sure game is not completed
        if 'completed' not in scratchResult:
            # Tell the server that we've scratched
            self._GameRequest("game_end", {"session": self.session_token, "game_id": scratchResult["game_id"], "playtime": 10, "type": "scratch", "name": "default"})
            
            # If the user had more scratches left and recursive is true, then try again
            if scratchResult["turn"]["current"] < scratchResult["turn"]["max"] and recursive:
                return self.Scratch()

        return scratchResult

class MFASession:
    def __init__(self, mfaSessionId, skrabClient):
        self.mfa_session_id = mfaSessionId
        self.skrabClient = skrabClient


    def Send(self):
        response = self.skrabClient.session.post("https://id.circlekeurope.com/api/v3/oauth/authorize/password/mfa/send", headers=self.skrabClient.headers, json={"mfaSessionId": self.mfa_session_id, "languageCode": "en"})
        if response.status_code != 200:
            raise Exception("Failed to send mfa code", response.status_code)
        else:
            return response.json()


    def Verify(self, code):
        response = self.skrabClient.session.post("https://id.circlekeurope.com/api/v3/oauth/authorize/password/mfa/verify", headers=self.skrabClient.headers, json={"client-id": APP_CLIENT_ID, "mfaSessionId": self.mfa_session_id, "markDeviceAsTrusted": True, "otp": code, "deviceName": "Totally an iPhone"})
        if response.status_code != 200:
            raise Exception("Failed to verify mfa code", response.status_code)
        else:
            if response.json()["tcAccepted"]:
                self.skrabClient.access_token = response.json()["access_token"]
                self.skrabClient.device_token = response.json()["deviceToken"] # Not sure if this is needed
                self.skrabClient.headers["Authorization"] = "Bearer " + self.skrabClient.access_token
                self.skrabClient.refresh_token = response.json()["refresh_token"]
                return response.json()
            else:
                raise Exception("Failed to verify mfa code. Most likely wrong code", response.status_code)
    
# Only tested with Denmark # Not sure what this means # Client Id for the CircleK app # Client ID for CircleK Skrab service # Sprites are stored on the server like this: https://skrab.circlek.one/lib/gfx/sprite/<SPRITE_ID>.png # Circle K Skrab API Class # Attempt OAuth with email and password # Check if mfa is required # Make sure we have an access token, if not, refresh it # Get the weird ot-link thing idk # Request a redirect with ot-link as cktoken # OT Link is one time use only, we must request a new one every login # Send weird request to ot-link stuff again and let it redirect us a million times # Get ckcode from response url # Get game token using ckcode # Login with game token and get session token # Idk what permission_primary is # Make sure we have a session token # Attempt scratch # Try again if error occured (Mabye we need to login again) # Don't end up in a loop of retries # scratchResult is a dictionary with the following keys: completed (bool), game_id (string), items (list), prize (string), time (timestamp), turn (dict with current and max tries) # Make sure game is not completed # Tell the server that we've scratched # If the user had more scratches left and recursive is true, then try again # Not sure if this is needed