from . import constants,exceptions # Constants is shared with the official SDK and the backend, and thus does not have exceptions.
import aiohttp
import json
import asyncio
from .sub import chat,acc,char

async def sendJSON(ws,data): # Internal helper function to send JSON data over WebSocket
    return await ws.send_str(json.dumps(data))

def ensureEventLoop(): # Ensure that the event loop is running
    try:
        return asyncio.get_running_loop()
    except RuntimeError:
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        print("New event loop created!")
        return loop
    
class BaseClient: # Base class for all clients
    def __init__(self,base_url="https://api.chattedrooms.com/"):
        self.base_url = base_url
        self.is_baseurl_secure = base_url.startswith("https://") # It's a secure base url.
        self.ws_baseurl = base_url.replace("http:","ws:") if not self.is_baseurl_secure else base_url.replace("https:","wss:")
        self.headers = {
            "Content-Type": "application/json",
            "User-Agent": "ChattedRooms-PythonSDK/1.0",
            "Referrer": "https://www.chattedrooms.com/"
        }
    
    def set_useragent(self,useragent):
        """
        Not recommended, please use the normal one. But works anyway.
        Allows you to set a custom user agent for your requests.

        Args:
            useragent (str): The new user agent string.
        """
        self.headers["User-Agent"] = useragent
    
    def set_referrer(self,referrer):
        """
        Recommended if you don't use the main ChattedRooms API.
        Allows you to set a custom referrer for your requests.

        Args:
           referrer (str): The new referrer string.
        """
        self.headers["Referrer"] = referrer

class AsyncClient(BaseClient):
    """
    A class to represent the ChattedRooms API client.
    This class is used to interact with the ChattedRooms API.
    It contains methods to send messages, get chat history, and more.
    """
    def __init__(self,base_url="https://api.chattedrooms.com/"):
        super().__init__(base_url=base_url)
        
        self.session = aiohttp.ClientSession()
        self.chat = chat.ChatMan(self) # Formatted just like PyCharacterAI. To make it easier to port apps from CharacterAI to ChattedRooms.
        self.account = acc.AccountMan(self) # No offense to the devs of PyCharacterAI, great library.
        self.character = char.CharMan(self) # Character management.
    
    
    async def authenticate(self,token):
        """
        Authenticate the user with the ChattedRooms API.
        This method is used to authenticate the user with the ChattedRooms API.
        Args:
            token (str): The authentication token.
        """
        self.headers["X-Authorization-CR"] = token
        try:
            self.current_user_info = await self.account.fetch_me()
        except exceptions.Unauthorized:
            self.headers["X-Authorization-CR"] = None # Remove wrong token
            raise exceptions.AuthorizationFailed("Invalid token. Maybe you have forgot to run self.authenticate()?")
        
    async def login(self,email): # Uses the next-gen login method instead of legacy login method.
        """
        Login the user with the ChattedRooms API.
        This method is used to authenticate the user with the ChattedRooms API.
        Warning: Actually sends an email to that target, do not abuse this method.
        Uses websocket.
        Args:
            email (str): The email of the user.
        """
        async with self.session.ws_connect(f"{self.ws_baseurl}api/v1/users/next-gen-login") as ws:
            await sendJSON(ws,{"email":email})
            # Wait for the server to reply with our token (user has authorized our request)
            token = None
            stopRecieving = False
            while True:
                if not stopRecieving:
                    rawdata = await ws.receive() # Wait for the server to reply with our token (user has authorized our request)
                try:
                    datatype = rawdata.type
                    print(f"Server responded to us! Data type: {datatype}")
                    if datatype == aiohttp.WSMsgType.TEXT or int(datatype) == 1:
                        data = json.loads(rawdata.data)
                    elif datatype == aiohttp.WSMsgType.BINARY:
                        data = json.loads(rawdata.data)
                    elif datatype == aiohttp.WSMsgType.CLOSED or datatype == aiohttp.WSMsgType.CLOSING or datatype == aiohttp.WSMsgType.CLOSED:
                        stopRecieving = True
                    else:
                        print(f"Error decoding JSON data from server. {rawdata}")
                except json.JSONDecodeError:
                    print(f"Error decoding JSON data from server. {rawdata}")
                except Exception:
                    print(f"Error decoding JSON data from server. {rawdata}")
                if "authkey" in data.keys():
                    token = data["authkey"]
                    break
                if "error" in data.keys():
                    raise exceptions.ServerSideAuthError(data["exception"]) # Not our fault. 
            await self.authenticate(token)
        
    async def loginlegacy(self,username,password):
        """
        Login the user with the ChattedRooms API.
        This method is used to authenticate the user with the ChattedRooms API.
        Args:
            username (str): The username of the user.
            password (str): Password
        """
        with self.session.post(f"{self.base_url}api/v1/users/legacy-login-app",data={"username":username,"password":password}) as r: 
            # This uses FormData to send the username and password in the request body.
            if r.status_code == 200:
                token = await r.text()
                await self.authenticate(token)
            else: 
                raise exceptions.AuthorizationFailed(f"Login failed with status code {r.status_code}.")