package errx

import (
	"fmt"
	"time"

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

type MailErrorLogType string

const (
	MailErrorLogTypeExternal MailErrorLogType = "EXTERNAL"
	MailErrorLogTypeInternal MailErrorLogType = "INTERNAL"
)

func (l MailErrorLogType) IsExternal() bool {
	return l == MailErrorLogTypeExternal
}

type MailErrorType string

const (
	MailErrorCritical      MailErrorType = "CRITICAL"
	MailErrorWarning       MailErrorType = "WARNING"
	MailErrorInformational MailErrorType = "INFORMATIONAL"
)

type MailErrorCode string

const (
	MailErrorCodeFolderLimit     MailErrorCode = "MAX_FOLDERS_REACHED"
	MailErrorCodeUpdateLimit     MailErrorCode = "MAX_FOLDERS_REACHED"
	MailErrorCodeGoogleAuth      MailErrorCode = "GOOGLE_AUTHENTICATION_FAILED"
	MailErrorCodeGooglePayment   MailErrorCode = "GOOGLE_PAYMENT_REQUIRED"
	MailErrorCodeGoogleForbidden MailErrorCode = "GOOGLE_FORBIDDEN"

	MailErrorCodeServerUnreachable MailErrorCode = "SERVER_UNREACHABLE"
	// MailErrorCodeNotFound is the provider saying the addressed resource is
	// absent, kept apart from SERVER_UNREACHABLE so "this folder does not
	// exist" is never inferred from a catch-all that also covers a 503.
	MailErrorCodeNotFound             MailErrorCode = "RESOURCE_NOT_FOUND"
	MailErrorCodeUnsupported          MailErrorCode = "UNSUPPORTED"
	MailErrorCodeInvalidCredentials   MailErrorCode = "INVALID_CREDENTIALS"   // e.g. invalid username or password
	MailErrorCodeAuthorizationFailed  MailErrorCode = "AUTHORIZATION_FAILED"  // e.g. imap disabled
	MailErrorCodeAuthenticationFailed MailErrorCode = "AUTHENTICATION_FAILED" // e.g. invalid token
	MailErrorCodeConnectionLost       MailErrorCode = "CONNECTION_LOST"
	MailErrorCodeImapUnknown          MailErrorCode = "IMAP_UNKNOWN"

	// Rate limiting and abuse detection
	MailErrorCodeRateLimitExceeded MailErrorCode = "RATE_LIMIT_EXCEEDED"
	// Sync fair use (internal/app/worker/wmail/governor.go). SYNC_FLOOD is
	// an hourly volume no real mailbox produces; SYNC_FAIR_USE is repeated
	// daily overage. Both deactivate the mailbox until someone reactivates it.
	MailErrorCodeSyncFlood         MailErrorCode = "SYNC_FLOOD"
	MailErrorCodeSyncFairUse       MailErrorCode = "SYNC_FAIR_USE"
	MailErrorCodeSendingTooFast    MailErrorCode = "SENDING_TOO_FAST"
	MailErrorCodeRecipientRejected MailErrorCode = "RECIPIENT_REJECTED"
	// MailErrorCodeDomainAuthRejected is the receiving side refusing the mail
	// because the SENDING DOMAIN failed its authentication bar (Outlook's
	// 5.7.515, Gmail's 5.7.26). Not a dead server and not a bad recipient:
	// retrying from the same domain fails identically until DNS is fixed.
	MailErrorCodeDomainAuthRejected MailErrorCode = "DOMAIN_AUTH_REJECTED"
	MailErrorCodeQuotaExceeded      MailErrorCode = "QUOTA_EXCEEDED"
	MailErrorCodeAccountSuspended   MailErrorCode = "ACCOUNT_SUSPENDED"
)

// CredentialMailErrorCodes are the credential-class errors a successful
// mailbox re-authorization or SMTP/IMAP credential update fixes.
var CredentialMailErrorCodes = []MailErrorCode{
	MailErrorCodeGoogleAuth,
	MailErrorCodeAuthenticationFailed,
	MailErrorCodeAuthorizationFailed,
	MailErrorCodeInvalidCredentials,
}

var MailErrorCodeGoogleUnknown = func(code int) MailErrorCode {
	return MailErrorCode(fmt.Sprintf("Unknown (%d)", code))
}

type MailErrorResolveMethod string

const (
	MailErrorResolveMethodNone   MailErrorResolveMethod = ""
	MailErrorResolveMethodAuth   MailErrorResolveMethod = "OAUTH"
	MailErrorResolveMethodRetry  MailErrorResolveMethod = "RETRY"
	MailErrorResolveMethodReload MailErrorResolveMethod = "RELOAD"
)

type MailError struct {
	ID string `json:"id"`

	Type          MailErrorType          `json:"type"`
	Code          MailErrorCode          `json:"code"`
	ResolveMethod MailErrorResolveMethod `json:"resolve_method"`
	ResolvedAt    *time.Time             `json:"resolved_at"`

	Message string `json:"message"`

	CreatedAt time.Time `json:"created_at"`
}

func (e *MailError) Error() string {
	return fmt.Sprintf("Email (%s): %s", e.ID, e.Message)
}

func (e *MailError) Unwrap() error {
	return fmt.Errorf("Email (%s): %s", e.ID, e.Message)
}

func MError(eType MailErrorType, code MailErrorCode, message string, resolveMethod MailErrorResolveMethod) *MailError {
	return &MailError{
		ID:            uuid.NewString(),
		Type:          eType,
		Code:          code,
		Message:       message,
		ResolveMethod: resolveMethod,
	}
}

var (
	ErrMailFoldersMax      = MError(MailErrorCritical, MailErrorCodeFolderLimit, fmt.Sprintf("You reached the maximum limit of %d folders reached.", config.MaxEmailFolders), MailErrorResolveMethodReload)
	ErrMailUpdateLimit     = MError(MailErrorCritical, MailErrorCodeUpdateLimit, "Your inbox has received an unusually large number of updates. Please reactivate your inbox once the issue is resolved.", MailErrorResolveMethodReload)
	ErrMailGoogleAuth      = MError(MailErrorCritical, MailErrorCodeGoogleAuth, "Cannot access your Gmail account. Please re-authorize your account to restore mailbox access.", MailErrorResolveMethodReload)
	ErrMailGooglePayment   = MError(MailErrorCritical, MailErrorCodeGooglePayment, "Gmail access blocked due to unpaid invoices. Please resolve the payment with Google.", MailErrorResolveMethodReload)
	ErrMailGoogleForbidden = func(message string) *MailError {
		return MError(MailErrorWarning, MailErrorCodeGoogleForbidden, fmt.Sprintf("Gmail access blocked: %s", message), MailErrorResolveMethodReload)
	}
	ErrMailGoogleUnknown = func(code int, message string) *MailError {
		return MError(MailErrorWarning, MailErrorCodeGoogleUnknown(code), message, MailErrorResolveMethodRetry)
	}
	ErrMailServerUnreachable     = MError(MailErrorWarning, MailErrorCodeServerUnreachable, "The connection to the mail server could not be established. The server may be offline or blocking the connection.", MailErrorResolveMethodRetry)
	ErrMailResourceNotFound      = MError(MailErrorWarning, MailErrorCodeNotFound, "The mail server does not have the folder or message that was requested.", MailErrorResolveMethodRetry)
	ErrMailCondStoreNotSupported = MError(MailErrorCritical, MailErrorCodeUnsupported, "The mail server does not support the required CONDSTORE extension. Synchronization cannot continue.", MailErrorResolveMethodReload)
	ErrMailInvalidCredentials    = MError(
		MailErrorCritical,
		MailErrorCodeInvalidCredentials,
		"The email address or password is incorrect. Please check your credentials and try again.",
		MailErrorResolveMethodReload,
	)
	ErrMailAuthenticationFailed = MError(
		MailErrorCritical,
		MailErrorCodeAuthenticationFailed,
		"Authentication failed. This often happens with OAuth2 providers (like Google for Gmail or Microsoft for Outlook). Possible causes: invalid or expired token, wrong permissions/scope, or two-factor authentication requiring an app password. Please re-authenticate or check your account security settings.",
		MailErrorResolveMethodAuth,
	)
	ErrMailAuthorizationFailed = MError(
		MailErrorCritical,
		MailErrorCodeAuthorizationFailed,
		"This account lacks permission to access certain required resources.",
		MailErrorResolveMethodReload,
	)
	ErrMailUnknownImapError = func(errStatus string) *MailError {
		return MError(
			MailErrorCritical,
			MailErrorCodeImapUnknown,
			fmt.Sprintf("Something went wrong: %s", errStatus),
			MailErrorResolveMethodReload,
		)
	}

	// Rate limiting and abuse errors
	ErrMailRateLimitExceeded = MError(
		MailErrorWarning,
		MailErrorCodeRateLimitExceeded,
		"This email account has exceeded the sync rate limit. This may indicate suspicious activity.",
		MailErrorResolveMethodNone,
	)
	ErrMailSyncFlood = MError(
		MailErrorWarning,
		MailErrorCodeSyncFlood,
		"This mailbox received far more new mail in one hour than sync fair use allows, so syncing was stopped to protect the platform.",
		MailErrorResolveMethodNone,
	)
	ErrMailSyncFairUse = MError(
		MailErrorWarning,
		MailErrorCodeSyncFairUse,
		"This mailbox exceeded its daily sync budget on several recent days, so syncing was stopped. Reactivate it once the volume is back to normal.",
		MailErrorResolveMethodNone,
	)
	ErrMailSendingTooFast = MError(
		MailErrorWarning,
		MailErrorCodeSendingTooFast,
		"Emails are being sent too quickly. Please wait before sending more emails.",
		MailErrorResolveMethodRetry,
	)
	ErrMailRecipientRejected = MError(
		MailErrorWarning,
		MailErrorCodeRecipientRejected,
		"The recipient email address was rejected by the mail server.",
		MailErrorResolveMethodNone,
	)
	ErrMailDomainAuthRejected = MError(
		MailErrorCritical,
		MailErrorCodeDomainAuthRejected,
		"The receiving mail server refused this message because your sending domain failed its authentication checks. Fix the domain's SPF, DKIM and DMARC records.",
		MailErrorResolveMethodNone,
	)
	ErrMailQuotaExceeded = MError(
		MailErrorCritical,
		MailErrorCodeQuotaExceeded,
		"Your email sending quota has been exceeded. Please try again later.",
		MailErrorResolveMethodRetry,
	)
	ErrMailAccountSuspended = MError(
		MailErrorCritical,
		MailErrorCodeAccountSuspended,
		"This email account has been suspended. Please contact your email provider.",
		MailErrorResolveMethodReload,
	)
)

// UserErrorInfo contains user-visible error information
type UserErrorInfo struct {
	Title          string
	Message        string
	ActionRequired string
}

// GetUserErrorInfo returns user-friendly error information for display
func (e *MailError) GetUserErrorInfo() UserErrorInfo {
	info := UserErrorInfo{
		Title:   "Email Error",
		Message: e.Message,
	}

	switch e.Code {
	case MailErrorCodeGoogleAuth, MailErrorCodeAuthenticationFailed:
		info.Title = "Authentication Required"
		info.ActionRequired = "Please re-authorize your email account"
	case MailErrorCodeInvalidCredentials:
		info.Title = "Invalid Credentials"
		info.ActionRequired = "Please update your email credentials"
	case MailErrorCodeServerUnreachable:
		info.Title = "Connection Error"
		info.ActionRequired = "The email server is temporarily unavailable. We'll retry automatically."
	case MailErrorCodeNotFound:
		info.Title = "Mailbox Item Missing"
		info.ActionRequired = "The folder or message is no longer on the mail server. Nothing to do; we'll skip it."
	case MailErrorCodeRateLimitExceeded:
		info.Title = "Rate Limit Exceeded"
		info.ActionRequired = "Your account has been temporarily limited due to unusual activity"
	case MailErrorCodeSyncFlood:
		info.Title = "Sync stopped: unusual volume"
		info.ActionRequired = "Check what is delivering mail into this mailbox, then reactivate it under Mailboxes"
	case MailErrorCodeSyncFairUse:
		info.Title = "Sync stopped: fair use"
		info.ActionRequired = "Reduce the volume landing in this mailbox or ask your administrator to raise the sync budget, then reactivate it"
	case MailErrorCodeSendingTooFast:
		info.Title = "Sending Too Fast"
		info.ActionRequired = "Please wait before sending more emails"
	case MailErrorCodeQuotaExceeded:
		info.Title = "Quota Exceeded"
		info.ActionRequired = "Your daily sending limit has been reached"
	case MailErrorCodeAccountSuspended:
		info.Title = "Account Suspended"
		info.ActionRequired = "Contact your email provider to resolve this issue"
	case MailErrorCodeRecipientRejected:
		info.Title = "Recipient Rejected"
		info.ActionRequired = "The recipient address was not accepted"
	}

	return info
}

// IsUserVisible returns true if this error should be shown to users
func (e *MailError) IsUserVisible() bool {
	return e.Type == MailErrorCritical || e.Type == MailErrorWarning
}
