package validator

import (
	"errors"
	"fmt"
	"regexp"
	"strings"

	validation "github.com/Thearas/ozzo-validation/v4"
	"github.com/asaskevich/govalidator"
	"github.com/hashicorp/go-version"
	"github.com/robfig/cron"
)

var (
	parameterNameRegexp = regexp.MustCompile(`^[a-zA-Z_]+[-a-zA-Z0-9_]*$`)
	TagRegexp           = regexp.MustCompile(`^[a-zA-Z0-9](([\\/_\\-a-zA-Z0-9]*[a-zA-Z0-9])?)*$`)

	Tag = validation.By(func(v interface{}) error {
		l := v.(string)
		if err := validation.Length(1, 40).Validate(v); err != nil {
			return err
		}
		if !TagRegexp.MatchString(l) {
			return fmt.Errorf("%s is not a valid tag, a valid tag must consist of alphanumeric characters, '_', '-' or '/', and must start and end with an alphanumeric character", l)
		}
		return nil
	})

	PingCAPEmail = validation.By(func(v interface{}) error {
		v, isNil := validation.Indirect(v)
		if isNil || validation.IsEmpty(v) {
			return nil
		}

		email := v.(string)
		if !govalidator.IsEmail(email) {
			return fmt.Errorf("%s is not a valid email", email)
		}
		if !strings.HasSuffix(email, "@pingcap.com") {
			return errors.New(`email should end with "@pingcap.com"`)
		}
		return nil
	})

	// SemVerConstraint validates semantic-version.
	// NOTE: items can be semver constraints or "nightly"
	SemVerConstraint = validation.By(func(value interface{}) error {
		v := value.(string)
		if v == "nightly" {
			return nil
		}
		_, err := version.NewConstraint(v)
		if err != nil {
			err = fmt.Errorf(`version should be semver constraints or "nightly", but got %s`, v)
		}
		return err
	})

	ParameterName = validation.By(func(value interface{}) error {
		value, isNil := validation.Indirect(value)
		if isNil || validation.IsEmpty(value) {
			return nil
		}

		// 1 <= len <= 200
		name := value.(string)
		if err := validation.Length(1, 200).Validate(name); err != nil {
			return err
		}

		if !parameterNameRegexp.MatchString(name) {
			return fmt.Errorf("%s isn't a valid parameter name, here is the valid regex: %s",
				name, parameterNameRegexp.String())
		}
		return nil
	})

	Cron = validation.By(func(value interface{}) error {
		value, isNil := validation.Indirect(value)
		if isNil || validation.IsEmpty(value) {
			return nil
		}

		_, err := cron.ParseStandard(value.(string))
		return err
	})
)

// StartsWith returns a validation function that checks if the string starts with one of the prefixs.
func StartsWith(prefixs []string, errMsg string) validation.Rule {
	return validation.NewStringRule(func(s string) bool {
		for _, prefix := range prefixs {
			if strings.HasPrefix(s, prefix) {
				return true
			}
		}
		return false
	}, errMsg)
}
