// 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 ghttp_test

import (
	"fmt"
	"testing"
	"time"

	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/net/ghttp"
	"github.com/gogf/gf/v2/test/gtest"
	"github.com/gogf/gf/v2/util/guid"
	"github.com/gogf/gf/v2/util/gvalid"
)

func Test_Params_Parse(t *testing.T) {
	type User struct {
		Id   int
		Name string
		Map  map[string]interface{}
	}
	s := g.Server(guid.S())
	s.BindHandler("/parse", func(r *ghttp.Request) {
		var user *User
		if err := r.Parse(&user); err != nil {
			r.Response.WriteExit(err)
		}
		r.Response.WriteExit(user.Map["id"], user.Map["score"])
	})
	s.BindHandler("/parseErr", func(r *ghttp.Request) {
		var user User
		err := r.Parse(user)
		r.Response.WriteExit(err != nil)
	})
	s.SetDumpRouterMap(false)
	s.Start()
	defer s.Shutdown()

	time.Sleep(100 * time.Millisecond)
	gtest.C(t, func(t *gtest.T) {
		client := g.Client()
		client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
		t.Assert(client.PostContent(ctx, "/parse", `{"id":1,"name":"john","map":{"id":1,"score":100}}`), `1100`)
		t.Assert(client.PostContent(ctx, "/parseErr", `{"id":1,"name":"john","map":{"id":1,"score":100}}`), true)
	})
}

func Test_Params_ParseQuery(t *testing.T) {
	type User struct {
		Id   int
		Name string
	}
	s := g.Server(guid.S())
	s.BindHandler("/parse-query", func(r *ghttp.Request) {
		var user *User
		if err := r.ParseQuery(&user); err != nil {
			r.Response.WriteExit(err)
		}
		r.Response.WriteExit(user.Id, user.Name)
	})
	s.SetDumpRouterMap(false)
	s.Start()
	defer s.Shutdown()

	time.Sleep(100 * time.Millisecond)
	gtest.C(t, func(t *gtest.T) {
		c := g.Client()
		c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
		t.Assert(c.GetContent(ctx, "/parse-query"), `0`)
		t.Assert(c.GetContent(ctx, "/parse-query?id=1&name=john"), `1john`)
		t.Assert(c.PostContent(ctx, "/parse-query"), `0`)
		t.Assert(c.PostContent(ctx, "/parse-query", g.Map{
			"id":   1,
			"name": "john",
		}), `0`)
	})
}

func Test_Params_ParseForm(t *testing.T) {
	type User struct {
		Id   int
		Name string
	}
	s := g.Server(guid.S())
	s.BindHandler("/parse-form", func(r *ghttp.Request) {
		var user *User
		if err := r.ParseForm(&user); err != nil {
			r.Response.WriteExit(err)
		}
		r.Response.WriteExit(user.Id, user.Name)
	})
	s.SetDumpRouterMap(false)
	s.Start()
	defer s.Shutdown()

	time.Sleep(100 * time.Millisecond)
	gtest.C(t, func(t *gtest.T) {
		c := g.Client()
		c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
		t.Assert(c.GetContent(ctx, "/parse-form"), `0`)
		t.Assert(c.GetContent(ctx, "/parse-form", g.Map{
			"id":   1,
			"name": "john",
		}), 0)
		t.Assert(c.PostContent(ctx, "/parse-form"), `0`)
		t.Assert(c.PostContent(ctx, "/parse-form", g.Map{
			"id":   1,
			"name": "john",
		}), `1john`)
	})
}

