# -*- coding: utf-8 -*-

"""Decorators that are used in Cloud 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 _CloudAPI


CloudFunc = typing.TypeVar('CloudFunc', bound=typing.Callable[..., typing.Any])


def basic_auth_required(func: CloudFunc) -> CloudFunc:
    """
    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: '_CloudAPI', *args: typing.Any, **kwargs: typing.Any) -> typing.Any:
        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(CloudFunc, check_auth)


def token_auth_required(func: CloudFunc) -> CloudFunc:
    """
    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: '_CloudAPI', *args: typing.Any, **kwargs: typing.Any) -> typing.Any:
        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(CloudFunc, check_auth)
