package utils

import (
	"crypto/rand"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"math/big"
	"regexp"
	"strings"
	"time"
	"unicode"

	"golang.org/x/crypto/bcrypt"
)

// GenerateID génère un ID unique
func GenerateID() string {
	b := make([]byte, 16)
	rand.Read(b)
	return hex.EncodeToString(b)
}

// GenerateUUID génère un UUID v4
func GenerateUUID() string {
	b := make([]byte, 16)
	rand.Read(b)

	// Version 4
	b[6] = (b[6] & 0x0f) | 0x40
	// Variant bits
	b[8] = (b[8] & 0x3f) | 0x80

	return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:16])
}

// GenerateRandomString génère une chaîne aléatoire de longueur donnée
func GenerateRandomString(length int) string {
	const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
	b := make([]byte, length)
	for i := range b {
		num, _ := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
		b[i] = charset[num.Int64()]
	}
	return string(b)
}

// GenerateRandomBytes génère des bytes aléatoires
func GenerateRandomBytes(length int) ([]byte, error) {
	b := make([]byte, length)
	_, err := rand.Read(b)
	return b, err
}

// HashPassword hash un mot de passe avec bcrypt
func HashPassword(password string) (string, error) {
	hashedBytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
	if err != nil {
		return "", fmt.Errorf("failed to hash password: %w", err)
	}
	return string(hashedBytes), nil
}

// VerifyPassword vérifie un mot de passe contre son hash
func VerifyPassword(hashedPassword, password string) error {
	return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
}

// CheckPasswordHash est un alias pour VerifyPassword (compatibilité)
func CheckPasswordHash(password, hashedPassword string) error {
	return VerifyPassword(hashedPassword, password)
}

// HashSHA256 hash une chaîne avec SHA256
func HashSHA256(input string) string {
	hash := sha256.Sum256([]byte(input))
	return hex.EncodeToString(hash[:])
}

// ValidateEmail valide le format d'un email
func ValidateEmail(email string) bool {
	emailRegex := regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
	return emailRegex.MatchString(email)
}

// ValidatePasswordStrength is now in password_validator.go
// T0197: Moved to password_validator.go for better organization

// ValidateUsername valide le format d'un nom d'utilisateur
func ValidateUsername(username string) (bool, []string) {
	var errors []string

	if len(username) < 3 {
		errors = append(errors, "Username must be at least 3 characters long")
	}

	if len(username) > 30 {
		errors = append(errors, "Username must be less than 30 characters")
	}

	usernameRegex := regexp.MustCompile(`^[a-zA-Z0-9_-]+$`)
	if !usernameRegex.MatchString(username) {
		errors = append(errors, "Username can only contain letters, numbers, underscores, and hyphens")
	}

	return len(errors) == 0, errors
}

// SanitizeString nettoie une chaîne de caractères
func SanitizeString(input string) string {
	// Supprimer les caractères de contrôle
	cleaned := strings.Map(func(r rune) rune {
		if r < 32 || r == 127 {
			return -1
		}
		return r
	}, input)

	// Supprimer les espaces en début et fin
	cleaned = strings.TrimSpace(cleaned)

	// Limiter la longueur
	if len(cleaned) > 1000 {
		cleaned = cleaned[:1000]
	}

	return cleaned
}

// SanitizeHTML is now in sanitizer.go with enhanced functionality
// BE-SEC-009: Moved to sanitizer.go for better organization and security

// TruncateString tronque une chaîne à la longueur spécifiée
func TruncateString(input string, maxLength int) string {
	if len(input) <= maxLength {
		return input
	}
	return input[:maxLength] + "..."
}

// ContainsString vérifie si une chaîne contient une sous-chaîne (insensible à la casse)
func ContainsString(s, substr string) bool {
	return strings.Contains(strings.ToLower(s), strings.ToLower(substr))
}

// IsEmpty vérifie si une chaîne est vide ou ne contient que des espaces
func IsEmpty(s string) bool {
	return strings.TrimSpace(s) == ""
}

// IsNotEmpty vérifie si une chaîne n'est pas vide
func IsNotEmpty(s string) bool {
	return !IsEmpty(s)
}

// FormatDuration formate une durée en chaîne lisible
func FormatDuration(d time.Duration) string {
	if d < time.Minute {
		return fmt.Sprintf("%.0fs", d.Seconds())
	}
	if d < time.Hour {
		return fmt.Sprintf("%.0fm", d.Minutes())
	}
	if d < 24*time.Hour {
		return fmt.Sprintf("%.1fh", d.Hours())
	}
	return fmt.Sprintf("%.1fd", d.Hours()/24)
}

// FormatFileSize formate une taille de fichier en chaîne lisible
func FormatFileSize(bytes int64) string {
	const unit = 1024
	if bytes < unit {
		return fmt.Sprintf("%d B", bytes)
	}
	div, exp := int64(unit), 0
	for n := bytes / unit; n >= unit; n /= unit {
		div *= unit
		exp++
	}
	return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
}