func Test_Params_ComplexJsonStruct(t *testing.T) {
	type ItemEnv struct {
		Type  string
		Key   string
		Value string
		Brief string
	}

	type ItemProbe struct {
		Type           string
		Port           int
		Path           string
		Brief          string
		Period         int
		InitialDelay   int
		TimeoutSeconds int
	}

	type ItemKV struct {
		Key   string
		Value string
	}

	type ItemPort struct {
		Port  int
		Type  string
		Alias string
		Brief string
	}

	type ItemMount struct {
		Type    string
		DstPath string
		Src     string
		SrcPath string
		Brief   string
	}

	type SaveRequest struct {
		AppId          uint
		Name           string
		Type           string
		Cluster        string
		Replicas       uint
		ContainerName  string
		ContainerImage string
		VersionTag     string
		Namespace      string
		Id             uint
		Status         uint
		Metrics        string
		InitImage      string
		CpuRequest     uint
		CpuLimit       uint
		MemRequest     uint
		MemLimit       uint
		MeshEnabled    uint
		ContainerPorts []ItemPort
		Labels         []ItemKV
		NodeSelector   []ItemKV
		EnvReserve     []ItemKV
		EnvGlobal      []ItemEnv
		EnvContainer   []ItemEnv
		Mounts         []ItemMount
		LivenessProbe  ItemProbe
		ReadinessProbe ItemProbe
	}

	s := g.Server(guid.S())
	s.BindHandler("/parse", func(r *ghttp.Request) {
		if m := r.GetMap(); len(m) > 0 {
			var data *SaveRequest
			if err := r.Parse(&data); err != nil {
				r.Response.WriteExit(err)
			}
			r.Response.WriteExit(data)
		}
	})
	s.SetDumpRouterMap(false)
	s.Start()
	defer s.Shutdown()

	time.Sleep(100 * time.Millisecond)
	gtest.C(t, func(t *gtest.T) {
		client := g.Client()
		client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
		content := `
{
    "app_id": 5,
    "cluster": "test",
    "container_image": "nginx",
    "container_name": "test",
    "container_ports": [
        {
            "alias": "别名",
            "brief": "描述",
            "port": 80,
            "type": "tcp"
        }
    ],
    "cpu_limit": 100,
    "cpu_request": 10,
    "create_at": "2020-10-10 12:00:00",
    "creator": 1,
    "env_container": [
        {
            "brief": "用户环境变量",
            "key": "NAME",
            "type": "string",
            "value": "john"
        }
    ],
    "env_global": [
        {
            "brief": "数据数量",
            "key": "NUMBER",
            "type": "string",
            "value": "1"
        }
    ],
    "env_reserve": [
        {
            "key": "NODE_IP",
            "value": "status.hostIP"
        }
    ],
    "liveness_probe": {
        "brief": "存活探针",
        "initial_delay": 10,
        "path": "",
        "period": 5,
        "port": 80,
        "type": "tcpSocket"
    },
    "readiness_probe": {
        "brief": "就绪探针",
        "initial_delay": 10,
        "path": "",
        "period": 5,
        "port": 80,
        "type": "tcpSocket"
    },
    "id": 0,
    "init_image": "",
    "labels": [
        {
            "key": "app",
            "value": "test"
        }
    ],
    "mem_limit": 1000,
    "mem_request": 100,
    "mesh_enabled": 0,
    "metrics": "",
    "mounts": [],
    "name": "test",
    "namespace": "test",
    "node_selector": [
        {
            "key": "group",
            "value": "app"
        }
    ],
    "replicas": 1,
    "type": "test",
    "update_at": "2020-10-10 12:00:00",
    "version_tag": "test"
}
`
		t.Assert(client.PostContent(ctx, "/parse", content), `{"AppId":5,"Name":"test","Type":"test","Cluster":"test","Replicas":1,"ContainerName":"test","ContainerImage":"nginx","VersionTag":"test","Namespace":"test","Id":0,"Status":0,"Metrics":"","InitImage":"","CpuRequest":10,"CpuLimit":100,"MemRequest":100,"MemLimit":1000,"MeshEnabled":0,"ContainerPorts":[{"Port":80,"Type":"tcp","Alias":"别名","Brief":"描述"}],"Labels":[{"Key":"app","Value":"test"}],"NodeSelector":[{"Key":"group","Value":"app"}],"EnvReserve":[{"Key":"NODE_IP","Value":"status.hostIP"}],"EnvGlobal":[{"Type":"string","Key":"NUMBER","Value":"1","Brief":"数据数量"}],"EnvContainer":[{"Type":"string","Key":"NAME","Value":"john","Brief":"用户环境变量"}],"Mounts":[],"LivenessProbe":{"Type":"tcpSocket","Port":80,"Path":"","Brief":"存活探针","Period":5,"InitialDelay":10,"TimeoutSeconds":0},"ReadinessProbe":{"Type":"tcpSocket","Port":80,"Path":"","Brief":"就绪探针","Period":5,"InitialDelay":10,"TimeoutSeconds":0}}`)
	})
}

