import traceback
import jwt
from typing import Optional
import requests

from requests.exceptions import ConnectionError as RequestsConnectionError, Timeout, RequestException
from urllib3.exceptions import NewConnectionError, MaxRetryError

from shared.utils import Logger, ConfigManager, TokenManager

class Authentification:
    """Contais the function to login the user."""
    def __init__(self):
        # Logger 
        self.logger = Logger.create_logger(logger_name = "Authentification", log_file_name = "authentification")

        # Helper classes instances
        self.configManagerInstance = ConfigManager()
        self.tokenManagerInstance = TokenManager()
    
    def login(self, email: str, password: str) -> bool | str:
        """Uses the credentials to log in the user.

        Args:
            email (str): The user email.
            password (str): The user password.

        Returns:
            bool | str: True if the login is successful. The error if one occurs.
        """
        try:
            accessUrl: str = self.configManagerInstance.get_value(
                section = "PROVIDER", key = self.configManagerInstance.get_value(section = "SERVICES", key = "production")
                ) + self.configManagerInstance.get_value(
                section = "LOGIN", key = "auth")
                
            accessRequest = self._request(url = accessUrl, email = email, password = password)
            
            if type(accessRequest) == str:
                return accessRequest
            
            if accessRequest.status_code == 200:
                self.tokenManagerInstance.write_tokens(tokens = accessRequest.json())
                return True
                
        except Exception as e:
            traceBack = traceback.format_exc()
            self.logger.error(
                f"[{self.__class__.__name__} - {self.login.__name__}] "
                f"Failed to login the user. \n{traceBack}")
    
    def autologin(self) -> bool:
        """Will autologin the user inside the validator using the tokens from a previous login.

        Returns:
            bool: True if the user tokens are still valid. False if both tokens are invalid.
        """
        try:
            # Gets the Auth API Url
            verifyUrl: str = self.configManagerInstance.get_value(
                section = "PROVIDER", key = self.configManagerInstance.get_value(section = "SERVICES", key = "development")
                ) + self.configManagerInstance.get_value(
                section = "LOGIN", key = "verify")
                
            # Gets the Auth API Url
            renewUrl: str = self.configManagerInstance.get_value(
                section = "PROVIDER", key = self.configManagerInstance.get_value(section = "SERVICES", key = "development")
                ) + self.configManagerInstance.get_value(
                section = "LOGIN", key = "refresh")
                
            self.tokenManagerInstance.verify_file()
            
            tokens = self.tokenManagerInstance.read_tokens()
            
            if tokens is None or tokens["access"] == '' or tokens["refresh"] == '':
                return False
            
            responseAutologin = self._request(url = verifyUrl, token_access = tokens["access"])
            
            if type(responseAutologin) == str:
                return False
            
            if responseAutologin.status_code == 200:
                return True
            
            responseRefresh = self._request(url = renewUrl, token_refresh = tokens["refresh"])
            
            if type(responseRefresh) == str:
                self.tokenManagerInstance.wipe_tokens()
                return False
            
            if responseRefresh.status_code == 200:
                new_tokens = responseRefresh.json()
                self.tokenManagerInstance.write_tokens(tokens = new_tokens)
                return True
            
        except Exception as e:
            traceBack = traceback.format_exc()
            self.logger.error(
                f"[{self.__class__.__name__} - {self.autologin.__name__}] "
                f"Failed to autologin the user. \n{traceBack}")
            
    def _request(self, url: str, email: str = None, password: str = None, token_refresh: str = None, token_access: str = None, headers: Optional[dict[str,str]] | None = None) -> requests.Response | str:
        """Function to send request to the APIs.

        Args:
            url (str): The url of the request.
            data (dict[str, str]): The data needed for the request.
            headers (Optional[dict[str, str]] | None, optional): The headers. Defaults to None.

        Returns:
            requests.Response | str: The response of the request or an error string.
        """
        try:
            if token_access is not None:
                response = requests.post(
                    url = url,
                    json = {"access" : token_access},
                    headers = headers or {},
                    timeout = 30
                )
            elif token_refresh is not None:
                response = requests.post(
                    url = url,
                    json = {"refresh" : token_refresh},
                    headers = headers or {},
                    timeout = 30
                )
            elif email and password is not None:
                response = requests.post(
                    url = url,
                    json = {"email" : email, "password" : password},
                    headers = headers or {},
                    timeout = 30
                )
            else:
                raise Exception(f"Invalid args set.")
            
            
            if response.status_code in [401, 403]:
                return "WrongCredentials"
            
            return response
        
        except NewConnectionError as e:
            # This catches DNS failures, refused connections, etc.
            self.logger.error(f"[{self.__class__.__name__}.{self._request.__name__}] No internet connection or host unreachable: {e}")
            return "ConnectionError"
        
        except MaxRetryError as e:
            # This catches DNS failures, refused connections, etc.
            self.logger.error(f"[{self.__class__.__name__}.{self._request.__name__}] No internet connection or host unreachable: {e}")
            return "ConnectionError"
        
        except RequestsConnectionError as e:
            # This catches DNS failures, refused connections, etc.
            self.logger.error(f"[{self.__class__.__name__}.{self._request.__name__}] No internet connection or host unreachable: {e}")
            return "ConnectionError"
        
        except ConnectionError as e:
            # This catches DNS failures, refused connections, etc.
            self.logger.error(f"[{self.__class__.__name__}.{self._request.__name__}] No internet connection or host unreachable: {e}")
            return "ConnectionError"
            
        except Timeout as e:
            self.logger.error(f"[{self.__class__.__name__}.{self._request.__name__}] Request timed out: {e}")
            return "ConnectionError"
            
        except RequestException as e:
            # Catches all other requests-related errors
            self.logger.error(f"[{self.__class__.__name__}.{self._request.__name__}] Request failed: {e}")
            return "ConnectionError"
            
        except Exception as e:
            self.logger.error(f"[Class '{self.__class__.__name__}' - Function '{self._request.__name__}'] An error has occurred: {e}")
            return "Error"
        
    def check_is_tester(self) -> bool:
        try:
            tokens: dict[str, str] = self.tokenManagerInstance.read_tokens()
            accessToken = tokens["access"]
            decoded = jwt.decode(accessToken, options={"verify_signature": False}, algorithms = ["HS256"])
            return decoded["is_tester"]
        except Exception as e:
            traceBack = traceback.format_exc()
            self.logger.error(
                f"[{self.__class__.__name__} - {self.check_is_tester.__name__}] "
                f"Failed to check if the user is a tester. \n{traceBack}")