// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.

package gvalid_test

import (
	"context"
	"testing"

	"github.com/gogf/gf/v2/container/gvar"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/os/gtime"
	"github.com/gogf/gf/v2/test/gtest"
	"github.com/gogf/gf/v2/util/gconv"
)

func Test_CheckStruct(t *testing.T) {
	gtest.C(t, func(t *gtest.T) {
		type Object struct {
			Name string
			Age  int
		}
		rules := []string{
			"@required|length:6,16",
			"@between:18,30",
		}
		msgs := map[string]interface{}{
			"Name": map[string]string{
				"required": "名称不能为空",
				"length":   "名称长度为{min}到{max}个字符",
			},
			"Age": "年龄为18到30周岁",
		}
		obj := &Object{"john", 16}
		err := g.Validator().Data(obj).Rules(rules).Messages(msgs).Run(context.TODO())
		t.AssertNil(err)
	})

	gtest.C(t, func(t *gtest.T) {
		type Object struct {
			Name string
			Age  int
		}
		rules := []string{
			"Name@required|length:6,16#名称不能为空",
			"Age@between:18,30",
		}
		msgs := map[string]interface{}{
			"Name": map[string]string{
				"required": "名称不能为空",
				"length":   "名称长度为{min}到{max}个字符",
			},
			"Age": "年龄为18到30周岁",
		}
		obj := &Object{"john", 16}
		err := g.Validator().Data(obj).Rules(rules).Messages(msgs).Run(context.TODO())
		t.AssertNE(err, nil)
		t.Assert(len(err.Maps()), 2)
		t.Assert(err.Maps()["Name"]["required"], "")
		t.Assert(err.Maps()["Name"]["length"], "名称长度为6到16个字符")
		t.Assert(err.Maps()["Age"]["between"], "年龄为18到30周岁")
	})

	gtest.C(t, func(t *gtest.T) {
		type Object struct {
			Name string
			Age  int
		}
		rules := []string{
			"Name@required|length:6,16#名称不能为空|",
			"Age@between:18,30",
		}
		msgs := map[string]interface{}{
			"Name": map[string]string{
				"required": "名称不能为空",
				"length":   "名称长度为{min}到{max}个字符",
			},
			"Age": "年龄为18到30周岁",
		}
		obj := &Object{"john", 16}
		err := g.Validator().Data(obj).Rules(rules).Messages(msgs).Run(context.TODO())
		t.AssertNE(err, nil)
		t.Assert(len(err.Maps()), 2)
		t.Assert(err.Maps()["Name"]["required"], "")
		t.Assert(err.Maps()["Name"]["length"], "名称长度为6到16个字符")
		t.Assert(err.Maps()["Age"]["between"], "年龄为18到30周岁")
	})

	gtest.C(t, func(t *gtest.T) {
		type Object struct {
			Name string
			Age  int
		}
		rules := map[string]string{
			"Name": "required|length:6,16",
			"Age":  "between:18,30",
		}
		msgs := map[string]interface{}{
			"Name": map[string]string{
				"required": "名称不能为空",
				"length":   "名称长度为{min}到{max}个字符",
			},
			"Age": "年龄为18到30周岁",
		}
		obj := &Object{"john", 16}
		err := g.Validator().Data(obj).Rules(rules).Messages(msgs).Run(context.TODO())
		t.AssertNE(err, nil)
		t.Assert(len(err.Maps()), 2)
		t.Assert(err.Maps()["Name"]["required"], "")
		t.Assert(err.Maps()["Name"]["length"], "名称长度为6到16个字符")
		t.Assert(err.Maps()["Age"]["between"], "年龄为18到30周岁")
	})

	gtest.C(t, func(t *gtest.T) {
		type LoginRequest struct {
			Username string `json:"username" valid:"username@required#用户名不能为空"`
			Password string `json:"password" valid:"password@required#登录密码不能为空"`
		}
		var login LoginRequest
		err := g.Validator().Data(login).Run(context.TODO())
		t.AssertNE(err, nil)
		t.Assert(len(err.Maps()), 2)
		t.Assert(err.Maps()["username"]["required"], "用户名不能为空")
		t.Assert(err.Maps()["password"]["required"], "登录密码不能为空")
	})

	gtest.C(t, func(t *gtest.T) {
		type LoginRequest struct {
			Username string `json:"username" valid:"@required#用户名不能为空"`
			Password string `json:"password" valid:"@required#登录密码不能为空"`
		}
		var login LoginRequest
		err := g.Validator().Data(login).Run(context.TODO())
		t.AssertNil(err)
	})

	gtest.C(t, func(t *gtest.T) {
		type LoginRequest struct {
			username string `json:"username" valid:"username@required#用户名不能为空"`
			Password string `json:"password" valid:"password@required#登录密码不能为空"`
		}
		var login LoginRequest
		err := g.Validator().Data(login).Run(context.TODO())
		t.AssertNE(err, nil)
		t.Assert(err.Maps()["password"]["required"], "登录密码不能为空")
	})

	// gvalid tag
	gtest.C(t, func(t *gtest.T) {
		type User struct {
			Id       int    `valid:"uid@required|min:10#|ID不能为空"`
			Age      int    `valid:"age@required#年龄不能为空"`
			Username string `json:"username" valid:"username@required#用户名不能为空"`
			Password string `json:"password" valid:"password@required#登录密码不能为空"`
		}
		user := &User{
			Id:       1,
			Username: "john",
			Password: "123456",
		}
		err := g.Validator().Data(user).Run(context.TODO())
		t.AssertNE(err, nil)
		t.Assert(len(err.Maps()), 1)
		t.Assert(err.Maps()["uid"]["min"], "ID不能为空")
	})

	gtest.C(t, func(t *gtest.T) {
		type User struct {
			Id       int    `valid:"uid@required|min:10#|ID不能为空"`
			Age      int    `valid:"age@required#年龄不能为空"`
			Username string `json:"username" valid:"username@required#用户名不能为空"`
			Password string `json:"password" valid:"password@required#登录密码不能为空"`
		}
		user := &User{
			Id:       1,
			Username: "john",
			Password: "123456",
		}

		rules := []string{
			"username@required#用户名不能为空",
		}

		err := g.Validator().Data(user).Rules(rules).Run(context.TODO())
		t.AssertNE(err, nil)
		t.Assert(len(err.Maps()), 1)
		t.Assert(err.Maps()["uid"]["min"], "ID不能为空")
	})

	gtest.C(t, func(t *gtest.T) {
		type User struct {
			Id       int    `valid:"uid@required|min:10#ID不能为空"`
			Age      int    `valid:"age@required#年龄不能为空"`
			Username string `json:"username" valid:"username@required#用户名不能为空"`
			Password string `json:"password" valid:"password@required#登录密码不能为空"`
		}
		user := &User{
			Id:       1,
			Username: "john",
			Password: "123456",
		}
		err := g.Validator().Data(user).Run(context.TODO())
		t.AssertNE(err, nil)
		t.Assert(len(err.Maps()), 1)
	})

	// valid tag
	gtest.C(t, func(t *gtest.T) {
		type User struct {
			Id       int    `valid:"uid@required|min:10#|ID不能为空"`
			Age      int    `valid:"age@required#年龄不能为空"`
			Username string `json:"username" valid:"username@required#用户名不能为空"`
			Password string `json:"password" valid:"password@required#登录密码不能为空"`
		}
		user := &User{
			Id:       1,
			Username: "john",
			Password: "123456",
		}
		err := g.Validator().Data(user).Run(context.TODO())
		t.AssertNE(err, nil)
		t.Assert(len(err.Maps()), 1)
		t.Assert(err.Maps()["uid"]["min"], "ID不能为空")
	})
}

