package apperror

import (
	"fmt"
	"net/http"
)

// The constructors in this file keep public messages, statuses, and details in one place

func AlreadyInUse(property string) *Error {
	return New(CodeAlreadyInUse, http.StatusConflict, property+" is already in use").WithDetail("property", property)
}

func NotFound(resource string) *Error {
	return New(CodeNotFound, http.StatusNotFound, resource+" not found").WithDetail("resource", resource)
}

func InvalidRequestBody(cause error) *Error {
	return Wrap(cause, CodeInvalidRequestBody, http.StatusBadRequest, "Request body is invalid")
}

func MissingField(field string) *Error {
	return InvalidField(field, "required", "is required")
}

func InvalidField(field, code, message string) *Error {
	return New(CodeValidationFailed, http.StatusBadRequest, fmt.Sprintf("%s %s", field, message)).WithFields([]FieldError{{
		Field:   field,
		Code:    code,
		Message: message,
	}})
}

func SetupNotAvailable() *Error {
	return New(CodeSetupNotAvailable, http.StatusNotFound, "Not found")
}

func SetupAlreadyCompleted() *Error {
	return New(CodeSetupAlreadyCompleted, http.StatusConflict, "Initial setup has already been completed")
}

func TokenInvalidOrExpired() *Error {
	return New(CodeTokenInvalidOrExpired, http.StatusUnauthorized, "Token is invalid or expired")
}

func DeviceCodeInvalid() *Error {
	return New(CodeDeviceCodeInvalid, http.StatusUnauthorized, "One-time access code must be used on the device it was generated for")
}

func TokenInvalid() *Error {
	return New(CodeInvalidToken, http.StatusUnauthorized, "Token is invalid")
}

func OidcMissingAuthorization() *Error {
	return New(CodeOidcMissingAuthorization, http.StatusForbidden, "Authorization is missing")
}

func OidcInvalidCallbackURL() *Error {
	return New(CodeOidcInvalidCallbackURL, http.StatusBadRequest, "Callback URL is invalid and may need to be corrected by an administrator")
}

func InvalidCIMDURLPattern(pattern string) *Error {
	return New(CodeValidationFailed, http.StatusBadRequest, "Metadata document URL pattern is invalid").
		WithDetail("pattern", pattern).
		WithFields([]FieldError{{
			Field:   "cimdUrlAllowlist",
			Code:    "invalid_value",
			Message: "contains an invalid URL pattern",
		}})
}

func UnsupportedFileType(expected string) *Error {
	if expected == "" {
		return New(CodeFileTypeNotSupported, http.StatusUnsupportedMediaType, "File type is not supported")
	}

	return New(CodeFileTypeNotSupported, http.StatusUnsupportedMediaType, "File must be of type "+expected).
		WithDetail("expected_file_type", expected)
}

func FileTooLarge(maxSize string) *Error {
	return New(CodeFileTooLarge, http.StatusRequestEntityTooLarge, fmt.Sprintf("File must not exceed %s", maxSize)).WithDetail("max_size", maxSize)
}

func NotSignedIn() *Error {
	return New(CodeNotSignedIn, http.StatusUnauthorized, "You are not signed in")
}

func MissingPermission() *Error {
	return New(CodeForbidden, http.StatusForbidden, "You don't have permission to perform this action")
}

func TooManyRequests() *Error {
	return New(CodeRateLimited, http.StatusTooManyRequests, "Too many requests")
}

func UserNotFound() *Error {
	return New(CodeUserNotFound, http.StatusNotFound, "User not found")
}

func InvalidImage(cause error) *Error {
	return Wrap(cause, CodeInvalidImage, http.StatusBadRequest, "File is not a valid image")
}

func MissingSessionID() *Error {
	return New(CodeMissingSessionID, http.StatusBadRequest, "Session ID is missing")
}

func InvalidWebAuthnSession() *Error {
	return New(CodeInvalidWebAuthnSession, http.StatusBadRequest, "Your passkey request has expired")
}

func InvalidWebAuthnResponse(cause error) *Error {
	return Wrap(cause, CodeInvalidWebAuthnResponse, http.StatusBadRequest, "We couldn't process the response from your passkey")
}

func WebAuthnAuthenticationFailed(cause error) *Error {
	return Wrap(cause, CodeWebAuthnAuthenticationFailed, http.StatusUnauthorized, "We couldn't verify your passkey")
}

func PasskeyUserVerificationRequired(cause error) *Error {
	return Wrap(cause, CodePasskeyUserVerificationRequired, http.StatusBadRequest, "Your passkey couldn't verify you. If you're using a security key, configure a FIDO2 PIN and try again")
}

func SyncedPasskeyNotAllowed() *Error {
	return New(CodeSyncedPasskeyNotAllowed, http.StatusBadRequest, "Synced passkeys are not allowed")
}

func ReservedClaim(key string) *Error {
	return New(CodeReservedClaim, http.StatusBadRequest, fmt.Sprintf("Claim %s is reserved and can't be used", key)).
		WithDetail("key", key).
		WithFields([]FieldError{{
			Field:   "key",
			Code:    "reserved",
			Message: "is reserved",
		}})
}

func DuplicateClaim(key string) *Error {
	return New(CodeDuplicateClaim, http.StatusBadRequest, fmt.Sprintf("Claim %s is already defined", key)).
		WithDetail("key", key).
		WithFields([]FieldError{{
			Field:   "key",
			Code:    "duplicate",
			Message: "is listed more than once",
		}})
}

