// Package httpvalidation provides consistent request validation across services.
//
// This package wraps go-playground/validator/v10 with a simpler API
// and human-readable error messages suitable for API responses.
//
// Usage:
//
//	type CreateUserRequest struct {
//	    Email string `json:"email" validate:"required,email"`
//	    Phone string `json:"phone" validate:"omitempty,e164"`
//	}
//
//	func CreateUser(w http.ResponseWriter, r *http.Request) {
//	    var req CreateUserRequest
//	    if err := httpresponse.DecodeJSON(r, &req); err != nil {
//	        httpresponse.BadRequest(w, r, "invalid JSON")
//	        return
//	    }
//	    if details := httpvalidation.ValidateStruct(req); len(details) > 0 {
//	        httpresponse.ValidationError(w, r, "validation failed", details)
//	        return
//	    }
//	    // ... proceed with valid request
//	}
package httpvalidation

import (
	"errors"
	"fmt"
	"reflect"
	"regexp"
	"strings"
	"sync"

	"github.com/go-playground/validator/v10"
	"github.com/google/uuid"
)

var (
	// Singleton validator instance
	once     sync.Once
	validate *validator.Validate

	// Regex patterns for custom validations
	// E.164 allows 1-15 digits total, with country code starting with 1-9
	phoneRegex = regexp.MustCompile(`^\+?[1-9]\d{4,14}$`)
)

// ValidationDetail represents a single field validation error.
// This structure is designed for API responses, providing clear
// field-level error information to clients.
type ValidationDetail struct {
	// Field is the JSON field name that failed validation.
	Field string `json:"field"`
	// Message is a human-readable description of the validation failure.
	Message string `json:"message"`
}

// Validator returns the singleton validator instance with all custom validators registered.
// Thread-safe and initialized only once.
func Validator() *validator.Validate {
	once.Do(func() {
		validate = validator.New()

		// Use JSON tag names in error messages
		validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
			name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
			if name == "-" || name == "" {
				return fld.Name
			}
			return name
		})

		// Register custom validators
		_ = validate.RegisterValidation("uuid", validateUUID)
		_ = validate.RegisterValidation("uuid_or_empty", validateUUIDOrEmpty)
		_ = validate.RegisterValidation("phone", validatePhone)
		_ = validate.RegisterValidation("slug", validateSlug)
		_ = validate.RegisterValidation("hex_color", validateHexColor)
	})
	return validate
}

// ValidateStruct validates a struct and returns a slice of ValidationDetail for any validation errors.
// Returns nil if validation passes.
//
// Example:
//
//	type CreateFanRequest struct {
//	    Email string `json:"email" validate:"required,email"`
//	    Phone string `json:"phone" validate:"omitempty,phone"`
//	}
//
//	if details := httpvalidation.ValidateStruct(req); len(details) > 0 {
//	    httpresponse.ValidationError(w, r, "validation failed", details)
//	    return
//	}
func ValidateStruct(s any) []ValidationDetail {
	v := Validator()
	err := v.Struct(s)
	if err == nil {
		return nil
	}

	var details []ValidationDetail

	// Use errors.As to handle wrapped errors
	var validationErrs validator.ValidationErrors
	if !errors.As(err, &validationErrs) {
		// If not validation errors, return generic error
		details = append(details, ValidationDetail{
			Field:   "unknown",
			Message: err.Error(),
		})
		return details
	}

	// Convert validator errors to ValidationDetails
	for _, e := range validationErrs {
		details = append(details, ValidationDetail{
			Field:   fieldName(e),
			Message: fieldError(e),
		})
	}

	return details
}

// ValidateVar validates a single variable against validation tags.
// Returns nil if validation passes, or a ValidationDetail slice with the error.
//
// Example:
//
//	if err := httpvalidation.ValidateVar(email, "required,email"); err != nil {
//	    // handle error
//	}
func ValidateVar(field any, tag string) []ValidationDetail {
	v := Validator()
	err := v.Var(field, tag)
	if err == nil {
		return nil
	}

	var validationErrs validator.ValidationErrors
	if !errors.As(err, &validationErrs) {
		return []ValidationDetail{{Field: "value", Message: err.Error()}}
	}

	if len(validationErrs) > 0 {
		return []ValidationDetail{{Field: "value", Message: fieldError(validationErrs[0])}}
	}

	return nil
}

// fieldName extracts the JSON field name from a validation error.
// Falls back to the struct field name if JSON tag is not present.
func fieldName(e validator.FieldError) string {
	field := e.Field()

	// Remove any struct prefix
	parts := strings.Split(field, ".")
	if len(parts) > 0 {
		field = parts[len(parts)-1]
	}

	// Convert to camelCase for API consistency
	if len(field) > 0 {
		return strings.ToLower(field[:1]) + field[1:]
	}
	return field
}

