package validators

import (
	"reflect"

	validation "github.com/go-ozzo/ozzo-validation/v4"
)

// Equal checks whether the validated value matches another one from the same type.
//
// It expects the compared values to be from the same type and works
// with booleans, numbers, strings and their pointer variants.
//
// If one of the value is pointer, the comparison is based on its
// underlying value (when possible to determine).
//
// Note that empty/zero values are also compared (this differ from other validation.RuleFunc).
//
// Example:
//
//	validation.Field(&form.PasswordConfirm, validation.By(validators.Equal(form.Password)))
func Equal[T comparable](valueToCompare T) validation.RuleFunc {
	return func(value any) error {
		if compareValues(value, valueToCompare) {
			return nil
		}

		return validation.NewError("validation_values_mismatch", "Values don't match.")
	}
}

func compareValues(a, b any) bool {
	if a == b {
		return true
	}

	if checkIsNil(a) && checkIsNil(b) {
		return true
	}

	var result bool

	defer func() {
		if err := recover(); err != nil {
			result = false
		}
	}()

	reflectA := reflect.ValueOf(a)
	reflectB := reflect.ValueOf(b)

	dereferencedA := dereference(reflectA)
	dereferencedB := dereference(reflectB)
	if dereferencedA.CanInterface() && dereferencedB.CanInterface() {
		result = dereferencedA.Interface() == dereferencedB.Interface()
	}

	return result
}

// note https://github.com/golang/go/issues/51649
func checkIsNil(value any) bool {
	if value == nil {
		return true
	}

	var result bool

	defer func() {
		if err := recover(); err != nil {
			result = false
		}
	}()

	result = reflect.ValueOf(value).IsNil()

	return result
}

func dereference(v reflect.Value) reflect.Value {
	for v.Kind() == reflect.Pointer {
		v = v.Elem()
	}
	return v
}