// File Path: monorepo/cloud/maplepress-backend/pkg/validation/email.go
package validation

import (
	"fmt"
	"strings"
)

// EmailValidator provides comprehensive email validation and normalization
// CWE-20: Improper Input Validation - Ensures email addresses are properly validated
type EmailValidator struct {
	validator *Validator
}

// NewEmailValidator creates a new email validator
func NewEmailValidator() *EmailValidator {
	return &EmailValidator{
		validator: NewValidator(),
	}
}

// ValidateAndNormalize validates and normalizes an email address
// Returns the normalized email and any validation error
func (ev *EmailValidator) ValidateAndNormalize(email, fieldName string) (string, error) {
	// Step 1: Basic validation using existing validator
	if err := ev.validator.ValidateEmail(email, fieldName); err != nil {
		return "", err
	}

	// Step 2: Normalize the email
	normalized := ev.Normalize(email)

	// Step 3: Additional security checks
	if err := ev.ValidateSecurityConstraints(normalized, fieldName); err != nil {
		return "", err
	}

	return normalized, nil
}

// Normalize normalizes an email address for consistent storage and comparison
// CWE-180: Incorrect Behavior Order: Validate Before Canonicalize
func (ev *EmailValidator) Normalize(email string) string {
	// Trim whitespace
	email = strings.TrimSpace(email)

	// Convert to lowercase (email local parts are case-sensitive per RFC 5321,
	// but most providers treat them as case-insensitive for better UX)
	email = strings.ToLower(email)

	// Remove any null bytes
	email = strings.ReplaceAll(email, "\x00", "")

	// Gmail-specific normalization (optional - commented out by default)
	// This removes dots and plus-aliases from Gmail addresses
	// Uncomment if you want to prevent abuse via Gmail aliases
	// email = ev.normalizeGmail(email)

	return email
}

// ValidateSecurityConstraints performs additional security validation
func (ev *EmailValidator) ValidateSecurityConstraints(email, fieldName string) error {
	// Check for suspicious patterns

	// 1. Detect emails with excessive special characters (potential obfuscation)
	specialCharCount := 0
	for _, ch := range email {
		if ch == '+' || ch == '.' || ch == '_' || ch == '-' || ch == '%' {
			specialCharCount++
		}
	}
	if specialCharCount > 10 {
		return fmt.Errorf("%s: contains too many special characters", fieldName)
	}

	// 2. Detect potentially disposable email patterns
	if ev.isLikelyDisposable(email) {
		// Note: This is a warning-level check. In production, you might want to
		// either reject these or flag them for review.
		// For now, we'll allow them but this can be configured.
	}

	// 3. Check for common typos in popular domains
	if typo := ev.detectCommonDomainTypo(email); typo != "" {
		return fmt.Errorf("%s: possible typo detected, did you mean %s?", fieldName, typo)
	}

	// 4. Prevent IP-based email addresses
	if ev.hasIPAddress(email) {
		return fmt.Errorf("%s: IP-based email addresses are not allowed", fieldName)
	}

	return nil
}

// isLikelyDisposable checks if email is from a known disposable email provider
// This is a basic implementation - in production, use a service like:
// - https://github.com/disposable/disposable-email-domains
// - or an API service
func (ev *EmailValidator) isLikelyDisposable(email string) bool {
	// Extract domain
	parts := strings.Split(email, "@")
	if len(parts) != 2 {
		return false
	}
	domain := strings.ToLower(parts[1])

	// Common disposable email patterns
	disposablePatterns := []string{
		"temp",
		"disposable",
		"throwaway",
		"guerrilla",
		"mailinator",
		"10minute",
		"trashmail",
		"yopmail",
		"fakeinbox",
	}

	for _, pattern := range disposablePatterns {
		if strings.Contains(domain, pattern) {
			return true
		}
	}

	// Known disposable domains (small sample - expand as needed)
	disposableDomains := map[string]bool{
		"mailinator.com":      true,
		"guerrillamail.com":   true,
		"10minutemail.com":    true,
		"tempmailaddress.com": true,
		"yopmail.com":         true,
		"fakeinbox.com":       true,
		"trashmail.com":       true,
		"throwaway.email":     true,
	}

	return disposableDomains[domain]
}