// fieldError generates a human-readable error message for a validation error.
func fieldError(e validator.FieldError) string {
	field := e.Field()
	tag := e.Tag()
	param := e.Param()

	switch tag {
	case "required":
		return fmt.Sprintf("%s is required", field)
	case "email":
		return fmt.Sprintf("%s must be a valid email address", field)
	case "min":
		if e.Kind() == reflect.String {
			return fmt.Sprintf("%s must be at least %s characters", field, param)
		}
		return fmt.Sprintf("%s must be at least %s", field, param)
	case "max":
		if e.Kind() == reflect.String {
			return fmt.Sprintf("%s must be at most %s characters", field, param)
		}
		return fmt.Sprintf("%s must be at most %s", field, param)
	case "len":
		if e.Kind() == reflect.String {
			return fmt.Sprintf("%s must be exactly %s characters", field, param)
		}
		return fmt.Sprintf("%s must have exactly %s items", field, param)
	case "uuid":
		return fmt.Sprintf("%s must be a valid UUID", field)
	case "uuid_or_empty":
		return fmt.Sprintf("%s must be a valid UUID or empty", field)
	case "phone", "e164":
		return fmt.Sprintf("%s must be a valid phone number in E.164 format", field)
	case "url":
		return fmt.Sprintf("%s must be a valid URL", field)
	case "oneof":
		return fmt.Sprintf("%s must be one of: %s", field, param)
	case "gt":
		return fmt.Sprintf("%s must be greater than %s", field, param)
	case "gte":
		return fmt.Sprintf("%s must be greater than or equal to %s", field, param)
	case "lt":
		return fmt.Sprintf("%s must be less than %s", field, param)
	case "lte":
		return fmt.Sprintf("%s must be less than or equal to %s", field, param)
	case "slug":
		return fmt.Sprintf("%s must be a valid slug (lowercase letters, numbers, hyphens)", field)
	case "hex_color":
		return fmt.Sprintf("%s must be a valid hex color code", field)
	case "alphanum":
		return fmt.Sprintf("%s must contain only alphanumeric characters", field)
	case "alpha":
		return fmt.Sprintf("%s must contain only alphabetic characters", field)
	case "numeric":
		return fmt.Sprintf("%s must be numeric", field)
	case "datetime":
		return fmt.Sprintf("%s must be a valid datetime in format %s", field, param)
	case "eqfield":
		return fmt.Sprintf("%s must equal %s", field, param)
	case "nefield":
		return fmt.Sprintf("%s must not equal %s", field, param)
	default:
		return fmt.Sprintf("%s failed validation (%s)", field, tag)
	}
}

// -----------------------------------------------------------------------------
// Custom Validators
// -----------------------------------------------------------------------------

// validateUUID checks if a field is a valid UUID.
func validateUUID(fl validator.FieldLevel) bool {
	field := fl.Field().String()
	if field == "" {
		return false
	}
	_, err := uuid.Parse(field)
	return err == nil
}

// validateUUIDOrEmpty checks if a field is either empty or a valid UUID.
func validateUUIDOrEmpty(fl validator.FieldLevel) bool {
	field := fl.Field().String()
	if field == "" {
		return true
	}
	_, err := uuid.Parse(field)
	return err == nil
}

// validatePhone checks if a field is a valid phone number in E.164 format.
// E.164 format: +[country code][number] (e.g., +14155552671)
func validatePhone(fl validator.FieldLevel) bool {
	phone := fl.Field().String()
	if phone == "" {
		return false
	}
	return phoneRegex.MatchString(phone)
}

// validateSlug checks if a field is a valid URL slug.
// Valid slugs contain only lowercase letters, numbers, and hyphens.
func validateSlug(fl validator.FieldLevel) bool {
	slug := fl.Field().String()
	if slug == "" {
		return false
	}
	// Must start with letter or number, can contain hyphens, must end with letter or number
	match, _ := regexp.MatchString(`^[a-z0-9]+(-[a-z0-9]+)*$`, slug)
	return match
}

// validateHexColor checks if a field is a valid hex color code.
// Accepts #RGB, #RRGGBB, #RRGGBBAA formats.
func validateHexColor(fl validator.FieldLevel) bool {
	color := fl.Field().String()
	if color == "" {
		return false
	}
	match, _ := regexp.MatchString(`^#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$`, color)
	return match
}

// RegisterValidation registers a custom validation function.
// Returns an error if registration fails.
func RegisterValidation(tag string, fn validator.Func) error {
	return Validator().RegisterValidation(tag, fn)
}

// MustRegisterValidation registers a custom validation function and panics on error.
// Use this during initialization when registration failure should be fatal.
func MustRegisterValidation(tag string, fn validator.Func) {
	if err := RegisterValidation(tag, fn); err != nil {
		panic(fmt.Sprintf("failed to register validation %q: %v", tag, err))
	}
}
