import functools
from typing import Any, Callable, cast, Optional, Type, TYPE_CHECKING, TypeVar, Union


class GitlabError(Exception):
    def __init__(
        self,
        error_message: Union[str, bytes] = "",
        response_code: Optional[int] = None,
        response_body: Optional[bytes] = None,
    ) -> None:

        Exception.__init__(self, error_message)
        # Http status code
        self.response_code = response_code
        # Full http response
        self.response_body = response_body
        # Parsed error message from gitlab
        try:
            # if we receive str/bytes we try to convert to unicode/str to have
            # consistent message types (see #616)
            if TYPE_CHECKING:
                assert isinstance(error_message, bytes)
            self.error_message = error_message.decode()
        except Exception:
            if TYPE_CHECKING:
                assert isinstance(error_message, str)
            self.error_message = error_message

    def __str__(self) -> str:
        if self.response_code is not None:
            return f"{self.response_code}: {self.error_message}"
        return f"{self.error_message}"


class GitlabAuthenticationError(GitlabError):
    pass


class RedirectError(GitlabError):
    pass


class GitlabParsingError(GitlabError):
    pass


class GitlabCiLintError(GitlabError):
    pass


class GitlabConnectionError(GitlabError):
    pass


class GitlabOperationError(GitlabError):
    pass


class GitlabHttpError(GitlabError):
    pass


class GitlabListError(GitlabOperationError):
    pass


class GitlabGetError(GitlabOperationError):
    pass


class GitlabHeadError(GitlabOperationError):
    pass


class GitlabCreateError(GitlabOperationError):
    pass


class GitlabUpdateError(GitlabOperationError):
    pass


class GitlabDeleteError(GitlabOperationError):
    pass


class GitlabSetError(GitlabOperationError):
    pass


class GitlabProtectError(GitlabOperationError):
    pass


class GitlabTransferProjectError(GitlabOperationError):
    pass


class GitlabGroupTransferError(GitlabOperationError):
    pass


class GitlabProjectDeployKeyError(GitlabOperationError):
    pass


class GitlabPromoteError(GitlabOperationError):
    pass


class GitlabCancelError(GitlabOperationError):
    pass


class GitlabPipelineCancelError(GitlabCancelError):
    pass


class GitlabRetryError(GitlabOperationError):
    pass


class GitlabBuildCancelError(GitlabCancelError):
    pass


class GitlabBuildRetryError(GitlabRetryError):
    pass


class GitlabBuildPlayError(GitlabRetryError):
    pass


class GitlabBuildEraseError(GitlabRetryError):
    pass


class GitlabJobCancelError(GitlabCancelError):
    pass


class GitlabJobRetryError(GitlabRetryError):
    pass


class GitlabJobPlayError(GitlabRetryError):
    pass


class GitlabJobEraseError(GitlabRetryError):
    pass


class GitlabPipelinePlayError(GitlabRetryError):
    pass


class GitlabPipelineRetryError(GitlabRetryError):
    pass


class GitlabBlockError(GitlabOperationError):
    pass


class GitlabUnblockError(GitlabOperationError):
    pass


class GitlabDeactivateError(GitlabOperationError):
    pass


class GitlabActivateError(GitlabOperationError):
    pass


class GitlabBanError(GitlabOperationError):
    pass


class GitlabUnbanError(GitlabOperationError):
    pass


class GitlabSubscribeError(GitlabOperationError):
    pass


class GitlabUnsubscribeError(GitlabOperationError):
    pass


class GitlabMRForbiddenError(GitlabOperationError):
    pass


class GitlabMRApprovalError(GitlabOperationError):
    pass


class GitlabMRRebaseError(GitlabOperationError):
    pass


class GitlabMRResetApprovalError(GitlabOperationError):
    pass


class GitlabMRClosedError(GitlabOperationError):
    pass


class GitlabMROnBuildSuccessError(GitlabOperationError):
    pass


class GitlabTodoError(GitlabOperationError):
    pass


class GitlabTopicMergeError(GitlabOperationError):
    pass


class GitlabTimeTrackingError(GitlabOperationError):
    pass


class GitlabUploadError(GitlabOperationError):
    pass


class GitlabAttachFileError(GitlabOperationError):
    pass


class GitlabImportError(GitlabOperationError):
    pass


class GitlabInvitationError(GitlabOperationError):
    pass


class GitlabCherryPickError(GitlabOperationError):
    pass


class GitlabHousekeepingError(GitlabOperationError):
    pass


class GitlabOwnershipError(GitlabOperationError):
    pass


class GitlabSearchError(GitlabOperationError):
    pass


class GitlabStopError(GitlabOperationError):
    pass


class GitlabMarkdownError(GitlabOperationError):
    pass


class GitlabVerifyError(GitlabOperationError):
    pass


class GitlabRenderError(GitlabOperationError):
    pass


class GitlabRepairError(GitlabOperationError):
    pass


class GitlabRestoreError(GitlabOperationError):
    pass


class GitlabRevertError(GitlabOperationError):
    pass


class GitlabLicenseError(GitlabOperationError):
    pass


class GitlabFollowError(GitlabOperationError):
    pass


class GitlabUnfollowError(GitlabOperationError):
    pass


class GitlabUserApproveError(GitlabOperationError):
    pass


class GitlabUserRejectError(GitlabOperationError):
    pass


class GitlabDeploymentApprovalError(GitlabOperationError):
    pass


# For an explanation of how these type-hints work see:
# https://mypy.readthedocs.io/en/stable/generics.html#declaring-decorators
#
# The goal here is that functions which get decorated will retain their types.
__F = TypeVar("__F", bound=Callable[..., Any])


def on_http_error(error: Type[Exception]) -> Callable[[__F], __F]:
    """Manage GitlabHttpError exceptions.

    This decorator function can be used to catch GitlabHttpError exceptions
    raise specialized exceptions instead.

    Args:
        The exception type to raise -- must inherit from GitlabError
    """

    def wrap(f: __F) -> __F:
        @functools.wraps(f)
        def wrapped_f(*args: Any, **kwargs: Any) -> Any:
            try:
                return f(*args, **kwargs)
            except GitlabHttpError as e:
                raise error(e.error_message, e.response_code, e.response_body) from e

        return cast(__F, wrapped_f)

    return wrap


__all__ = [name for name in dir() if name.endswith("Error")]