func Test_CheckStruct_EmbeddedObject_Attribute(t *testing.T) {
	gtest.C(t, func(t *gtest.T) {
		type Base struct {
			Time *gtime.Time
		}
		type Object struct {
			Base
			Name string
			Type int
		}
		rules := map[string]string{
			"Name": "required",
			"Type": "required",
		}
		ruleMsg := map[string]interface{}{
			"Name": "名称必填",
			"Type": "类型必填",
		}
		obj := &Object{}
		obj.Type = 1
		obj.Name = "john"
		obj.Time = gtime.Now()
		err := g.Validator().Data(obj).Rules(rules).Messages(ruleMsg).Run(context.TODO())
		t.AssertNil(err)
	})
	gtest.C(t, func(t *gtest.T) {
		type Base struct {
			Name string
			Type int
		}
		type Object struct {
			Base Base
			Name string
			Type int
		}
		rules := map[string]string{
			"Name": "required",
			"Type": "required",
		}
		ruleMsg := map[string]interface{}{
			"Name": "名称必填",
			"Type": "类型必填",
		}
		obj := &Object{}
		obj.Type = 1
		obj.Name = "john"
		err := g.Validator().Data(obj).Rules(rules).Messages(ruleMsg).Run(context.TODO())
		t.AssertNil(err)
	})
}

