# -*- coding: utf -*-

"""Decorators that are used in Nucleus API"""

__all__ = ['basic_auth_required', 'token_auth_required']

import functools
import typing
import requests
from .exceptions import LoginRequiredException

if typing.TYPE_CHECKING:
    from .api import _NucleusAPI

NucleusFunc = typing.TypeVar('NucleusFunc', bound=typing.Callable[..., requests.Response])


def basic_auth_required(func: NucleusFunc) -> NucleusFunc:
    """
    Decorator that catches 401 and 403 requests.HTTPError and raises LoginRequiredException.

    :returns: Wrapped function.
    :raises LoginRequiredException: Exception that has to be handled and trigger basic authorization process.
    """
    @functools.wraps(func)
    def check_auth(self: '_NucleusAPI', *args: typing.Any, **kwargs: typing.Any) -> requests.Response:
        try:
            return func(self, *args, **kwargs)
        except requests.HTTPError as err:
            response = err.response

            if response is None or response.status_code in (401, 403):
                raise LoginRequiredException(
                    "Client Error: Required basic auth using username/password.",
                    response=response,
                ) from err

            raise

    return typing.cast(NucleusFunc, check_auth)


def token_auth_required(func: NucleusFunc) -> NucleusFunc:
    """
    Decorator that catches 401 and 403 requests.HTTPError and raises LoginRequiredException.

    :returns: Wrapped function.
    :raises requests.HTTPError: Raise any requests.HTTPError if status_code not in [401, 403].
    """
    @functools.wraps(func)
    def check_auth(self: '_NucleusAPI', *args: typing.Any, **kwargs: typing.Any) -> requests.Response:
        self.refresh_token_if_required()

        try:
            return func(self, *args, **kwargs)
        except requests.HTTPError as err:
            response: requests.Response = err.response

            if response.status_code in (401, 403):
                self.refresh_token()
                return func(self, *args, **kwargs)

            raise

    return typing.cast(NucleusFunc, check_auth)
