"""OAuth errors"""
from urllib.parse import quote


class OAuth2Error(Exception):
    """Base class for all OAuth2 Errors"""

    error: str
    description: str

    def create_dict(self):
        """Return error as dict for JSON Rendering"""
        return {
            "error": self.error,
            "error_description": self.description,
        }

    def __repr__(self) -> str:
        return self.error


class RedirectUriError(OAuth2Error):
    """The request fails due to a missing, invalid, or mismatching
    redirection URI (redirect_uri)."""

    error = "Redirect URI Error"
    description = (
        "The request fails due to a missing, invalid, or mismatching"
        " redirection URI (redirect_uri)."
    )


class ClientIdError(OAuth2Error):
    """The client identifier (client_id) is missing or invalid."""

    error = "Client ID Error"
    description = "The client identifier (client_id) is missing or invalid."


class UserAuthError(OAuth2Error):
    """
    Specific to the Resource Owner Password Credentials flow when
    the Resource Owners credentials are not valid.
    """

    error = "access_denied"
    description = "The resource owner or authorization server denied the request."


class TokenIntrospectionError(OAuth2Error):
    """
    Specific to the introspection endpoint. This error will be converted
    to an "active: false" response, as per the spec.
    See https://tools.ietf.org/html/rfc7662
    """


class AuthorizeError(OAuth2Error):
    """General Authorization Errors"""

    _errors = {
        # OAuth2 errors.
        # https://tools.ietf.org/html/rfc6749#section-4.1.2.1
        "invalid_request": "The request is otherwise malformed",
        "unauthorized_client": "The client is not authorized to request an "
        "authorization code using this method",
        "access_denied": "The resource owner or authorization server denied "
        "the request",
        "unsupported_response_type": "The authorization server does not "
        "support obtaining an authorization code "
        "using this method",
        "invalid_scope": "The requested scope is invalid, unknown, or " "malformed",
        "server_error": "The authorization server encountered an error",
        "temporarily_unavailable": "The authorization server is currently "
        "unable to handle the request due to a "
        "temporary overloading or maintenance of "
        "the server",
        # OpenID errors.
        # http://openid.net/specs/openid-connect-core-1_0.html#AuthError
        "interaction_required": "The Authorization Server requires End-User "
        "interaction of some form to proceed",
        "login_required": "The Authorization Server requires End-User "
        "authentication",
        "account_selection_required": "The End-User is required to select a "
        "session at the Authorization Server",
        "consent_required": "The Authorization Server requires End-User" "consent",
        "invalid_request_uri": "The request_uri in the Authorization Request "
        "returns an error or contains invalid data",
        "invalid_request_object": "The request parameter contains an invalid "
        "Request Object",
        "request_not_supported": "The provider does not support use of the "
        "request parameter",
        "request_uri_not_supported": "The provider does not support use of the "
        "request_uri parameter",
        "registration_not_supported": "The provider does not support use of "
        "the registration parameter",
    }

    def __init__(self, redirect_uri, error, grant_type):
        super().__init__()
        self.error = error
        self.description = self._errors[error]
        self.redirect_uri = redirect_uri
        self.grant_type = grant_type

    def create_uri(self, redirect_uri: str, state: str) -> str:
        """Get a redirect URI with the error message"""
        description = quote(str(self.description))

        # See:
        # http://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthError
        hash_or_question = "#" if self.grant_type == "implicit" else "?"

        uri = "{0}{1}error={2}&error_description={3}".format(
            redirect_uri, hash_or_question, self.error, description
        )

        # Add state if present.
        uri = uri + ("&state={0}".format(state) if state else "")

        return uri


class TokenError(OAuth2Error):
    """
    OAuth2 token endpoint errors.
    https://tools.ietf.org/html/rfc6749#section-5.2
    """

    _errors = {
        "invalid_request": "The request is otherwise malformed",
        "invalid_client": "Client authentication failed (e.g., unknown client, "
        "no client authentication included, or unsupported "
        "authentication method)",
        "invalid_grant": "The provided authorization grant or refresh token is "
        "invalid, expired, revoked, does not match the "
        "redirection URI used in the authorization request, "
        "or was issued to another client",
        "unauthorized_client": "The authenticated client is not authorized to "
        "use this authorization grant type",
        "unsupported_grant_type": "The authorization grant type is not "
        "supported by the authorization server",
        "invalid_scope": "The requested scope is invalid, unknown, malformed, "
        "or exceeds the scope granted by the resource owner",
    }

    def __init__(self, error):
        super().__init__()
        self.error = error
        self.description = self._errors[error]


class BearerTokenError(OAuth2Error):
    """
    OAuth2 errors.
    https://tools.ietf.org/html/rfc6750#section-3.1
    """

    _errors = {
        "invalid_request": ("The request is otherwise malformed", 400),
        "invalid_token": (
            "The access token provided is expired, revoked, malformed, "
            "or invalid for other reasons",
            401,
        ),
        "insufficient_scope": (
            "The request requires higher privileges than provided by "
            "the access token",
            403,
        ),
    }

    def __init__(self, code):
        super().__init__()
        self.code = code
        error_tuple = self._errors.get(code, ("", ""))
        self.description = error_tuple[0]
        self.status = error_tuple[1]