func Test_CheckStruct_With_EmbeddedObject(t *testing.T) {
	gtest.C(t, func(t *gtest.T) {
		type Pass struct {
			Pass1 string `valid:"password1@required|same:password2#请输入您的密码|您两次输入的密码不一致"`
			Pass2 string `valid:"password2@required|same:password1#请再次输入您的密码|您两次输入的密码不一致"`
		}
		type User struct {
			Id   int
			Name string `valid:"name@required#请输入您的姓名"`
			Pass
		}
		user := &User{
			Name: "",
			Pass: Pass{
				Pass1: "1",
				Pass2: "2",
			},
		}
		err := g.Validator().Data(user).Run(context.TODO())
		t.AssertNE(err, nil)
		t.Assert(err.Maps()["name"], g.Map{"required": "请输入您的姓名"})
		t.Assert(err.Maps()["password1"], g.Map{"same": "您两次输入的密码不一致"})
		t.Assert(err.Maps()["password2"], g.Map{"same": "您两次输入的密码不一致"})
	})
}

func Test_CheckStruct_With_StructAttribute(t *testing.T) {
	gtest.C(t, func(t *gtest.T) {
		type Pass struct {
			Pass1 string `valid:"password1@required|same:password2#请输入您的密码|您两次输入的密码不一致"`
			Pass2 string `valid:"password2@required|same:password1#请再次输入您的密码|您两次输入的密码不一致"`
		}
		type User struct {
			Pass
			Id   int
			Name string `valid:"name@required#请输入您的姓名"`
		}
		user := &User{
			Name: "",
			Pass: Pass{
				Pass1: "1",
				Pass2: "2",
			},
		}
		err := g.Validator().Data(user).Run(context.TODO())
		t.AssertNE(err, nil)
		t.Assert(err.Maps()["name"], g.Map{"required": "请输入您的姓名"})
		t.Assert(err.Maps()["password1"], g.Map{"same": "您两次输入的密码不一致"})
		t.Assert(err.Maps()["password2"], g.Map{"same": "您两次输入的密码不一致"})
	})
}

func Test_CheckStruct_Optional(t *testing.T) {
	gtest.C(t, func(t *gtest.T) {
		type Params struct {
			Page      int    `v:"required|min:1         # page is required"`
			Size      int    `v:"required|between:1,100 # size is required"`
			ProjectId string `v:"between:1,10000        # project id must between {min}, {max}"`
		}
		obj := &Params{
			Page: 1,
			Size: 10,
		}
		err := g.Validator().Data(obj).Run(context.TODO())
		t.AssertNil(err)
	})
	gtest.C(t, func(t *gtest.T) {
		type Params struct {
			Page      int       `v:"required|min:1         # page is required"`
			Size      int       `v:"required|between:1,100 # size is required"`
			ProjectId *gvar.Var `v:"between:1,10000        # project id must between {min}, {max}"`
		}
		obj := &Params{
			Page: 1,
			Size: 10,
		}
		err := g.Validator().Data(obj).Run(context.TODO())
		t.AssertNil(err)
	})
	gtest.C(t, func(t *gtest.T) {
		type Params struct {
			Page      int `v:"required|min:1         # page is required"`
			Size      int `v:"required|between:1,100 # size is required"`
			ProjectId int `v:"between:1,10000        # project id must between {min}, {max}"`
		}
		obj := &Params{
			Page: 1,
			Size: 10,
		}
		err := g.Validator().Data(obj).Run(context.TODO())
		t.Assert(err.String(), "project id must between 1, 10000")
	})
}

func Test_CheckStruct_NoTag(t *testing.T) {
	gtest.C(t, func(t *gtest.T) {
		type Params struct {
			Page      int
			Size      int
			ProjectId string
		}
		obj := &Params{
			Page: 1,
			Size: 10,
		}
		err := g.Validator().Data(obj).Run(context.TODO())
		t.AssertNil(err)
	})
}