// detectCommonDomainTypo checks for common typos in popular email domains
func (ev *EmailValidator) detectCommonDomainTypo(email string) string {
	parts := strings.Split(email, "@")
	if len(parts) != 2 {
		return ""
	}

	localPart := parts[0]
	domain := strings.ToLower(parts[1])

	// Common typos map: typo -> correct
	typos := map[string]string{
		"gmial.com":     "gmail.com",
		"gmai.com":      "gmail.com",
		"gmil.com":      "gmail.com",
		"yahooo.com":    "yahoo.com",
		"yaho.com":      "yahoo.com",
		"hotmial.com":   "hotmail.com",
		"hotmal.com":    "hotmail.com",
		"outlok.com":    "outlook.com",
		"outloo.com":    "outlook.com",
		"iclodu.com":    "icloud.com",
		"iclod.com":     "icloud.com",
		"protonmai.com": "protonmail.com",
		"protonmal.com": "protonmail.com",
	}

	if correct, found := typos[domain]; found {
		return localPart + "@" + correct
	}

	return ""
}

// hasIPAddress checks if email domain is an IP address
func (ev *EmailValidator) hasIPAddress(email string) bool {
	parts := strings.Split(email, "@")
	if len(parts) != 2 {
		return false
	}

	domain := parts[1]

	// Check for IPv4 pattern: [192.168.1.1]
	if strings.HasPrefix(domain, "[") && strings.HasSuffix(domain, "]") {
		return true
	}

	// Check for unbracketed IP patterns (less common but possible)
	// Simple heuristic: contains only digits and dots
	hasOnlyDigitsAndDots := true
	for _, ch := range domain {
		if ch != '.' && (ch < '0' || ch > '9') {
			hasOnlyDigitsAndDots = false
			break
		}
	}

	return hasOnlyDigitsAndDots && strings.Count(domain, ".") >= 3
}

// normalizeGmail normalizes Gmail addresses by removing dots and plus-aliases
// Gmail ignores dots in the local part and treats everything after + as an alias
// Example: john.doe+test@gmail.com -> johndoe@gmail.com
func (ev *EmailValidator) normalizeGmail(email string) string {
	parts := strings.Split(email, "@")
	if len(parts) != 2 {
		return email
	}

	localPart := parts[0]
	domain := strings.ToLower(parts[1])

	// Only normalize for Gmail and Googlemail
	if domain != "gmail.com" && domain != "googlemail.com" {
		return email
	}

	// Remove dots from local part
	localPart = strings.ReplaceAll(localPart, ".", "")

	// Remove everything after + (plus-alias)
	if plusIndex := strings.Index(localPart, "+"); plusIndex != -1 {
		localPart = localPart[:plusIndex]
	}

	return localPart + "@" + domain
}

// ValidateEmailList validates a list of email addresses
// Returns the first error encountered, or nil if all are valid
func (ev *EmailValidator) ValidateEmailList(emails []string, fieldName string) ([]string, error) {
	normalized := make([]string, 0, len(emails))

	for i, email := range emails {
		norm, err := ev.ValidateAndNormalize(email, fmt.Sprintf("%s[%d]", fieldName, i))
		if err != nil {
			return nil, err
		}
		normalized = append(normalized, norm)
	}

	return normalized, nil
}

// IsValidEmailDomain checks if a domain is likely valid (has proper structure)
// This is a lightweight check - for production, consider DNS MX record validation
func (ev *EmailValidator) IsValidEmailDomain(email string) bool {
	parts := strings.Split(email, "@")
	if len(parts) != 2 {
		return false
	}

	domain := strings.ToLower(parts[1])

	// Must have at least one dot
	if !strings.Contains(domain, ".") {
		return false
	}

	// TLD must be at least 2 characters
	tldParts := strings.Split(domain, ".")
	if len(tldParts) < 2 {
		return false
	}

	tld := tldParts[len(tldParts)-1]
	if len(tld) < 2 {
		return false
	}

	return true
}
