package validators

import (
	"regexp"
	"strings"
)

var (
	hasUpper   = regexp.MustCompile(`[A-Z]`)
	hasLower   = regexp.MustCompile(`[a-z]`)
	hasNumber  = regexp.MustCompile(`[0-9]`)
	hasSpecial = regexp.MustCompile(`[!@#$%^&*(),.?":{}|<>]`)

	// Common weak passwords list
	commonPasswords = []string{
		"password", "12345678", "123456789", "1234567890",
		"qwerty", "abc123", "monkey", "1234567", "letmein",
		"trustno1", "dragon", "baseball", "iloveyou", "master",
		"sunshine", "ashley", "bailey", "passw0rd", "shadow",
		"123123", "654321", "superman", "qazwsx", "michael",
		"football", "welcome", "jesus", "ninja", "mustang",
		"password1", "admin", "1234", "12345", "123456",
	}
)

// PasswordValidator valide la force d'un mot de passe
type PasswordValidator struct {
	MinLength int
}

// NewPasswordValidator crée une nouvelle instance de PasswordValidator
func NewPasswordValidator() *PasswordValidator {
	return &PasswordValidator{MinLength: 12}
}

// PasswordStrength représente le résultat de la validation d'un mot de passe
type PasswordStrength struct {
	Valid   bool
	Score   int
	Details []string
}

// Validate valide la force d'un mot de passe selon les règles définies
// BE-SEC-006: Implement comprehensive password strength validation
func (v *PasswordValidator) Validate(password string) (PasswordStrength, error) {
	strength := PasswordStrength{
		Valid:   true,
		Details: []string{},
	}

	// Length check
	if len(password) < v.MinLength {
		strength.Valid = false
		strength.Details = append(strength.Details,
			"Password must be at least 12 characters long")
		return strength, nil
	}

	// Maximum length check (prevent DoS)
	if len(password) > 128 {
		strength.Valid = false
		strength.Details = append(strength.Details,
			"Password must be less than 128 characters")
		return strength, nil
	}

	// Check for common weak passwords
	// Only reject if password IS exactly a common password
	// This is less aggressive than checking if password contains common words,
	// which would reject valid passwords like "VerySecurePassword123!@#"
	passwordLower := strings.ToLower(password)
	for _, common := range commonPasswords {
		// Exact match only
		if passwordLower == common {
			strength.Valid = false
			strength.Details = append(strength.Details,
				"Password contains common words or patterns")
			return strength, nil
		}
	}

	// Check for repetitive patterns (e.g., "aaaa", "1234", "abcd")
	if hasRepetitivePattern(password) {
		strength.Valid = false
		strength.Details = append(strength.Details,
			"Password contains repetitive patterns")
		return strength, nil
	}

	// Check for sequential patterns (e.g., "1234", "abcd", "qwerty")
	if hasSequentialPattern(password) {
		strength.Valid = false
		strength.Details = append(strength.Details,
			"Password contains sequential patterns")
		return strength, nil
	}

	// Upper case check
	if !hasUpper.MatchString(password) {
		strength.Valid = false
		strength.Details = append(strength.Details, "Must contain uppercase letter")
	} else {
		strength.Score++
	}

	// Lower case check
	if !hasLower.MatchString(password) {
		strength.Valid = false
		strength.Details = append(strength.Details, "Must contain lowercase letter")
	} else {
		strength.Score++
	}

	// Number check
	if !hasNumber.MatchString(password) {
		strength.Valid = false
		strength.Details = append(strength.Details, "Must contain number")
	} else {
		strength.Score++
	}

	// Special character check
	if !hasSpecial.MatchString(password) {
		strength.Valid = false
		strength.Details = append(strength.Details, "Must contain special character")
	} else {
		strength.Score++
	}

	return strength, nil
}

// hasRepetitivePattern checks if password contains repetitive characters
func hasRepetitivePattern(password string) bool {
	if len(password) < 4 {
		return false
	}

	// Check for 4+ consecutive identical characters
	count := 1
	for i := 1; i < len(password); i++ {
		if password[i] == password[i-1] {
			count++
			if count >= 4 {
				return true
			}
		} else {
			count = 1
		}
	}
	return false
}

// hasSequentialPattern checks if password contains sequential characters
func hasSequentialPattern(password string) bool {
	if len(password) < 4 {
		return false
	}

	passwordLower := strings.ToLower(password)

	// Check for sequential patterns (e.g., "1234", "abcd", "qwerty")
	sequences := []string{
		"0123456789", "9876543210",
		"abcdefghijklmnopqrstuvwxyz", "zyxwvutsrqponmlkjihgfedcba",
		"qwertyuiop", "poiuytrewq",
		"asdfghjkl", "lkjhgfdsa",
		"zxcvbnm", "mnbvcxz",
	}

	for _, seq := range sequences {
		for i := 0; i <= len(seq)-4; i++ {
			subseq := seq[i : i+4]
			if strings.Contains(passwordLower, subseq) {
				return true
			}
		}
	}

	return false
}

// ValidatePasswordChange validates a new password against the old password
// BE-SEC-006: Ensure new password is sufficiently different from old password
func (v *PasswordValidator) ValidatePasswordChange(newPassword, oldPassword string) (PasswordStrength, error) {
	strength, err := v.Validate(newPassword)
	if err != nil || !strength.Valid {
		return strength, err
	}

	// Check if new password is too similar to old password
	if oldPassword != "" {
		similarity := calculateSimilarity(newPassword, oldPassword)
		if similarity > 0.7 {
			strength.Valid = false
			strength.Details = append(strength.Details,
				"New password is too similar to the old password")
			return strength, nil
		}

		// Check if new password contains old password
		if strings.Contains(strings.ToLower(newPassword), strings.ToLower(oldPassword)) {
			strength.Valid = false
			strength.Details = append(strength.Details,
				"New password cannot contain the old password")
			return strength, nil
		}
	}

	return strength, nil
}

// calculateSimilarity calculates the similarity ratio between two strings
func calculateSimilarity(s1, s2 string) float64 {
	if len(s1) == 0 && len(s2) == 0 {
		return 1.0
	}
	if len(s1) == 0 || len(s2) == 0 {
		return 0.0
	}

	// Simple Levenshtein-like similarity
	maxLen := len(s1)
	if len(s2) > maxLen {
		maxLen = len(s2)
	}

	matches := 0
	minLen := len(s1)
	if len(s2) < minLen {
		minLen = len(s2)
	}

	for i := 0; i < minLen; i++ {
		if s1[i] == s2[i] {
			matches++
		}
	}

	return float64(matches) / float64(maxLen)
}
