"""
This module contains all the necessary functionality for the authentication of a request to the pubtrack app.
"""
from typing import Dict, Any
from abc import ABC, abstractmethod


class AbstractAuthentication(ABC):
    """
    This is the abstract base class for any "Authentication" object.

    **Background**

    The pubtrack application is a web application, which is running a REST API. So data transfer to and from this
    application is managed by HTTP requests. But only registered users are supposed to be able to make successful
    requests. Thus the need for an authentication. Roughly speaking that is something you can add to the request to
    ensure, that you are indeed a registered user.

    **Details**

    Each subclass of this base class is supposed to represent and implement the functionality to add such a
    authentication to a request! For that this class is designed to be a callable. The input parameters for this
    call are the the keyword arguments for the http request and the return will also be dict with mostly the same
    keyword arguments, except with additional authentication arguments added on top. To actually implement this
    the child classes will have to implement the "update_request_kwargs" abstract method.

    **Example**

    The following example illustrates how a Authentication object is used theoretically.

    .. code-block::python

        # "Authentication" is a sub class of "AbstractAuthentication" and implements
        # the "update_request_kwargs" method appropriately
        authenticate = Authentication(config)
        request_kwargs = {}
        authenticated_request_kwargs = authenticate(request_kwargs)

    """

    def __init__(self, config: Any):
        self.config = config

    def __call__(self, kwargs: dict) -> Dict[str, Any]:
        """
        This method will accept the kwargs dict of a http request towards an endpoint and then add additional arguments
        to it, which authenticate this request properly.

        :param kwargs: The keyword arguments for a http request to a pubtrack endpoint.
        :return: The authenticated keyword arguments dict for that request
        """
        return self.update_request_kwargs(kwargs)

    # TO BE IMPLEMENTED
    # -----------------

    @abstractmethod
    def update_request_kwargs(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:
        """
        This method has to be implemented for the subclass!

        It is supposed to add appropriate authentication details to the dict of keyword arguments.

        :param kwargs: The keyword arguments for a http request to a pubtrack endpoint.
        :return: The authenticated keyword arguments dict for that request
        """
        raise NotImplementedError


class TokenAuthentication(AbstractAuthentication):
    """
    Subclass of AbstractAuthentication, which implements the token authentication for the pubtrack application

    **Background**

    With token authentication, special "token" strings are generated by the web application and distributed to the
    users. The users then have to add this token string to each request to identify themselves.

    **Details**

    This class will read the token of the user from the given "config" object and add it as a keyword argument to the
    request
    """

    # IMPLEMENT "AbstractAuthentication"
    # ----------------------------------

    def update_request_kwargs(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:
        token = self.config.get_token()
        headers = self.authentication_headers(token)

        kwargs.update({'headers': headers})
        return kwargs

    # HELPER METHODS
    # --------------

    @classmethod
    def authentication_headers(cls, token: str) -> Dict[str, str]:
        """
        This method will generate the necessary keyword key-value structure, which is needed for token authentication
        with the pubtrack application, given the actual "token" of the user.

        :param token: The token which is to be used for the authentication.
        :return:
        """
        return {
            'Authorization': 'TOKEN {}'.format(token)
        }
