# Path: esteid/authentication/types.py
# class AuthenticationResult(PredictableDict):
#     country: str
#     id_code: str
#     given_name: str
#     surname: str
#     certificate_b64: str
# 
# class SessionData(PredictableDict):
#     """
#     Wrapper for temporary data stored between authentication polling requests.
# 
#     Contains the upstream service's session ID and the hash value used to calculate the authentication signature,
#      along with a timestamp that is used to determine session validity timeout.
#     """
# 
#     timestamp: int
#     session_id: str
#     hash_value_b64: str
# 
# Path: esteid/exceptions.py
# class EsteidError(Exception):
#     """
#     A generic Esteid error.
# 
#     Provides an interface for displaying a translated message to the user.
# 
#     Note: by default, any message passed as a first positional argument is ignored, because it only
#     serves for better logging. The User Interface should display a verbose error message
#     in case of a user-caused error so that user can take action accordingly. Errors caused by service malfunction
#     are likely not informative to a user without malicious intent.
# 
#     If the default message contains {key} placeholders, the exception is supposed to be
#     raised with kwargs, these will be passed on to <translated default message>.format(**kwargs).
#     """
# 
#     status = HTTPStatus.INTERNAL_SERVER_ERROR
# 
#     default_message = _("Something went wrong")
# 
#     kwargs: dict
# 
#     def __init__(self, message=None, **kwargs):
#         super().__init__(message)
#         self.kwargs = kwargs
# 
#     def get_message(self):
#         return str(self.default_message).format(**self.kwargs)
# 
#     def get_user_error(self):
#         return {
#             "error": self.__class__.__name__,  # error code that can be bound to JS translations
#             # TODO review errors and add translations
#             "message": self.get_message(),
#         }
# 
# class SigningSessionDoesNotExist(SigningSessionError):
#     """A signing session does not exist while expected to exist"""
# 
#     default_message = _("No signing session found.")
# 
# class SigningSessionExists(SigningSessionError):
#     """A signing session already exists while not expected to exist (when initializing)"""
# 
#     default_message = _("A signing session is already in progress.")


import logging
from time import time
from typing import Dict, Optional, Type
from esteid.authentication.types import AuthenticationResult, SessionData
from esteid.exceptions import EsteidError, SigningSessionDoesNotExist, SigningSessionExists
logger = logging.getLogger(__name__)


class Authenticator:
    """
    Abstract pluggable authenticator.

    Receives initial parameters from a view (or any other caller), based on which
    it selects the concrete Authenticator subclass.

    The concrete Authenticator calls the associated services as necessary,
      to obtain the authentication data.

    The concrete Authenticator is also associated with an initial parameters type,
    a session storage type, ... - akin to DRF serializers.

    The only requirement to a concrete authenticator class is that it
    inherit from the Authenticator class and register itself
    by being imported. The registration is done by `__init_subclass__()`.

    The authenticator class' name, lowercase, without `authenticator` suffix, will be the authentication method name.
    """

    AUTHENTICATION_METHODS: Dict[str, Type["Authenticator"]] = {}

    _SESSION_KEY = f"{__name__}.session"

    # timeout in seconds, after which a fresh session can be started even if old session data is present.
    SESSION_VALIDITY_TIMEOUT = 60 * 2


# Path: esteid/authentication/types.py
# class AuthenticationResult(PredictableDict):
#     country: str
#     id_code: str
#     given_name: str
#     surname: str
#     certificate_b64: str
# 
# class SessionData(PredictableDict):
#     """
#     Wrapper for temporary data stored between authentication polling requests.
# 
#     Contains the upstream service's session ID and the hash value used to calculate the authentication signature,
#      along with a timestamp that is used to determine session validity timeout.
#     """
# 
#     timestamp: int
#     session_id: str
#     hash_value_b64: str
# 
# Path: esteid/exceptions.py
# class EsteidError(Exception):
#     """
#     A generic Esteid error.
# 
#     Provides an interface for displaying a translated message to the user.
# 
#     Note: by default, any message passed as a first positional argument is ignored, because it only
#     serves for better logging. The User Interface should display a verbose error message
#     in case of a user-caused error so that user can take action accordingly. Errors caused by service malfunction
#     are likely not informative to a user without malicious intent.
# 
#     If the default message contains {key} placeholders, the exception is supposed to be
#     raised with kwargs, these will be passed on to <translated default message>.format(**kwargs).
#     """
# 
#     status = HTTPStatus.INTERNAL_SERVER_ERROR
# 
#     default_message = _("Something went wrong")
# 
#     kwargs: dict
# 
#     def __init__(self, message=None, **kwargs):
#         super().__init__(message)
#         self.kwargs = kwargs
# 
#     def get_message(self):
#         return str(self.default_message).format(**self.kwargs)
# 
#     def get_user_error(self):
#         return {
#             "error": self.__class__.__name__,  # error code that can be bound to JS translations
#             # TODO review errors and add translations
#             "message": self.get_message(),
#         }
# 
# class SigningSessionDoesNotExist(SigningSessionError):
#     """A signing session does not exist while expected to exist"""
# 
#     default_message = _("No signing session found.")
# 
# class SigningSessionExists(SigningSessionError):
#     """A signing session already exists while not expected to exist (when initializing)"""
# 
#     default_message = _("A signing session is already in progress.")


# Path: esteid/authentication/authenticator.py
import logging
from time import time
from typing import Dict, Optional, Type
from esteid.authentication.types import AuthenticationResult, SessionData
from esteid.exceptions import EsteidError, SigningSessionDoesNotExist, SigningSessionExists

logger = logging.getLogger(__name__)


class Authenticator:
    """
    Abstract pluggable authenticator.

    Receives initial parameters from a view (or any other caller), based on which
    it selects the concrete Authenticator subclass.

    The concrete Authenticator calls the associated services as necessary,
      to obtain the authentication data.

    The concrete Authenticator is also associated with an initial parameters type,
    a session storage type, ... - akin to DRF serializers.

    The only requirement to a concrete authenticator class is that it
    inherit from the Authenticator class and register itself
    by being imported. The registration is done by `__init_subclass__()`.

    The authenticator class' name, lowercase, without `authenticator` suffix, will be the authentication method name.
    """

    AUTHENTICATION_METHODS: Dict[str, Type["Authenticator"]] = {}

    _SESSION_KEY = f"{__name__}.session"

    # timeout in seconds, after which a fresh session can be started even if old session data is present.
    SESSION_VALIDITY_TIMEOUT = 60 * 2


    session_data: SessionData