package model

import (
	"encoding/json"
	"fmt"

	"github.com/gofiber/fiber/v2"
)

// APIError is an error object that is returned on the api when an error occurs
type APIError struct {
	Error            string `json:"error"`
	ErrorDescription string `json:"error_description,omitempty"`
}

// ResponseNYI is the server response when something is not yet implemented
var ResponseNYI = Response{Status: fiber.StatusNotImplemented, Response: APIErrorNYI}

// Predefined errors
var (
	APIErrorUnknownIssuer            = APIError{ErrorInvalidRequest, "The provided issuer is not supported"}
	APIErrorStateMismatch            = APIError{ErrorInvalidRequest, "State mismatched"}
	APIErrorUnsupportedOIDCFlow      = APIError{ErrorInvalidGrant, "Unsupported oidc_flow"}
	APIErrorUnsupportedGrantType     = APIError{ErrorInvalidGrant, "Unsupported grant_type"}
	APIErrorBadTransferCode          = APIError{ErrorInvalidToken, "Bad polling or transfer code"}
	APIErrorTransferCodeExpired      = APIError{ErrorExpiredToken, "polling or transfer code is expired"}
	APIErrorAuthorizationPending     = ErrorWithoutDescription(ErrorAuthorizationPending)
	APIErrorNoRefreshToken           = APIError{ErrorOIDC, "Did not receive a refresh token"}
	APIErrorInsufficientCapabilities = APIError{ErrorInsufficientCapabilities, "The provided token does not have the required capability for this operation"}
	APIErrorUsageRestricted          = APIError{ErrorUsageRestricted, "The restrictions of this token does not allow this usage"}
	APIErrorNYI                      = ErrorWithoutDescription(ErrorNYI)
)

// Predefined OAuth2/OIDC errors
const (
	ErrorInvalidRequest       = "invalid_request"
	ErrorInvalidClient        = "invalid_client"
	ErrorInvalidGrant         = "invalid_grant"
	ErrorUnauthorizedClient   = "unauthorized_client"
	ErrorUnsupportedGrantType = "unsupported_grant_type"
	ErrorInvalidScope         = "invalid_scope"
	ErrorInvalidToken         = "invalid_token"
	ErrorInsufficientScope    = "insufficient_scope"
	ErrorExpiredToken         = "expired_token"
	ErrorAccessDenied         = "access_denied"
	ErrorAuthorizationPending = "authorization_pending"
)

// Additional Mytoken errors
const (
	ErrorInternal                 = "internal_server_error"
	ErrorOIDC                     = "oidc_error"
	ErrorNYI                      = "not_yet_implemented"
	ErrorInsufficientCapabilities = "insufficient_capabilities"
	ErrorUsageRestricted          = "usage_restricted"
)

// InternalServerError creates an APIError for internal server errors
func InternalServerError(errorDescription string) APIError {
	return APIError{
		Error:            ErrorInternal,
		ErrorDescription: errorDescription,
	}
}

// OIDCError creates an APIError for oidc related errors
func OIDCError(oidcError, oidcErrorDescription string) APIError {
	err := oidcError
	if oidcErrorDescription != "" {
		err = fmt.Sprintf("%s: %s", oidcError, oidcErrorDescription)
	}
	return APIError{
		Error:            ErrorOIDC,
		ErrorDescription: err,
	}
}

// OIDCErrorFromBody creates an APIError for oidc related errors from the response of an oidc provider
func OIDCErrorFromBody(body []byte) (error APIError, ok bool) {
	bodyError := APIError{}
	if err := json.Unmarshal(body, &bodyError); err != nil {
		return
	}
	error = OIDCError(bodyError.Error, bodyError.ErrorDescription)
	ok = true
	return
}

// BadRequestError creates an APIError for bad request errors
func BadRequestError(errorDescription string) APIError {
	return APIError{
		Error:            ErrorInvalidRequest,
		ErrorDescription: errorDescription,
	}
}

// InvalidTokenError creates an APIError for invalid token errors
func InvalidTokenError(errorDescription string) APIError {
	return APIError{
		Error:            ErrorInvalidToken,
		ErrorDescription: errorDescription,
	}
}

// ErrorWithoutDescription creates an APIError from an error string
func ErrorWithoutDescription(err string) APIError {
	return APIError{
		Error: err,
	}
}

// ErrorWithErrorDescription creates an APIError from an error string and golang error
func ErrorWithErrorDescription(e string, err error) APIError {
	return APIError{
		Error:            e,
		ErrorDescription: err.Error(),
	}
}