package user

import (
	"unicode"

	"github.com/grafana/grafana/pkg/apimachinery/errutil"
	"github.com/grafana/grafana/pkg/setting"
	"github.com/grafana/grafana/pkg/util"
)

var (
	ErrPasswordTooShort       = errutil.BadRequest("password.password-policy-too-short", errutil.WithPublicMessage("New password is too short"))
	ErrPasswordPolicyInfringe = errutil.BadRequest("password.password-policy-infringe", errutil.WithPublicMessage("New password doesn't comply with the password policy"))
	MinPasswordLength         = 12
)

type Password string

func NewPassword(newPassword string, config *setting.Cfg) (Password, error) {
	if err := ValidatePassword(newPassword, config); err != nil {
		return "", err
	}
	return Password(newPassword), nil
}

func (p Password) Validate(config *setting.Cfg) error {
	return ValidatePassword(string(p), config)
}

func (p Password) Hash(salt string) (Password, error) {
	hashed, err := util.EncodePassword(string(p), salt)
	if err != nil {
		return "", err
	}
	return Password(hashed), nil
}

// ValidatePassword checks if a new password meets the required criteria based on the given configuration.
// If BasicAuthStrongPasswordPolicy is disabled, it only checks for password length.
// Otherwise, it ensures the password meets the minimum length requirement and contains at least one uppercase letter,
// one lowercase letter, one number, and one symbol.
func ValidatePassword(newPassword string, config *setting.Cfg) error {
	if !config.BasicAuthStrongPasswordPolicy {
		if len(newPassword) < 4 {
			return ErrPasswordTooShort.Errorf("new password is too short")
		}
		return nil
	}
	if len(newPassword) < MinPasswordLength {
		return ErrPasswordPolicyInfringe.Errorf("new password is too short for the strong password policy")
	}

	hasUpperCase := false
	hasLowerCase := false
	hasNumber := false
	hasSymbol := false

	for _, r := range newPassword {
		if !hasLowerCase && unicode.IsLower(r) {
			hasLowerCase = true
		}

		if !hasUpperCase && unicode.IsUpper(r) {
			hasUpperCase = true
		}

		if !hasNumber && unicode.IsNumber(r) {
			hasNumber = true
		}

		if !hasSymbol && !unicode.IsLetter(r) && !unicode.IsNumber(r) {
			hasSymbol = true
		}

		if hasUpperCase && hasLowerCase && hasNumber && hasSymbol {
			return nil
		}
	}
	return ErrPasswordPolicyInfringe.Errorf("new password doesn't comply with the password policy")
}
