package validation

import (
	"errors"
	"fmt"
	"reflect"
	"strings"

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

var customMessages = map[string]string{
	"required":         "Field %s is required",
	"required_strict":  "Field %s is required and cannot be null or empty",
	"omitempty_strict": "Field %s cannot be null or empty when provided",

	"email":    "Invalid email address for field %s",
	"min":      "Field %s must have a minimum length of %s characters",
	"max":      "Field %s must have a maximum length of %s characters",
	"len":      "Field %s must be exactly %s characters long",
	"number":   "Field %s must be a number",
	"positive": "Field %s must be a positive number",
	"alphanum": "Field %s must contain only alphanumeric characters",
	"oneof":    "Invalid value for field %s",
	"password": "Field %s must be at least 8 characters, contain uppercase, lowercase, number, and special character",
	"gt":       "Invalid %s, must be greater than %s",
}

func CustomErrorMessages(err error) (string, map[string]string) {
	var validationErrors validator.ValidationErrors
	if errors.As(err, &validationErrors) {
		return generateErrorMessages(validationErrors)
	}
	return "", nil
}

func generateErrorMessages(validationErrors validator.ValidationErrors) (string, map[string]string) {
	errorsMap := make(map[string]string)
	var firstMessage string
	for i, err := range validationErrors {
		fieldName := err.StructNamespace()
		tag := err.Tag()

		customMessage := customMessages[tag]
		var msg string
		if customMessage != "" {
			msg = formatErrorMessage(customMessage, err, tag)
		} else {
			msg = defaultErrorMessage(err)
		}
		errorsMap[fieldName] = msg
		if i == 0 {
			firstMessage = msg
		}
	}
	return firstMessage, errorsMap
}

func formatErrorMessage(customMessage string, err validator.FieldError, tag string) string {
	if tag == "min" || tag == "max" || tag == "len" || tag == "gt" {
		return fmt.Sprintf(customMessage, err.Field(), err.Param())
	}
	return fmt.Sprintf(customMessage, err.Field())
}

func defaultErrorMessage(err validator.FieldError) string {
	return fmt.Sprintf("Field validation for '%s' failed on the '%s' tag", err.Field(), err.Tag())
}

func Validator() *validator.Validate {
	validate := validator.New()

	validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
		if jsonTag := getTagName(fld, "json"); jsonTag != "" {
			return jsonTag
		}
		if queryTag := getTagName(fld, "query"); queryTag != "" {
			return queryTag
		}
		return fld.Name
	})

	if err := validate.RegisterValidation("password", Password); err != nil {
		return nil
	}
	if err := validate.RegisterValidation("required_strict", RequiredStrict); err != nil {
		return nil
	}
	if err := validate.RegisterValidation("omitempty_strict", OmitemptyStrict); err != nil {
		return nil
	}
	return validate
}

func getTagName(fld reflect.StructField, tag string) string {
	value, ok := fld.Tag.Lookup(tag)
	if !ok || value == "-" {
		return ""
	}

	name := strings.Split(value, ",")[0]
	if name == "" || name == "-" {
		return ""
	}
	return name
}
