package validations

import (
	"fmt"
	"reflect"
	"regexp"
	"strings"

	"github.com/asaskevich/govalidator"
	"github.com/pingcap/errors"
	"gorm.io/gorm"
)

var skipValidations = "validations:skip_validations"

func validate(db *gorm.DB) {
	if _, ok := db.Get("gorm:update_column"); ok {
		return
	}

	result, ok := db.Get(skipValidations)
	if ok && result.(bool) {
		return
	}

	if db.Error != nil {
		return
	}

	if !db.Statement.ReflectValue.IsValid() {
		return
	}

	// TODO: support struct Validate method.

	resource := reflect.Indirect(db.Statement.ReflectValue).Interface()
	_, validatorErrors := govalidator.ValidateStruct(resource)
	if validatorErrors != nil {
		if errors, ok := validatorErrors.(govalidator.Errors); ok {
			for _, err := range flatValidatorErrors(errors) {
				_ = db.AddError(formattedError(err, resource))
			}
		} else {
			_ = db.AddError(validatorErrors)
		}
	}
}

func flatValidatorErrors(validatorErrors govalidator.Errors) []govalidator.Error {
	resultErrors := []govalidator.Error{}
	for _, validatorError := range validatorErrors.Errors() {
		if errors, ok := validatorError.(govalidator.Errors); ok {
			flattedErrors := flatValidatorErrors(errors)
			resultErrors = append(resultErrors, flattedErrors...)

		}
		if e, ok := validatorError.(govalidator.Error); ok {
			resultErrors = append(resultErrors, e)
		}
	}
	return resultErrors
}

func formattedError(err govalidator.Error, resource interface{}) error {
	message := err.Error()
	attrName := err.Name
	if strings.Contains(message, "non zero value required") {
		message = fmt.Sprintf("%v can't be blank", attrName)
	} else if strings.Contains(message, "as length") {
		reg, _ := regexp.Compile(`\(([0-9]+)\|([0-9]+)\)`)
		submatch := reg.FindSubmatch([]byte(err.Error()))
		message = fmt.Sprintf("%v is the wrong length (should be %v~%v characters)", attrName, string(submatch[1]), string(submatch[2]))
	} else if strings.Contains(message, "as numeric") {
		message = fmt.Sprintf("%v is not a number", attrName)
	} else if strings.Contains(message, "as email") {
		message = fmt.Sprintf("%v is not a valid email address", attrName)
	}
	return NewError(resource, attrName, message)

}

// RegisterCallbacks register callback into GORM DB
func RegisterCallbacks(db *gorm.DB) error {
	callback := db.Callback()
	if callback.Create().Get("validations:validate") == nil {
		err := callback.Create().Before("gorm:before_create").Register("validations:validate", validate)
		if err != nil {
			return errors.AddStack(err)
		}
	}
	if callback.Update().Get("validations:validate") == nil {
		err := callback.Update().Before("gorm:before_update").Register("validations:validate", validate)
		if err != nil {
			return errors.AddStack(err)
		}
	}
	return nil
}
