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


# Export manually to keep mypy happy
__all__ = [
    "GitlabActivateError",
    "GitlabAttachFileError",
    "GitlabAuthenticationError",
    "GitlabBanError",
    "GitlabBlockError",
    "GitlabBuildCancelError",
    "GitlabBuildEraseError",
    "GitlabBuildPlayError",
    "GitlabBuildRetryError",
    "GitlabCancelError",
    "GitlabCherryPickError",
    "GitlabCiLintError",
    "GitlabConnectionError",
    "GitlabCreateError",
    "GitlabDeactivateError",
    "GitlabDeleteError",
    "GitlabDeploymentApprovalError",
    "GitlabError",
    "GitlabFollowError",
    "GitlabGetError",
    "GitlabGroupTransferError",
    "GitlabHeadError",
    "GitlabHousekeepingError",
    "GitlabHttpError",
    "GitlabImportError",
    "GitlabInvitationError",
    "GitlabJobCancelError",
    "GitlabJobEraseError",
    "GitlabJobPlayError",
    "GitlabJobRetryError",
    "GitlabLicenseError",
    "GitlabListError",
    "GitlabMRApprovalError",
    "GitlabMRClosedError",
    "GitlabMRForbiddenError",
    "GitlabMROnBuildSuccessError",
    "GitlabMRRebaseError",
    "GitlabMRResetApprovalError",
    "GitlabMarkdownError",
    "GitlabOperationError",
    "GitlabOwnershipError",
    "GitlabParsingError",
    "GitlabPipelineCancelError",
    "GitlabPipelinePlayError",
    "GitlabPipelineRetryError",
    "GitlabProjectDeployKeyError",
    "GitlabPromoteError",
    "GitlabProtectError",
    "GitlabRenderError",
    "GitlabRepairError",
    "GitlabRestoreError",
    "GitlabRetryError",
    "GitlabRevertError",
    "GitlabSearchError",
    "GitlabSetError",
    "GitlabStopError",
    "GitlabSubscribeError",
    "GitlabTimeTrackingError",
    "GitlabTodoError",
    "GitlabTopicMergeError",
    "GitlabTransferProjectError",
    "GitlabUnbanError",
    "GitlabUnblockError",
    "GitlabUnfollowError",
    "GitlabUnsubscribeError",
    "GitlabUpdateError",
    "GitlabUploadError",
    "GitlabUserApproveError",
    "GitlabUserRejectError",
    "GitlabVerifyError",
    "RedirectError",
]
