package validation

import (
	"fmt"
	"net/mail"
	"net/url"
	"regexp"
	"strings"
	"time"
	"unicode"
)

// Common validation errors
var (
	ErrRequired          = fmt.Errorf("field is required")
	ErrInvalidEmail      = fmt.Errorf("invalid email format")
	ErrInvalidURL        = fmt.Errorf("invalid URL format")
	ErrInvalidDomain     = fmt.Errorf("invalid domain format")
	ErrTooShort          = fmt.Errorf("value is too short")
	ErrTooLong           = fmt.Errorf("value is too long")
	ErrInvalidCharacters = fmt.Errorf("contains invalid characters")
	ErrInvalidFormat     = fmt.Errorf("invalid format")
	ErrInvalidValue      = fmt.Errorf("invalid value")
	ErrWhitespaceOnly    = fmt.Errorf("cannot contain only whitespace")
	ErrContainsHTML      = fmt.Errorf("cannot contain HTML tags")
	ErrInvalidSlug       = fmt.Errorf("invalid slug format")
)

// Regex patterns for validation
var (
	// Email validation: RFC 5322 compliant
	emailRegex = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9._%+\-]*[a-zA-Z0-9]@[a-zA-Z0-9][a-zA-Z0-9.\-]*[a-zA-Z0-9]\.[a-zA-Z]{2,}$`)

	// Domain validation: alphanumeric with dots and hyphens
	domainRegex = regexp.MustCompile(`^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$`)

	// Slug validation: lowercase alphanumeric with hyphens
	slugRegex = regexp.MustCompile(`^[a-z0-9]+(?:-[a-z0-9]+)*$`)

	// HTML tag detection
	htmlTagRegex = regexp.MustCompile(`<[^>]+>`)

	// UUID validation (version 4)
	uuidRegex = regexp.MustCompile(`^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$`)

	// Alphanumeric only
	alphanumericRegex = regexp.MustCompile(`^[a-zA-Z0-9]+$`)
)

// Reserved slugs that cannot be used for tenant names
var ReservedSlugs = map[string]bool{
	"api":       true,
	"admin":     true,
	"www":       true,
	"mail":      true,
	"email":     true,
	"health":    true,
	"status":    true,
	"metrics":   true,
	"static":    true,
	"cdn":       true,
	"assets":    true,
	"blog":      true,
	"docs":      true,
	"help":      true,
	"support":   true,
	"login":     true,
	"logout":    true,
	"signup":    true,
	"register":  true,
	"app":       true,
	"dashboard": true,
	"settings":  true,
	"account":   true,
	"profile":   true,
	"root":      true,
	"system":    true,
	"public":    true,
	"private":   true,
}

// Validator provides input validation utilities
type Validator struct{}

// NewValidator creates a new validator instance
func NewValidator() *Validator {
	return &Validator{}
}

// ==================== String Validation ====================

// ValidateRequired checks if a string is not empty
func (v *Validator) ValidateRequired(value, fieldName string) error {
	if strings.TrimSpace(value) == "" {
		return fmt.Errorf("%s: %w", fieldName, ErrRequired)
	}
	return nil
}

// ValidateLength checks if string length is within range
func (v *Validator) ValidateLength(value, fieldName string, min, max int) error {
	length := len(strings.TrimSpace(value))

	if length < min {
		return fmt.Errorf("%s: %w (minimum %d characters)", fieldName, ErrTooShort, min)
	}

	if max > 0 && length > max {
		return fmt.Errorf("%s: %w (maximum %d characters)", fieldName, ErrTooLong, max)
	}

	return nil
}

// ValidateNotWhitespaceOnly ensures the string contains non-whitespace characters
func (v *Validator) ValidateNotWhitespaceOnly(value, fieldName string) error {
	if len(strings.TrimSpace(value)) == 0 && len(value) > 0 {
		return fmt.Errorf("%s: %w", fieldName, ErrWhitespaceOnly)
	}
	return nil
}

// ValidateNoHTML checks that the string doesn't contain HTML tags
func (v *Validator) ValidateNoHTML(value, fieldName string) error {
	if htmlTagRegex.MatchString(value) {
		return fmt.Errorf("%s: %w", fieldName, ErrContainsHTML)
	}
	return nil
}

// ValidateAlphanumeric checks if string contains only alphanumeric characters
func (v *Validator) ValidateAlphanumeric(value, fieldName string) error {
	if !alphanumericRegex.MatchString(value) {
		return fmt.Errorf("%s: %w (only letters and numbers allowed)", fieldName, ErrInvalidCharacters)
	}
	return nil
}

// ValidatePrintable ensures string contains only printable characters
func (v *Validator) ValidatePrintable(value, fieldName string) error {
	for _, r := range value {
		if !unicode.IsPrint(r) && !unicode.IsSpace(r) {
			return fmt.Errorf("%s: %w (contains non-printable characters)", fieldName, ErrInvalidCharacters)
		}
	}
	return nil
}

// ==================== Email Validation ====================

// ValidateEmail validates email format using RFC 5322 compliant regex
func (v *Validator) ValidateEmail(email, fieldName string) error {
	email = strings.TrimSpace(email)

	// Check required
	if email == "" {
		return fmt.Errorf("%s: %w", fieldName, ErrRequired)
	}

	// Check length (RFC 5321: max 320 chars)
	if len(email) > 320 {
		return fmt.Errorf("%s: %w (maximum 320 characters)", fieldName, ErrTooLong)
	}

	// Validate using regex
	if !emailRegex.MatchString(email) {
		return fmt.Errorf("%s: %w", fieldName, ErrInvalidEmail)
	}

	// Additional validation using net/mail package
	_, err := mail.ParseAddress(email)
	if err != nil {
		return fmt.Errorf("%s: %w", fieldName, ErrInvalidEmail)
	}

	// Check for consecutive dots
	if strings.Contains(email, "..") {
		return fmt.Errorf("%s: %w (consecutive dots not allowed)", fieldName, ErrInvalidEmail)
	}

	// Check for leading/trailing dots in local part
	parts := strings.Split(email, "@")
	if len(parts) == 2 {
		if strings.HasPrefix(parts[0], ".") || strings.HasSuffix(parts[0], ".") {
			return fmt.Errorf("%s: %w (local part cannot start or end with dot)", fieldName, ErrInvalidEmail)
		}
	}

	return nil
}

// ==================== URL Validation ====================

// ValidateURL validates URL format and ensures it has a valid scheme
func (v *Validator) ValidateURL(urlStr, fieldName string) error {
	urlStr = strings.TrimSpace(urlStr)

	// Check required
	if urlStr == "" {
		return fmt.Errorf("%s: %w", fieldName, ErrRequired)
	}

	// Check length (max 2048 chars for URL)
	if len(urlStr) > 2048 {
		return fmt.Errorf("%s: %w (maximum 2048 characters)", fieldName, ErrTooLong)
	}

	// Parse URL
	parsedURL, err := url.Parse(urlStr)
	if err != nil {
		return fmt.Errorf("%s: %w", fieldName, ErrInvalidURL)
	}

	// Ensure scheme is present and valid
	if parsedURL.Scheme == "" {
		return fmt.Errorf("%s: %w (missing scheme)", fieldName, ErrInvalidURL)
	}

	// Only allow http and https
	if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" {
		return fmt.Errorf("%s: %w (only http and https schemes allowed)", fieldName, ErrInvalidURL)
	}

	// Ensure host is present
	if parsedURL.Host == "" {
		return fmt.Errorf("%s: %w (missing host)", fieldName, ErrInvalidURL)
	}

	return nil
}

// ValidateHTTPSURL validates URL and ensures it uses HTTPS
func (v *Validator) ValidateHTTPSURL(urlStr, fieldName string) error {
	if err := v.ValidateURL(urlStr, fieldName); err != nil {
		return err
	}

	parsedURL, err := url.Parse(urlStr)
	if err != nil {
		return fmt.Errorf("%s: invalid URL format", fieldName)
	}
	if parsedURL.Scheme != "https" {
		return fmt.Errorf("%s: must use HTTPS protocol", fieldName)
	}

	return nil
}

// ==================== Domain Validation ====================

// ValidateDomain validates domain name format
// Supports standard domains (example.com) and localhost with ports (localhost:8081) for development
func (v *Validator) ValidateDomain(domain, fieldName string) error {
	domain = strings.TrimSpace(strings.ToLower(domain))

	// Check required
	if domain == "" {
		return fmt.Errorf("%s: %w", fieldName, ErrRequired)
	}

	// Check length (max 253 chars per RFC 1035)
	if len(domain) > 253 {
		return fmt.Errorf("%s: %w (maximum 253 characters)", fieldName, ErrTooLong)
	}

	// Check minimum length
	if len(domain) < 4 {
		return fmt.Errorf("%s: %w (minimum 4 characters)", fieldName, ErrTooShort)
	}

	// Allow localhost with optional port for development
	// Examples: localhost, localhost:8080, localhost:3000
	if strings.HasPrefix(domain, "localhost") {
		// If it has a port, validate the port format
		if strings.Contains(domain, ":") {
			parts := strings.Split(domain, ":")
			if len(parts) != 2 {
				return fmt.Errorf("%s: %w (invalid localhost format)", fieldName, ErrInvalidDomain)
			}
			// Port should be numeric
			if parts[1] == "" {
				return fmt.Errorf("%s: %w (missing port number)", fieldName, ErrInvalidDomain)
			}
			// Basic port validation (could be more strict)
			for _, c := range parts[1] {
				if c < '0' || c > '9' {
					return fmt.Errorf("%s: %w (port must be numeric)", fieldName, ErrInvalidDomain)
				}
			}
		}
		return nil
	}

	// Allow 127.0.0.1 and other local IPs with optional port for development
	if strings.HasPrefix(domain, "127.") || strings.HasPrefix(domain, "192.168.") || strings.HasPrefix(domain, "10.") {
		// If it has a port, just verify format (IP:port)
		if strings.Contains(domain, ":") {
			parts := strings.Split(domain, ":")
			if len(parts) != 2 {
				return fmt.Errorf("%s: %w (invalid IP format)", fieldName, ErrInvalidDomain)
			}
		}
		return nil
	}

	// Validate standard domain format (example.com)
	if !domainRegex.MatchString(domain) {
		return fmt.Errorf("%s: %w", fieldName, ErrInvalidDomain)
	}

	// Check each label length (max 63 chars per RFC 1035)
	labels := strings.Split(domain, ".")
	for _, label := range labels {
		if len(label) > 63 {
			return fmt.Errorf("%s: %w (label exceeds 63 characters)", fieldName, ErrInvalidDomain)
		}
	}

	return nil
}

// ==================== Slug Validation ====================

// ValidateSlug validates slug format (lowercase alphanumeric with hyphens)
func (v *Validator) ValidateSlug(slug, fieldName string) error {
	slug = strings.TrimSpace(strings.ToLower(slug))

	// Check required
	if slug == "" {
		return fmt.Errorf("%s: %w", fieldName, ErrRequired)
	}

	// Check length (3-63 chars)
	if len(slug) < 3 {
		return fmt.Errorf("%s: %w (minimum 3 characters)", fieldName, ErrTooShort)
	}

	if len(slug) > 63 {
		return fmt.Errorf("%s: %w (maximum 63 characters)", fieldName, ErrTooLong)
	}

	// Validate format
	if !slugRegex.MatchString(slug) {
		return fmt.Errorf("%s: %w (only lowercase letters, numbers, and hyphens allowed)", fieldName, ErrInvalidSlug)
	}

	// Check for reserved slugs
	if ReservedSlugs[slug] {
		return fmt.Errorf("%s: '%s' is a reserved slug and cannot be used", fieldName, slug)
	}

	return nil
}

// GenerateSlug generates a URL-friendly slug from a name
// Converts to lowercase, replaces spaces and special chars with hyphens
// Ensures the slug matches the slug validation regex
func (v *Validator) GenerateSlug(name string) string {
	// Convert to lowercase and trim spaces
	slug := strings.TrimSpace(strings.ToLower(name))

	// Replace any non-alphanumeric characters (except hyphens) with hyphens
	var result strings.Builder
	prevWasHyphen := false

	for _, char := range slug {
		if (char >= 'a' && char <= 'z') || (char >= '0' && char <= '9') {
			result.WriteRune(char)
			prevWasHyphen = false
		} else if !prevWasHyphen {
			// Replace any non-alphanumeric character with a hyphen
			// But don't add consecutive hyphens
			result.WriteRune('-')
			prevWasHyphen = true
		}
	}

	slug = result.String()

	// Remove leading and trailing hyphens
	slug = strings.Trim(slug, "-")

	// Enforce length constraints (3-63 chars)
	if len(slug) < 3 {
		// If too short, pad with random suffix
		slug = slug + "-" + strings.ToLower(fmt.Sprintf("%d", time.Now().UnixNano()%10000))
	}

	if len(slug) > 63 {
		// Truncate to 63 chars
		slug = slug[:63]
		// Remove trailing hyphen if any
		slug = strings.TrimRight(slug, "-")
	}

	return slug
}

// ==================== UUID Validation ====================

// ValidateUUID validates UUID format (version 4)
func (v *Validator) ValidateUUID(id, fieldName string) error {
	id = strings.TrimSpace(strings.ToLower(id))

	// Check required
	if id == "" {
		return fmt.Errorf("%s: %w", fieldName, ErrRequired)
	}

	// Validate format
	if !uuidRegex.MatchString(id) {
		return fmt.Errorf("%s: %w (must be a valid UUID v4)", fieldName, ErrInvalidFormat)
	}

	return nil
}

// ==================== Enum Validation ====================

// ValidateEnum checks if value is in the allowed list (whitelist validation)
func (v *Validator) ValidateEnum(value, fieldName string, allowedValues []string) error {
	value = strings.TrimSpace(value)

	// Check required
	if value == "" {
		return fmt.Errorf("%s: %w", fieldName, ErrRequired)
	}

	// Check if value is in allowed list
	for _, allowed := range allowedValues {
		if value == allowed {
			return nil
		}
	}

	return fmt.Errorf("%s: %w (allowed values: %s)", fieldName, ErrInvalidValue, strings.Join(allowedValues, ", "))
}

// ==================== Number Validation ====================

// ValidateRange checks if a number is within the specified range
func (v *Validator) ValidateRange(value int, fieldName string, min, max int) error {
	if value < min {
		return fmt.Errorf("%s: value must be at least %d", fieldName, min)
	}

	if max > 0 && value > max {
		return fmt.Errorf("%s: value must be at most %d", fieldName, max)
	}

	return nil
}

// ==================== Sanitization ====================

// SanitizeString removes potentially dangerous characters and trims whitespace
func (v *Validator) SanitizeString(value string) string {
	// Trim whitespace
	value = strings.TrimSpace(value)

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

	// Normalize Unicode
	// Note: For production, consider using golang.org/x/text/unicode/norm

	return value
}

// StripHTML removes all HTML tags from a string
func (v *Validator) StripHTML(value string) string {
	return htmlTagRegex.ReplaceAllString(value, "")
}

// ==================== Combined Validations ====================

// ValidateAndSanitizeString performs validation and sanitization
func (v *Validator) ValidateAndSanitizeString(value, fieldName string, minLen, maxLen int) (string, error) {
	// Sanitize first
	value = v.SanitizeString(value)

	// Validate required
	if err := v.ValidateRequired(value, fieldName); err != nil {
		return "", err
	}

	// Validate length
	if err := v.ValidateLength(value, fieldName, minLen, maxLen); err != nil {
		return "", err
	}

	// Validate printable characters
	if err := v.ValidatePrintable(value, fieldName); err != nil {
		return "", err
	}

	return value, nil
}
