
package validation

import (
	"errors"
	"github.com/gin-gonic/gin/binding"
	"github.com/go-playground/locales/en"
	"github.com/go-playground/locales/zh"
	ut "github.com/go-playground/universal-translator"
	"github.com/go-playground/validator/v10"
	zh_translations "github.com/go-playground/validator/v10/translations/zh"
	"golang.org/x/net/idna"
	"net"
	"reflect"
	"strconv"
	"strings"
	"sync"
	"unicode/utf8"
)

// validate 是一个单例的 validator 实例，确保全局只有一个验证器，以提高性能。
// validator 实例的创建是昂贵的，因此我们使用 sync.Once 来确保它只被创建一次。
// validate 是一个单例的 validator 实例，确保全局只有一个验证器，以提高性能。
// validator 实例的创建是昂贵的，因此我们使用 sync.Once 来确保它只被创建一次。
var (
	validate *validator.Validate
	trans    ut.Translator // 全局翻译器
	once     sync.Once
)

func init() {
	getInstance()
}

// getInstance 返回一个初始化好的 validator 单例实例。
func getInstance() *validator.Validate {
	once.Do(func() {
		validate = validator.New()

		// 注册一个函数，获取struct tag中自定义的json字段名
		validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
			name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
			if name == "-" {
				return ""
			}
			return name
		})

		// 创建翻译器
		en := en.New()
		zh := zh.New()
		uni := ut.New(en, zh, en) // 第一个参数是备用语言

		// 我们假设服务主要使用中文，所以获取中文翻译器
		trans, _ = uni.GetTranslator("zh")

		// 注册中文翻译
		zh_translations.RegisterDefaultTranslations(validate, trans)

		// 注册自定义校验
		registerCustomValidations(validate)

		// 将我们自定义的校验器与 gin 的校验器绑定
		binding.Validator = &defaultValidator{validate: validate}
		// 在这里可以注册自定义的验证函数
		// 例如: validate.RegisterValidation("custom_tag", customValidationFunc)
	})
	return validate
}

// defaultValidator 实现了 gin.Engine 的 validator 接口
type defaultValidator struct {
	validate *validator.Validate
}

// ValidateStruct 接收一个对象并校验它。
// ValidateStruct 接收一个对象并校验它，它会返回翻译后的错误信息。
func (v *defaultValidator) ValidateStruct(obj interface{}) error {
	err := v.validate.Struct(obj)
	if err != nil {
		// 如果发生校验错误，则调用我们的翻译函数
		return translateError(err)
	}
	return nil
}

// Engine 返回底层的 validator 引擎
func (v *defaultValidator) Engine() interface{} {
	return v.validate
}

// translateError 将 validator.ValidationErrors 翻译成更友好的中文错误信息
func translateError(err error) error {
	var validationErrors validator.ValidationErrors
	if errors.As(err, &validationErrors) {
		var errs []string
		for _, e := range validationErrors {
			// 使用我们注册的翻译器进行翻译
			errs = append(errs, e.Translate(trans))
		}
		return errors.New(strings.Join(errs, "; "))
	}
	return err
}

// Validate 函数用于验证一个结构体的所有字段。
// 它现在会返回翻译后的中文错误信息。
func Validate(s interface{}) error {
	err := getInstance().Struct(s)
	if err != nil {
		return translateError(err)
	}
	return nil
}

// ValidateVar 函数用于验证单个变量。
// 它现在也会返回翻译后的中文错误信息。
func ValidateVar(field interface{}, tag string) error {
	err := getInstance().Var(field, tag)
	if err != nil {
		return translateError(err)
	}
	return nil
}

// registerCustomValidations 注册所有自定义的校验规则和翻译
func registerCustomValidations(v *validator.Validate) {
	// 注册 hostport 校验器
	v.RegisterValidation("hostport", validateHostPort)
	v.RegisterValidation("idn_fqdn",isIdnFqdn)
	v.RegisterValidation("isPortStringValid", isPortStringValid)

	// 为 hostport 校验器注册中文翻译
	v.RegisterTranslation("hostport", trans, func(ut ut.Translator) error {
		return ut.Add("hostport", "{0} 必须是有效的 'IP:端口' 或 '域名:端口' 格式", true)
	}, func(ut ut.Translator, fe validator.FieldError) string {
		t, _ := ut.T("hostport", fe.Field())
		return t
	})

	v.RegisterTranslation("idn_fqdn", trans, func(ut ut.Translator) error {
		return ut.Add("idn_fqdn", "{0} 必须是有效的 IDN 格式", true)
	}, func(ut ut.Translator, fe validator.FieldError) string {
		t, _ := ut.T("idn_fqdn", fe.Field())
		return t
	})

	v.RegisterTranslation("isPortStringValid", trans, func(ut ut.Translator) error {
		return ut.Add("isPortStringValid", "{0} 必须是有效的端口号", true)
	}, func(ut ut.Translator, fe validator.FieldError) string {
		t, _ := ut.T("isPortStringValid", fe.Field())
		return t
	})
}