func Test_Params_Parse_Attr_Pointer1(t *testing.T) {
	type User struct {
		Id   *int
		Name *string
	}
	s := g.Server(guid.S())
	s.BindHandler("/parse1", func(r *ghttp.Request) {
		if m := r.GetMap(); len(m) > 0 {
			var user *User
			if err := r.Parse(&user); err != nil {
				r.Response.WriteExit(err)
			}
			r.Response.WriteExit(user.Id, user.Name)
		}
	})
	s.BindHandler("/parse2", func(r *ghttp.Request) {
		if m := r.GetMap(); len(m) > 0 {
			var user = new(User)
			if err := r.Parse(user); err != nil {
				r.Response.WriteExit(err)
			}
			r.Response.WriteExit(user.Id, user.Name)
		}
	})
	s.SetDumpRouterMap(false)
	s.Start()
	defer s.Shutdown()

	time.Sleep(100 * time.Millisecond)
	gtest.C(t, func(t *gtest.T) {
		client := g.Client()
		client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
		t.Assert(client.PostContent(ctx, "/parse1", `{"id":1,"name":"john"}`), `1john`)
		t.Assert(client.PostContent(ctx, "/parse2", `{"id":1,"name":"john"}`), `1john`)
		t.Assert(client.PostContent(ctx, "/parse2?id=1&name=john"), `1john`)
		t.Assert(client.PostContent(ctx, "/parse2", `id=1&name=john`), `1john`)
	})
}

func Test_Params_Parse_Attr_Pointer2(t *testing.T) {
	type User struct {
		Id *int `v:"required"`
	}
	s := g.Server(guid.S())
	s.BindHandler("/parse", func(r *ghttp.Request) {
		var user *User
		if err := r.Parse(&user); err != nil {
			r.Response.WriteExit(err.Error())
		}
		r.Response.WriteExit(user.Id)
	})
	s.SetDumpRouterMap(false)
	s.Start()
	defer s.Shutdown()

	time.Sleep(100 * time.Millisecond)
	gtest.C(t, func(t *gtest.T) {
		client := g.Client()
		client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
		t.Assert(client.PostContent(ctx, "/parse"), `The Id field is required`)
		t.Assert(client.PostContent(ctx, "/parse?id=1"), `1`)
	})
}

// It does not support this kind of converting yet.
// func Test_Params_Parse_Attr_SliceSlice(t *testing.T) {
//	type User struct {
//		Id     int
//		Name   string
//		Scores [][]int
//	}
//	//	s := g.Server(guid.S())
//	s.BindHandler("/parse", func(r *ghttp.Request) {
//		if m := r.GetMap(); len(m) > 0 {
//			var user *User
//			if err := r.Parse(&user); err != nil {
//				r.Response.WriteExit(err)
//			}
//			r.Response.WriteExit(user.Scores)
//		}
//	})
//	//	s.SetDumpRouterMap(false)
//	s.Start()
//	defer s.Shutdown()
//
//	time.Sleep(100 * time.Millisecond)
//	gtest.C(t, func(t *gtest.T) {
//		client := g.Client()
//		client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
//		t.Assert(client.PostContent(ctx, "/parse", `{"id":1,"name":"john","scores":[[1,2,3]]}`), `1100`)
//	})
// }

func Test_Params_Struct(t *testing.T) {
	type User struct {
		Id    int
		Name  string
		Time  *time.Time
		Pass1 string `p:"password1"`
		Pass2 string `p:"password2" v:"password2 @required|length:2,20|password3#||密码强度不足"`
	}
	s := g.Server(guid.S())
	s.BindHandler("/struct1", func(r *ghttp.Request) {
		if m := r.GetMap(); len(m) > 0 {
			user := new(User)
			if err := r.GetStruct(user); err != nil {
				r.Response.WriteExit(err)
			}
			r.Response.WriteExit(user.Id, user.Name, user.Pass1, user.Pass2)
		}
	})
	s.BindHandler("/struct2", func(r *ghttp.Request) {
		if m := r.GetMap(); len(m) > 0 {
			user := (*User)(nil)
			if err := r.GetStruct(&user); err != nil {
				r.Response.WriteExit(err)
			}
			if user != nil {
				r.Response.WriteExit(user.Id, user.Name, user.Pass1, user.Pass2)
			}
		}
	})
	s.BindHandler("/struct-valid", func(r *ghttp.Request) {
		if m := r.GetMap(); len(m) > 0 {
			user := new(User)
			if err := r.GetStruct(user); err != nil {
				r.Response.WriteExit(err)
			}
			if err := gvalid.New().Data(user).Run(r.Context()); err != nil {
				r.Response.WriteExit(err)
			}
		}
	})
	s.BindHandler("/parse", func(r *ghttp.Request) {
		if m := r.GetMap(); len(m) > 0 {
			var user *User
			if err := r.Parse(&user); err != nil {
				r.Response.WriteExit(err)
			}
			r.Response.WriteExit(user.Id, user.Name, user.Pass1, user.Pass2)
		}
	})
	s.SetDumpRouterMap(false)
	s.Start()
	defer s.Shutdown()

	time.Sleep(100 * time.Millisecond)
	gtest.C(t, func(t *gtest.T) {
		client := g.Client()
		client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
		t.Assert(client.GetContent(ctx, "/struct1", `id=1&name=john&password1=123&password2=456`), `1john123456`)
		t.Assert(client.PostContent(ctx, "/struct1", `id=1&name=john&password1=123&password2=456`), `1john123456`)
		t.Assert(client.PostContent(ctx, "/struct2", `id=1&name=john&password1=123&password2=456`), `1john123456`)
		t.Assert(client.PostContent(ctx, "/struct2", ``), ``)
		t.Assert(client.PostContent(ctx, "/struct-valid", `id=1&name=john&password1=123&password2=0`), "The password2 value `0` length must be between 2 and 20; 密码强度不足")
		t.Assert(client.PostContent(ctx, "/parse", `id=1&name=john&password1=123&password2=0`), "The password2 value `0` length must be between 2 and 20")
		t.Assert(client.PostContent(ctx, "/parse", `{"id":1,"name":"john","password1":"123Abc!@#","password2":"123Abc!@#"}`), `1john123Abc!@#123Abc!@#`)
	})
}

