package errx

import (
	"fmt"

	"github.com/warmbly/warmbly/internal/config"
)

var (
	ErrInvalid = New(BadRequest, "The request body contains invalid or malformed JSON.")

	ErrLock      = New(BadRequest, "Resource is locked. Another process is running, please wait.")
	ErrColor     = New(BadRequest, "Hex color must be a valid string.")
	ErrUuid      = New(BadRequest, "The id must be a valid uuid.")
	ErrCategory  = New(BadRequest, "Category doesn't exists.")
	ErrTag       = New(BadRequest, "Tag doesn't exists.")
	ErrBitmask   = New(BadRequest, "Invalid bitmask value.")
	ErrRole      = New(BadRequest, "Role doesn't exists.")
	ErrPosition  = New(BadRequest, "Invalid position.")
	ErrTimezone  = New(BadRequest, "Timezone doesn't exists.")
	ErrTime      = New(BadRequest, "Invalid time format.")
	ErrNotEnough = New(BadRequest, "Not enough data to perform this action.")
	ErrLimit     = New(BadRequest, "Limit must be between 10 and 200.")

	// Authorization
	ErrToken = New(Unauthorized, "Invalid or expired token.")
	ErrAuth  = New(Unauthorized, "Missing or invalid Authorization header.")

	ErrUser        = New(BadRequest, "User doesn't exists.")
	ErrPassword    = New(BadRequest, "Password must be at least 8 characters long.")
	ErrEmail       = New(BadRequest, "Invalid email address.")
	ErrCredentials = New(BadRequest, "Invalid email or password.")
	ErrSession     = New(BadRequest, "Invalid or expired session.")
	ErrCodeLimit   = New(BadRequest, "Too many attempts. Start a new session and try again later.")
	ErrCode        = New(BadRequest, "Invalid or expired verification code.")
	ErrAuthLimit   = New(BadRequest, "Too many attempts, please try again later.")

	// ErrMailUndeliverable separates "we could not send you the email" from
	// every other internal fault. It used to be a bare 500, which on a
	// self-hosted install with no working relay is the single least helpful
	// thing to show someone who cannot log in.
	ErrMailUndeliverable = New(Internal, "We couldn't send the email. If you administer this server, check the mail transport configuration.")

	// Registration refusals. Each names the deployment policy rather than the
	// person, and carries its own identifier so a client can branch on the
	// specific condition instead of string-matching a 403.
	ErrRegistrationInviteOnly = NewWithIdentifier(Forbidden, "registration_invite_only",
		"This server is invite only. Ask an administrator to invite you, then open the link in the invitation to create your account. See https://docs.warmbly.com/development/accounts-and-access/")
	// ErrRegistrationClosed is returned when signups are off entirely. The
	// operator sets DISABLE_REGISTRATION=true, and no invitation overrides it.
	ErrRegistrationClosed = NewWithIdentifier(Forbidden, "registration_closed",
		"This server is not accepting new accounts. See https://docs.warmbly.com/development/accounts-and-access/")
	ErrInvitationInvalid = NewWithIdentifier(Forbidden, "invitation_invalid",
		"That invitation link is invalid, expired, or was issued for a different email address. Ask for a fresh one.")

	// First-run claim. Both name the command that resolves them, because the
	// person who sees these is the person who can run it.
	ErrSetupToken = NewWithIdentifier(Unauthorized, "setup_token_invalid",
		"That setup link is invalid, already used, or expired. Print a new one with `warmblyctl setup-link`.")
	ErrSetupComplete = NewWithIdentifier(Forbidden, "setup_already_complete",
		"This instance has already been set up. Sign in, or recover access with `warmblyctl user reset-password`.")

	// ErrSSOBrowser is a sign-in collected somewhere other than where it
	// started. The handoff is deliberately non-transferable, so a link someone
	// forwarded (or was sent) cannot sign the recipient into the sender's
	// workspace.
	ErrSSOBrowser = NewWithIdentifier(Unauthorized, "sso_wrong_browser",
		"Finish signing in in the browser you started in. Open the sign-in page there and try again.")

	ErrExternalCode     = New(BadRequest, "Invalid or expired code, please try again.")
	ErrExternalEmail    = New(BadRequest, "Invalid or unverified email address.")
	ErrExternalProvider = New(BadRequest, "This sign-in method isn't available on this server.")

	// Sessions
	ErrSessionNotFound = New(NotFound, "Session not found.")
	ErrSessionCurrent  = New(BadRequest, "You can't revoke the session you're currently using. Use sign out instead.")

	// Passkeys (WebAuthn)
	ErrPasskey         = New(BadRequest, "We couldn’t verify that passkey. Please try again.")
	ErrPasskeySession  = New(BadRequest, "Your passkey request expired. Please start again.")
	ErrPasskeyName     = New(BadRequest, "Passkey name must be between 1 and 60 characters.")
	ErrPasskeyNotFound = New(NotFound, "Passkey not found.")
	ErrPasskeyExists   = New(Conflict, "This passkey is already registered.")
	ErrPasskeyNone     = New(BadRequest, "No passkey was found for this account.")

	// Organization
	//
	// Raised where a workspace is required to scope a write or a policy check.
	// Every entitlement, limit and suppression rule is org-scoped, so a request
	// without one is refused rather than run unscoped.
	ErrNoOrganization = NewWithIdentifier(BadRequest, "no_organization",
		"No workspace selected. Pick a workspace and try again.")

	// Roles
	ErrRoleName = New(BadRequest, "Role name length must be between 3 and 100 characters.")

	// Captch
	ErrCaptcha = New(BadRequest, "We couldn’t verify you’re human. Please try the security check again or reload the page.")

	// Group
	ErrGroupTitle = New(BadRequest, "The title length must be between 1 and 50 characters.")
	ErrGroupMax   = New(BadRequest, "You reached the maximum amount.")

	// Email
	ErrEmailCredentials     = New(BadRequest, "Invalid email credentials.")
	ErrEmailValidation      = New(BadRequest, "Deadline exceed, try again later.")
	ErrEmailOnboardProvider = New(BadRequest, "Unsupported email provider. Use 'gmail', 'outlook', or 'smtp_imap'.")
	// Raised when the provider is supported but this deployment has no OAuth
	// client for it. Self-host only: the hosted product always has both set. The
	// message names the variables because the person hitting this is usually the
	// same person who can fix it.
	ErrEmailOnboardGoogleNotConfigured = NewWithIdentifier(ServiceUnavailable, "mailbox_provider_not_configured",
		"Gmail is not configured on this deployment. Set BOX_GOOGLE_CLIENT_ID and BOX_GOOGLE_CLIENT_SECRET in your .env, then restart. See https://docs.warmbly.com/development/deployment-guide/#connect-mailboxes")
	ErrEmailOnboardOutlookNotConfigured = NewWithIdentifier(ServiceUnavailable, "mailbox_provider_not_configured",
		"Microsoft 365 is not configured on this deployment. Set BOX_OUTLOOK_CLIENT_ID and BOX_OUTLOOK_CLIENT_SECRET in your .env, then restart. See https://docs.warmbly.com/development/deployment-guide/#connect-mailboxes")
	ErrEmailOnboardState         = New(BadRequest, "Invalid or expired onboarding state.")
	ErrEmailOnboardCode          = New(BadRequest, "Authorization code is missing or invalid.")
	ErrEmailOnboardExchange      = New(BadRequest, "Could not exchange the authorization code with the provider.")
	ErrEmailOnboardUserInfo      = New(BadRequest, "Could not read account details from the provider.")
	ErrEmailOnboardAlreadyExists = New(Conflict, "This email account is already connected.")
	ErrEmailOnboardNoWorker      = New(ServiceUnavailable, "No mailbox workers are available right now. Please try again shortly.")
	ErrEmailReauthProvider       = New(BadRequest, "This mailbox connects with SMTP/IMAP credentials. Update its credentials instead of re-authorizing.")
	ErrEmailReauthOAuthOnly      = New(BadRequest, "This mailbox signs in with OAuth. Re-authorize it instead of entering credentials.")
	ErrEmailReauthWrongAccount   = New(Conflict, "The account you signed in with is not this mailbox's address. Sign in with the mailbox's own account and try again.")
	ErrEmailReauthCloudManaged   = New(Conflict, "Warmbly Cloud holds this mailbox's sign-in. Reconnect it from your cloud workspace instead.")
	ErrEmailReauthNoRefreshToken = New(BadRequest, "The provider did not return a refresh token and none is stored. Please try re-authorizing again.")
	ErrEmailSMTPHost             = New(BadRequest, "SMTP host is required.")
	ErrEmailSMTPPort             = New(BadRequest, "SMTP port must be between 1 and 65535.")
	ErrEmailSMTPSecurity         = New(BadRequest, "SMTP security must be tls or starttls.")
	ErrEmailIMAPSecurity         = New(BadRequest, "IMAP security must be tls or starttls.")
	ErrEmailIMAPHost             = New(BadRequest, "IMAP host is required.")
	ErrEmailIMAPPort             = New(BadRequest, "IMAP port must be between 1 and 65535.")
	ErrEmailCredentialsRequired  = New(BadRequest, "SMTP and IMAP credentials are required.")
	ErrEmailTrackingDomain       = New(BadRequest, "Invalid tracking domain.")
	ErrEmailTrackingDomainLength = New(BadRequest, "Tracking domain is too long (max 253 characters).")
	ErrEmailName                 = New(BadRequest, "Invalid name. Must be 2–100 characters and contain only letters, numbers, spaces, '-', '.', or '’'.")
	ErrEmailSignaturePlain       = New(BadRequest, "Plain email signature is too long.")
	ErrEmailSignatureHTML        = New(BadRequest, "HTML email signature is too long.")
	ErrEmailMinWaitTime          = New(BadRequest, "Minimum time gap between emails must be between 0 and 86400 seconds.")
	ErrEmailCampaignLimit        = New(BadRequest, fmt.Sprintf("Campaign limit must be between %d and %d.", config.LimitMin, config.LimitMax))
	ErrEmailTimezone             = New(BadRequest, "Invalid timezone. Use an IANA name such as Europe/London or America/Denver, or leave it empty to follow the campaign.")
	ErrEmailWarmupBase           = New(BadRequest, "Warmup base must be between 0 and 100.")
	ErrEmailWarmupMax            = New(BadRequest, "Warmup max amount must be between 0 and 100.")
	ErrEmailWarmupIncrease       = New(BadRequest, "Warmup increase amount must be between 0 and 100.")
	ErrEmailReplyRate            = New(BadRequest, "Warmup reply rate must be between 0 and 100.")

	// Disconnecting a mailbox has to reach the machine syncing it before the
	// row goes: afterwards there is no assignment left to read and nothing that
	// can repair a missed removal, so the mailbox would sync on forever.
	ErrEmailWorkerUnreachable = NewWithIdentifier(ServiceUnavailable, "mailbox_worker_unreachable",
		"This mailbox could not be disconnected right now because the machine syncing it could not be reached. Nothing was removed, so try again in a moment.")

	// Campaign
	ErrCampaignName        = New(BadRequest, "Campaign name length must be between 3 and 50 characters.")
	ErrCampaignDescription = New(BadRequest, "Campaign description length must be below 300 characters.")
	ErrCampaignDailyLimit  = New(BadRequest, fmt.Sprintf("Daily limit must be between %d and %d.", config.CampaignDailyLimitMin, config.LimitMax))
	ErrCampaignStartDate   = New(BadRequest, "Start date cannot be in the past. Pick today or later, or clear it (null) to start right away.")
	ErrCampaignEndDate     = New(BadRequest, "End date must be in the future.")
	ErrCampaignLimit       = New(BadRequest, "You reached your limit for campaigns, please try again later.")

	// Sequence
	ErrSequenceName      = New(BadRequest, "Sequence name cannot be longer than 50 characters.")
	ErrSequenceSubject   = New(BadRequest, "Sequence subject cannot be longer than 100 characters.")
	ErrSequenceBody      = New(BadRequest, fmt.Sprintf("Sequence body cannot be longer than %d characters.", config.SequenceBodyLimit))
	ErrSequenceBranch    = New(BadRequest, "Invalid branching conditions.")
	ErrSequenceBranchTo  = New(BadRequest, "Branch target must be another step in the same campaign and cannot create a cycle.")
	ErrSequenceKind      = New(BadRequest, "Step kind must be email, action, or wait.")
	ErrSequenceAction    = New(BadRequest, "Invalid action configuration for this step.")
	ErrSequenceWaitAfter = New(BadRequest, fmt.Sprintf("Step wait must be between 0 and %d days.", config.SequenceWaitAfterMax))

	// Contact
	ErrContactSerialize = New(BadRequest, "Failed to serialize contact.")
	ErrContactSize      = New(BadRequest, "Contact size cannot be bigger than 10KB.")

	// Unibox
	ErrUniboxLimit = New(BadRequest, fmt.Sprintf("Limit must be between %d and %d.", config.UniboxLimitMin, config.UniboxLimitMax))
	ErrSeenMax     = New(BadRequest, "Cannot update more than 500 messages.")
	// Folder scoping (unibox sidebar).
	ErrUniboxFolder     = New(BadRequest, "Folder must be one of inbox, sent, drafts, archive, spam, trash.")
	ErrSeenFolderAndIDs = New(BadRequest, "Provide either email_ids or folder, not both.")

	// Servers
	ErrIPAddr    = New(BadRequest, "Invalid IP Address.")
	ErrPublicKey = New(BadRequest, "Invalid Public Key.")

	// Advisor
	ErrAdvisorNoAction     = New(BadRequest, "This recommendation doesn't have a one-click fix.")
	ErrAdvisorNotApplied   = New(BadRequest, "This recommendation hasn't been applied, so there's nothing to undo.")
	ErrAdvisorSnoozeRange  = New(BadRequest, "Snooze length must be between 1 and 90 days.")
	ErrAdvisorBadSeverity  = New(BadRequest, "Minimum severity must be one of critical, high, medium, or low.")
	ErrAdvisorFixForbidden = New(Forbidden, "You can see this recommendation but don't have permission to apply the change it makes.")
	ErrAdvisorNoAgentFix   = New(BadRequest, "This recommendation needs a person: there's no change an agent can safely make for it.")
)

// MailboxAllowanceReached is the refusal every connect path returns when the
// workspace holds its whole allowance. The identifier is stable so the
// dashboard can open the request-more flow instead of showing the text.
func MailboxAllowanceReached(used, allowance int, paid bool) *Error {
	if !paid {
		return NewWithIdentifier(Forbidden, "mailbox_allowance_reached",
			fmt.Sprintf("A free workspace holds up to %d mailboxes. Choose a plan to add more.", allowance))
	}
	return NewWithIdentifier(Forbidden, "mailbox_allowance_reached",
		fmt.Sprintf("This workspace holds %d of its %d mailboxes. Request an increase, or move to a plan with more daily sends.", used, allowance))
}

// StorageLimitReached is the refusal for an attachment upload or copy that
// would take the workspace past its storage quota.
func StorageLimitReached(usedBytes, limitBytes, addingBytes int64) *Error {
	const mb = 1024 * 1024
	return NewWithIdentifier(BadRequest, "storage_limit_reached",
		fmt.Sprintf("Storage limit reached: %d MB of %d MB used, %d MB to add. Remove attachments or upgrade your plan.",
			usedBytes/mb, limitBytes/mb, addingBytes/mb))
}