func LdapDisabled() *Error {
	return New(CodeLdapDisabled, http.StatusConflict, "LDAP is not enabled")
}

func LdapUserUpdate() *Error {
	return New(CodeLdapUserUpdate, http.StatusForbidden, "LDAP users can't be updated")
}

func LdapUserGroupUpdate() *Error {
	return New(CodeLdapUserGroupUpdate, http.StatusForbidden, "LDAP user groups can't be updated")
}

func OidcAccessDenied() *Error {
	return New(CodeOidcAccessDenied, http.StatusForbidden, "You're not allowed to access this service")
}

func OidcInteractionNotFound() *Error {
	return New(CodeNotFound, http.StatusNotFound, "OIDC interaction not found or expired").
		WithDetail("resource", "OIDC interaction")
}

func OidcClientIDNotMatching() *Error {
	return New(CodeOidcClientIDNotMatching, http.StatusBadRequest, "Client ID in request doesn't match client ID in token")
}

func UIConfigDisabled() *Error {
	return New(CodeUIConfigDisabled, http.StatusForbidden, "The configuration can't be changed since the UI configuration is disabled")
}

func InvalidUserID() *Error {
	return Validation([]FieldError{{
		Field:   "userId",
		Code:    "invalid_format",
		Message: "must be a valid UUID",
	}})
}

func OneTimeAccessDisabled() *Error {
	return New(CodeOneTimeAccessDisabled, http.StatusForbidden, "One-time access is disabled")
}

func DeviceLoginRequestInvalidOrExpired() *Error {
	return New(CodeDeviceLoginExpired, http.StatusUnauthorized, "Device login request is invalid or expired")
}

func DeviceLoginDenied() *Error {
	return New(CodeDeviceLoginDenied, http.StatusForbidden, "Device login request was denied")
}

func InvalidAPIKey() *Error {
	return New(CodeInvalidAPIKey, http.StatusUnauthorized, "API key is invalid")
}

func NoAPIKeyProvided() *Error {
	return New(CodeNoAPIKeyProvided, http.StatusUnauthorized, "API key is missing")
}

func APIKeyNotFound() *Error {
	return New(CodeAPIKeyNotFound, http.StatusNotFound, "API key not found")
}

func APIKeyNotExpired() *Error {
	return New(CodeAPIKeyNotExpired, http.StatusConflict, "API key is not expired yet")
}

func InvalidAPIKeyExpiration() *Error {
	return New(CodeInvalidAPIKeyExpiration, http.StatusBadRequest, "API key expiration time must be in the future").WithFields([]FieldError{{
		Field:   "expiresAt",
		Code:    "invalid_value",
		Message: "must be in the future",
	}})
}

func APIKeyAuthNotAllowed() *Error {
	return New(CodeAPIKeyAuthNotAllowed, http.StatusForbidden, "API key authentication is not allowed for this endpoint")
}

func UserDisabled() *Error {
	return New(CodeUserDisabled, http.StatusForbidden, "User account is disabled")
}

func ValidationMessage(message string) *Error {
	return New(CodeValidationFailed, http.StatusBadRequest, message)
}

func OidcDeviceCodeExpired() *Error {
	return New(CodeOidcDeviceCodeExpired, http.StatusBadRequest, "Device code has expired")
}

func OidcInvalidDeviceCode() *Error {
	return New(CodeOidcInvalidDeviceCode, http.StatusBadRequest, "Device code is invalid")
}

func ReauthenticationRequired() *Error {
	return New(CodeReauthenticationRequired, http.StatusUnauthorized, "Reauthentication is required")
}

func ReauthenticationRequiredWithCause(cause error) *Error {
	return Wrap(cause, CodeReauthenticationRequired, http.StatusUnauthorized, "Reauthentication is required")
}

func OpenSignupDisabled() *Error {
	return New(CodeOpenSignupDisabled, http.StatusForbidden, "Open user signup is not enabled")
}

func ClientIDAlreadyExists() *Error {
	return New(CodeClientIDAlreadyExists, http.StatusConflict, "Client ID is already in use")
}

func UserEmailNotSet() *Error {
	return New(CodeUserEmailNotSet, http.StatusConflict, "The user does not have an email address set")
}

func ImageNotFound() *Error {
	return New(CodeImageNotFound, http.StatusNotFound, "Image not found")
}

func LogoDownloadFailed(cause error) *Error {
	return Wrap(cause, CodeLogoDownloadFailed, http.StatusUnprocessableEntity, "Logo could not be downloaded")
}

func LogoTypeNotSupported() *Error {
	return New(CodeLogoTypeNotSupported, http.StatusUnprocessableEntity, "Downloaded logo has an unsupported file type")
}

func LogoTooLarge(maxSize string) *Error {
	return New(CodeLogoTooLarge, http.StatusUnprocessableEntity, fmt.Sprintf("Downloaded logo must not exceed %s", maxSize)).
		WithDetail("max_size", maxSize)
}

func InvalidLogoURL(cause error) *Error {
	return Wrap(cause, CodeValidationFailed, http.StatusBadRequest, "Logo URL is not allowed").WithFields([]FieldError{{
		Field:   "logoUrl",
		Code:    "invalid_value",
		Message: "must be a public HTTP or HTTPS URL",
	}})
}

func OidcPARRequired() *Error {
	return New(CodeOidcPARRequired, http.StatusBadRequest, "This client requires pushed authorization requests")
}

func InvalidEmailVerificationToken() *Error {
	return New(CodeEmailVerificationTokenInvalid, http.StatusBadRequest, "Email verification token is invalid")
}