// validateHostPort 是一个自定义校验函数，用于检查字符串是否为有效的 "host:port"。
// 主机部分可以是 IP 地址或域名。
func validateHostPort(fl validator.FieldLevel) bool {
	addr := fl.Field().String()

	// 我们可以使用 net.SplitHostPort 来检查格式。
	// 它能正确处理 IPv6 地址，例如 "[::1]:8080"。
	host, port, err := net.SplitHostPort(addr)
	if err != nil || host == "" || port == "" {
		return false // 格式不正确，或者主机或端口为空
	}


	portInt, err := strconv.Atoi(port)
	if err != nil {
		return false
	}
	if portInt <= 0 || portInt > 65535 {
		return false
	}

	// 检查主机部分是否为 IP 地址
	if net.ParseIP(host) != nil {
		return true
	}



	// --- 如果不是 IP，则按域名处理（这是支持中文域名的关键部分） ---

	// 4. 将可能为中文的域名转换为 Punycode (ASCII) 格式
	// 例如: "例子.com" -> "xn--fsq270a.com"
	asciiHost, err := idna.ToASCII(host)
	if err != nil {
		// 如果转换失败，说明主机名包含无效字符（如下划线 "_" 等），不是合法的域名
		return false
	}
	// 5. 使用 validator 内置的 hostname_rfc1123 规则来验证转换后的 ASCII 域名
	// RFC 1123 是对主机名规范的常见标准。
	// 注意：我们验证的是 asciiHost，而不是原始的 host。
	v := getInstance()
	err = v.Var(asciiHost, "fqdn")
	return err == nil
}

func isIdnFqdn(fl validator.FieldLevel) bool {
	// 获取字段的值
	domain := fl.Field().String()

	// omitempty 的行为由 validator 自身处理
	if domain == "" {
		return true
	}

	// 关键步骤 1：一个有效的 FQDN 必须包含至少一个点来分隔 TLD。
	// 这会直接拦截掉 "你好世界" 这样的输入。
	if !strings.Contains(domain, ".") {
		return false
	}

	// 关键步骤 2：FQDN 不能以点开头或结尾。
	// 注意：idna.ToASCII 会检查标签是否以连字符开头/结尾，所以我们只需要检查点。
	if strings.HasPrefix(domain, ".") || strings.HasSuffix(domain, ".") {
		return false
	}

	// 关键步骤 3：检查是否为有效的 UTF-8 字符串。
	// 这是防止非 UTF-8 编码字节流的关键。
	if !utf8.ValidString(domain) {
		return false
	}

	// 关键步骤 4：使用 idna.ToASCII 进行最严格的格式验证。
	// 它会检查：
	// - 是否包含非法字符（如 @, :, / 等）。
	// - 每个标签（点之间的部分）是否过长。
	// - 每个标签是否以连字符（-）开头或结尾。
	// - 以及其他所有 IDNA2008 标准中定义的规则。
	_, err := idna.ToASCII(domain)

	// 如果没有错误，说明它在格式上是一个有效的 FQDN。
	return err == nil
}

func isPortStringValid(fl validator.FieldLevel) bool {
	// 1. 获取字段的字符串值
	portStr := fl.Field().String()

	// 2. 将字符串转换为整数
	// 注意：Atoi 已经确保了字符串是纯数字，所以 'numeric' 标签其实有点冗余，但保留它有助于提前过滤和提供更清晰的错误。
	portNum, err := strconv.Atoi(portStr)
	if err != nil {
		// 如果无法转换为整数（例如，包含非数字字符），则验证失败。
		// 理论上 'numeric' 标签会先捕捉到这个错误。
		return false
	}

	// 3. 检查数值范围
	if portNum >= 1 && portNum <= 65535 {
		return true // 在有效范围内，验证通过
	}

	return false // 不在有效范围内，验证失败
}