func Test_Params_Structs(t *testing.T) {
	type User struct {
		Id    int
		Name  string
		Time  *time.Time
		Pass1 string `p:"password1"`
		Pass2 string `p:"password2" v:"password2 @required|length:2,20|password3#||密码强度不足"`
	}
	s := g.Server(guid.S())
	s.BindHandler("/parse1", func(r *ghttp.Request) {
		var users []*User
		if err := r.Parse(&users); err != nil {
			r.Response.WriteExit(err)
		}
		r.Response.WriteExit(users[0].Id, users[1].Id)
	})
	s.SetDumpRouterMap(false)
	s.Start()
	defer s.Shutdown()

	time.Sleep(100 * time.Millisecond)
	gtest.C(t, func(t *gtest.T) {
		client := g.Client()
		client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
		t.Assert(client.PostContent(ctx,
			"/parse1",
			`[{"id":1,"name":"john","password1":"123Abc!@#","password2":"123Abc!@#"}, {"id":2,"name":"john","password1":"123Abc!@#","password2":"123Abc!@#"}]`),
			`12`,
		)
	})
}

func Test_Params_Struct_Validation(t *testing.T) {
	type User struct {
		Id   int    `v:"required"`
		Name string `v:"name@required-with:id"`
	}
	s := g.Server(guid.S())
	s.Group("/", func(group *ghttp.RouterGroup) {
		group.ALL("/", func(r *ghttp.Request) {
			var (
				err  error
				user *User
			)
			err = r.Parse(&user)
			if err != nil {
				r.Response.WriteExit(err)
			}
			r.Response.WriteExit(user.Id, user.Name)
		})
	})
	s.SetDumpRouterMap(false)
	s.Start()
	defer s.Shutdown()

	time.Sleep(100 * time.Millisecond)
	gtest.C(t, func(t *gtest.T) {
		c := g.Client()
		c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
		t.Assert(c.GetContent(ctx, "/", ``), `The Id field is required`)
		t.Assert(c.GetContent(ctx, "/", `id=1&name=john`), `1john`)
		t.Assert(c.PostContent(ctx, "/", `id=1&name=john&password1=123&password2=456`), `1john`)
		t.Assert(c.PostContent(ctx, "/", `id=1`), `The name field is required`)
	})
}

// https://github.com/gogf/gf/issues/1488
func Test_Params_Parse_Issue1488(t *testing.T) {
	s := g.Server(guid.S())
	s.Group("/", func(group *ghttp.RouterGroup) {
		group.ALL("/", func(r *ghttp.Request) {
			type Request struct {
				Type         []int  `p:"type"`
				Keyword      string `p:"keyword"`
				Limit        int    `p:"per_page" d:"10"`
				Page         int    `p:"page" d:"1"`
				Order        string
				CreatedAtLte string
				CreatedAtGte string
				CreatorID    []int
			}
			for i := 0; i < 10; i++ {
				r.SetParamMap(g.Map{
					"type[]":           0,
					"keyword":          "",
					"t_start":          "",
					"t_end":            "",
					"reserve_at_start": "",
					"reserve_at_end":   "",
					"user_name":        "",
					"flag":             "",
					"per_page":         6,
				})
				var parsed Request
				_ = r.Parse(&parsed)
				r.Response.Write(parsed.Page, parsed.Limit)
			}
		})
	})
	s.SetDumpRouterMap(false)
	s.Start()
	defer s.Shutdown()

	time.Sleep(100 * time.Millisecond)
	gtest.C(t, func(t *gtest.T) {
		c := g.Client()
		c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
		t.Assert(c.GetContent(ctx, "/", ``), `16161616161616161616`)
	})
}
