package validation

import (
	"errors"
	"regexp"
	"slices"
	"strconv"
	"strings"
	"unicode/utf8"
)

func SanitizeEmail(input string) string {
	input = strings.ToLower(strings.TrimSpace(input))

	re := regexp.MustCompile(`[a-z0-9!#$%&'*+/=?^_` + "`" + `{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_` + "`" + `{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?`)
	match := re.FindString(input)

	return match
}

// Checks an email address against a first and last name and returns true if it matches one of the following patterns:
// firstlast@domain.tld, flast@domain.tld, firstl@domain.tld, first.last@domain.tld, f.last@domain.tld,
// first.l@domain.tld, first-last@domain.tld, f-last@domain.tld, first-l@domain.tld. Otherwise, false.
// Also handles hyphenated and space-separated last names (e.g., Meyer-Ogren or Van Der Berg)
func DoesNameMatchEmail(email string, firstName string, lastName string) bool {
	// Validate inputs
	if email == "" || firstName == "" || lastName == "" {
		return true
	}

	email = strings.ToLower(email)
	firstName = strings.ToLower(firstName)
	lastName = strings.ToLower(lastName)

	emailIdentifier := strings.Split(email, "@")[0]
	firstInitial := string(firstName[0])

	// Split hyphenated and space-separated last names
	lastNameParts := []string{lastName}

	// Also add concatenated version (removing hyphens/spaces)
	lastNameNormalized := strings.ReplaceAll(lastName, "-", "")
	lastNameNormalized = strings.ReplaceAll(lastNameNormalized, " ", "")
	if lastNameNormalized != lastName {
		lastNameParts = append(lastNameParts, lastNameNormalized)
	}

	// Add individual parts
	if strings.Contains(lastName, "-") {
		lastNameParts = append(lastNameParts, strings.Split(lastName, "-")...)
	}
	if strings.Contains(lastName, " ") {
		lastNameParts = append(lastNameParts, strings.Split(lastName, " ")...)
	}

	separators := []string{"", ".", "-"}

	// Check standalone first or last names
	if emailIdentifier == firstName {
		return true
	}

	// Check each part of the last name
	for _, lastPart := range lastNameParts {
		if lastPart == "" {
			continue
		}

		if emailIdentifier == lastPart {
			return true
		}

		lastPartInitial := string(lastPart[0])

		// Check all separator combinations with each last name part
		for _, separator := range separators {
			switch emailIdentifier {
			case strings.Join([]string{firstName, lastPart}, separator),
				strings.Join([]string{firstInitial, lastPart}, separator),
				strings.Join([]string{firstName, lastPartInitial}, separator):
				return true
			}
		}
	}

	return false
}

func SanitizePhone(input string) string {
	// Regular expression to match non-digit characters
	re := regexp.MustCompile(`[^0-9]`)

	// Replace non-digit characters with empty string
	sanitized := re.ReplaceAllString(input, "")

	return sanitized
}

func ValidatePhoneNumber(phoneNumber string) error {
	isNumber, _ := regexp.MatchString(`^\d+$`, phoneNumber)
	if !isNumber {
		return errors.New("Phone number must only contain numbers.")
	}

	if len(phoneNumber) != 10 {
		return errors.New("Invalid phone number length.")
	}

	areaCode, _ := strconv.Atoi(phoneNumber[:3])

	// Lowest area code is 200, so less than (200) 000-0000
	if areaCode < 200 {
		return errors.New("Phone number has invalid area code.")
	}

	return nil
}

// Removes invalid UTF-8 sequences and replaces special characters
func SanitizeString(s string) string {
	// Replace non-breaking spaces (0xa0) with regular spaces
	s = strings.ReplaceAll(s, "\u00a0", " ")

	// Remove other common problematic characters
	s = strings.ReplaceAll(s, "\r", "")

	// Remove replacement character (�) that appears when invalid UTF-8 is encountered
	s = strings.ReplaceAll(s, "\ufffd", "")

	// Validate and fix UTF-8 encoding
	if !utf8.ValidString(s) {
		// Convert to valid UTF-8 by removing invalid bytes
		v := make([]rune, 0, len(s))
		for _, r := range s {
			if r != utf8.RuneError {
				v = append(v, r)
			}
		}
		s = string(v)
	}

	// Trim whitespace
	return strings.TrimSpace(s)
}

var stateCodes []string = []string{
	"AL", "AK", "AZ", "AR", "AS", "CA", "CO", "CT", "DE", "DC", "FL", "GA",
	"GU", "HI", "ID", "IL", "IN", "IA", "KS", "KY", "LA", "ME", "MD", "MA",
	"MI", "MN", "MS", "MO", "MT", "NE", "NV", "NH", "NJ", "NM", "NY", "NC",
	"ND", "MP", "OH", "OK", "OR", "PA", "PR", "RI", "SC", "SD", "TN", "TX",
	"TT", "UT", "VT", "VA", "VI", "WA", "WV", "WI", "WY",
}

// Checks if the input string is a proper two-letter state code
func ValidateStateCode(s string) error {
	if slices.Contains(stateCodes, s) {
		return nil
	}

	return errors.New("Invalid state code.")
}

func ValidateTaxId(id string) error {
	isNumber, _ := regexp.MatchString(`^\d+$`, id)
	if !isNumber {
		return errors.New("Tax ID must only contain numbers.")
	}

	if len(id) != 9 {
		return errors.New("Tax ID must be 9 digits long.")
	}

	idNum, _ := strconv.Atoi(id)
	if idNum == 0 {
		return errors.New("Tax ID cannot be 00-0000000.")
	}

	return nil
}

func ValidateZipCode(zip string) error {
	isNumber, _ := regexp.MatchString(`^\d+$`, zip)
	if !isNumber {
		return errors.New("ZIP code must only contain numbers.")
	}

	if (len(zip) != 5) && (len(zip) != 9) {
		return errors.New("Invalid length for ZIP code.")
	}

	zipCode, _ := strconv.Atoi(zip[:5])

	// Lowest 5 digit ZIP code is 00501
	if zipCode < 501 {
		return errors.New("Invalid ZIP code.")
	}

	return nil
}

func ValidateUrl(url string) error {
	err := errors.New("Invalid URL.")

	valid, _ := regexp.MatchString(`[(http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)`, url)

	if !valid {
		return err
	}

	return nil
}