// FormatNumber formate un nombre avec des séparateurs de milliers
func FormatNumber(n int64) string {
	str := fmt.Sprintf("%d", n)
	if len(str) <= 3 {
		return str
	}

	var result strings.Builder
	for i, char := range str {
		if i > 0 && (len(str)-i)%3 == 0 {
			result.WriteString(",")
		}
		result.WriteRune(char)
	}
	return result.String()
}

// ParseDuration parse une durée depuis une chaîne
func ParseDuration(s string) (time.Duration, error) {
	// Supprimer les espaces
	s = strings.TrimSpace(s)

	// Ajouter 's' si pas d'unité spécifiée
	if !strings.ContainsAny(s, "smhd") {
		s += "s"
	}

	return time.ParseDuration(s)
}

// IsValidURL vérifie si une chaîne est une URL valide
func IsValidURL(url string) bool {
	urlRegex := regexp.MustCompile(`^https?://[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(/.*)?$`)
	return urlRegex.MatchString(url)
}

// ExtractDomain extrait le domaine d'une URL
func ExtractDomain(url string) string {
	// Supprimer le protocole
	if strings.HasPrefix(url, "http://") {
		url = url[7:]
	} else if strings.HasPrefix(url, "https://") {
		url = url[8:]
	}

	// Supprimer le chemin
	if idx := strings.Index(url, "/"); idx != -1 {
		url = url[:idx]
	}

	return url
}

// GenerateSlug génère un slug à partir d'une chaîne
func GenerateSlug(input string) string {
	// Convertir en minuscules
	slug := strings.ToLower(input)

	// Remplacer les espaces par des tirets
	slug = strings.ReplaceAll(slug, " ", "-")

	// Supprimer les caractères non alphanumériques sauf les tirets
	slug = regexp.MustCompile(`[^a-z0-9-]`).ReplaceAllString(slug, "")

	// Supprimer les tirets multiples
	slug = regexp.MustCompile(`-+`).ReplaceAllString(slug, "-")

	// Supprimer les tirets en début et fin
	slug = strings.Trim(slug, "-")

	return slug
}

// ContainsOnlyDigits vérifie si une chaîne ne contient que des chiffres
func ContainsOnlyDigits(s string) bool {
	for _, char := range s {
		if !unicode.IsDigit(char) {
			return false
		}
	}
	return true
}

// ContainsOnlyLetters vérifie si une chaîne ne contient que des lettres
func ContainsOnlyLetters(s string) bool {
	for _, char := range s {
		if !unicode.IsLetter(char) {
			return false
		}
	}
	return true
}

// ContainsOnlyAlphanumeric vérifie si une chaîne ne contient que des caractères alphanumériques
func ContainsOnlyAlphanumeric(s string) bool {
	for _, char := range s {
		if !unicode.IsLetter(char) && !unicode.IsDigit(char) {
			return false
		}
	}
	return true
}

// RemoveDuplicates supprime les doublons d'une slice de chaînes
func RemoveDuplicates(slice []string) []string {
	keys := make(map[string]bool)
	var result []string

	for _, item := range slice {
		if !keys[item] {
			keys[item] = true
			result = append(result, item)
		}
	}

	return result
}

// Contains vérifie si une slice contient un élément
func Contains(slice []string, item string) bool {
	for _, s := range slice {
		if s == item {
			return true
		}
	}
	return false
}

// IndexOf retourne l'index d'un élément dans une slice
func IndexOf(slice []string, item string) int {
	for i, s := range slice {
		if s == item {
			return i
		}
	}
	return -1
}

// Reverse inverse l'ordre d'une slice
func Reverse(slice []string) []string {
	for i, j := 0, len(slice)-1; i < j; i, j = i+1, j-1 {
		slice[i], slice[j] = slice[j], slice[i]
	}
	return slice
}

// Chunk divise une slice en chunks de taille donnée
func Chunk(slice []string, chunkSize int) [][]string {
	var chunks [][]string

	for i := 0; i < len(slice); i += chunkSize {
		end := i + chunkSize
		if end > len(slice) {
			end = len(slice)
		}
		chunks = append(chunks, slice[i:end])
	}

	return chunks
}

// Filter filtre une slice selon une condition
func Filter(slice []string, predicate func(string) bool) []string {
	var result []string

	for _, item := range slice {
		if predicate(item) {
			result = append(result, item)
		}
	}

	return result
}

// Map applique une fonction à chaque élément d'une slice
func Map(slice []string, mapper func(string) string) []string {
	var result []string

	for _, item := range slice {
		result = append(result, mapper(item))
	}

	return result
}

// Reduce réduit une slice à une seule valeur
func Reduce(slice []string, initial string, reducer func(string, string) string) string {
	result := initial

	for _, item := range slice {
		result = reducer(result, item)
	}

	return result
}