func Test_CheckStruct_InvalidRule(t *testing.T) {
	gtest.C(t, func(t *gtest.T) {
		type Params struct {
			Name  string
			Age   uint
			Phone string `v:"mobile"`
		}
		obj := &Params{
			Name:  "john",
			Age:   18,
			Phone: "123",
		}
		err := g.Validator().Data(obj).Run(context.TODO())
		t.AssertNE(err, nil)
	})
}

func TestValidator_CheckStructWithData(t *testing.T) {
	gtest.C(t, func(t *gtest.T) {
		type UserApiSearch struct {
			Uid      int64  `v:"required"`
			Nickname string `v:"required-with:uid"`
		}
		data := UserApiSearch{
			Uid:      1,
			Nickname: "john",
		}
		t.Assert(
			g.Validator().Data(data).Assoc(
				g.Map{"uid": 1, "nickname": "john"},
			).Run(context.TODO()),
			nil,
		)
	})
	gtest.C(t, func(t *gtest.T) {
		type UserApiSearch struct {
			Uid      int64  `v:"required"`
			Nickname string `v:"required-with:uid"`
		}
		data := UserApiSearch{}
		t.AssertNE(g.Validator().Data(data).Assoc(g.Map{}).Run(context.TODO()), nil)
	})
	gtest.C(t, func(t *gtest.T) {
		type UserApiSearch struct {
			Uid      int64  `json:"uid" v:"required"`
			Nickname string `json:"nickname" v:"required-with:Uid"`
		}
		data := UserApiSearch{
			Uid: 1,
		}
		t.AssertNE(g.Validator().Data(data).Assoc(g.Map{}).Run(context.TODO()), nil)
	})

	gtest.C(t, func(t *gtest.T) {
		type UserApiSearch struct {
			Uid       int64       `json:"uid"`
			Nickname  string      `json:"nickname" v:"required-with:Uid"`
			StartTime *gtime.Time `json:"start_time" v:"required-with:EndTime"`
			EndTime   *gtime.Time `json:"end_time" v:"required-with:StartTime"`
		}
		data := UserApiSearch{
			StartTime: nil,
			EndTime:   nil,
		}
		t.Assert(g.Validator().Data(data).Assoc(g.Map{}).Run(context.TODO()), nil)
	})
	gtest.C(t, func(t *gtest.T) {
		type UserApiSearch struct {
			Uid       int64       `json:"uid"`
			Nickname  string      `json:"nickname" v:"required-with:Uid"`
			StartTime *gtime.Time `json:"start_time" v:"required-with:EndTime"`
			EndTime   *gtime.Time `json:"end_time" v:"required-with:StartTime"`
		}
		data := UserApiSearch{
			StartTime: gtime.Now(),
			EndTime:   nil,
		}
		t.AssertNE(g.Validator().Data(data).Assoc(g.Map{"start_time": gtime.Now()}).Run(context.TODO()), nil)
	})
}

func Test_CheckStruct_PointerAttribute(t *testing.T) {
	gtest.C(t, func(t *gtest.T) {
		type Req struct {
			Name string
			Age  *uint `v:"min:18"`
		}
		req := &Req{
			Name: "john",
			Age:  gconv.PtrUint(0),
		}
		err := g.Validator().Data(req).Run(context.TODO())
		t.Assert(err.String(), "The Age value `0` must be equal or greater than 18")
	})
	gtest.C(t, func(t *gtest.T) {
		type Req struct {
			Name string `v:"min-length:3"`
			Age  *uint  `v:"min:18"`
		}
		req := &Req{
			Name: "j",
			Age:  gconv.PtrUint(19),
		}
		err := g.Validator().Data(req).Run(context.TODO())
		t.Assert(err.String(), "The Name value `j` length must be equal or greater than 3")
	})
	gtest.C(t, func(t *gtest.T) {
		type Params struct {
			Age *uint `v:"min:18"`
		}
		type Req struct {
			Name   string
			Params *Params
		}
		req := &Req{
			Name: "john",
			Params: &Params{
				Age: gconv.PtrUint(0),
			},
		}
		err := g.Validator().Data(req).Run(context.TODO())
		t.Assert(err.String(), "The Age value `0` must be equal or greater than 18")
	})
}
