package fasthttp

import (
	"bytes"
	"fmt"
	"net/url"
	"reflect"
	"strings"
	"testing"
	"time"

	"github.com/valyala/bytebufferpool"
)

func TestDecodeArgAppend(t *testing.T) {
	t.Parallel()

	testDecodeArgAppend(t, "", "")
	testDecodeArgAppend(t, "foobar", "foobar")
	testDecodeArgAppend(t, "тест", "тест")
	testDecodeArgAppend(t, "a%", "a%")
	testDecodeArgAppend(t, "%a%21", "%a!")
	testDecodeArgAppend(t, "ab%test", "ab%test")
	testDecodeArgAppend(t, "d%тестF", "d%тестF")
	testDecodeArgAppend(t, "a%\xffb%20c", "a%\xffb c")
	testDecodeArgAppend(t, "foo%20bar", "foo bar")
	testDecodeArgAppend(t, "f.o%2C1%3A2%2F4=%7E%60%21%40%23%24%25%5E%26*%28%29_-%3D%2B%5C%7C%2F%5B%5D%7B%7D%3B%3A%27%22%3C%3E%2C.%2F%3F",
		"f.o,1:2/4=~`!@#$%^&*()_-=+\\|/[]{};:'\"<>,./?")
}

func testDecodeArgAppend(t *testing.T, s, expectedResult string) {
	result := decodeArgAppend(nil, []byte(s))
	if string(result) != expectedResult {
		t.Fatalf("unexpected decodeArgAppend(%q)=%q; expecting %q", s, result, expectedResult)
	}
}

func TestArgsAdd(t *testing.T) {
	t.Parallel()

	var a Args
	a.Add("foo", "bar")
	a.Add("foo", "baz")
	a.Add("foo", "1")
	a.Add("ba", "23")
	a.Add("foo", "")
	a.AddNoValue("foo")
	if a.Len() != 6 {
		t.Fatalf("unexpected number of elements: %d. Expecting 6", a.Len())
	}
	s := a.String()
	expectedS := "foo=bar&foo=baz&foo=1&ba=23&foo=&foo"
	if s != expectedS {
		t.Fatalf("unexpected result: %q. Expecting %q", s, expectedS)
	}

	a.Sort(bytes.Compare)
	ss := a.String()
	expectedSS := "ba=23&foo=&foo&foo=1&foo=bar&foo=baz"
	if ss != expectedSS {
		t.Fatalf("unexpected result: %q. Expecting %q", ss, expectedSS)
	}

	var a1 Args
	a1.Parse(s)
	if a1.Len() != 6 {
		t.Fatalf("unexpected number of elements: %d. Expecting 6", a.Len())
	}

	var barFound, bazFound, oneFound, emptyFound1, emptyFound2, baFound bool
	for k, v := range a1.All() {
		switch string(k) {
		case "foo":
			switch string(v) {
			case "bar":
				barFound = true
			case "baz":
				bazFound = true
			case "1":
				oneFound = true
			case "":
				if emptyFound1 {
					emptyFound2 = true
				} else {
					emptyFound1 = true
				}
			default:
				t.Fatalf("unexpected value %q", v)
			}
		case "ba":
			if string(v) != "23" {
				t.Fatalf("unexpected value: %q. Expecting %q", v, "23")
			}
			baFound = true
		default:
			t.Fatalf("unexpected key found %q", k)
		}
	}
	if !barFound || !bazFound || !oneFound || !emptyFound1 || !emptyFound2 || !baFound {
		t.Fatalf("something is missing: %v, %v, %v, %v, %v, %v", barFound, bazFound, oneFound, emptyFound1, emptyFound2, baFound)
	}
}

func TestArgsAcquireReleaseSequential(t *testing.T) {
	testArgsAcquireRelease(t)
}

func TestArgsAcquireReleaseConcurrent(t *testing.T) {
	ch := make(chan struct{}, 10)
	for i := 0; i < 10; i++ {
		go func() {
			testArgsAcquireRelease(t)
			ch <- struct{}{}
		}()
	}
	for i := 0; i < 10; i++ {
		select {
		case <-ch:
		case <-time.After(time.Second):
			t.Fatalf("timeout")
		}
	}
}

func testArgsAcquireRelease(t *testing.T) {
	a := AcquireArgs()

	for i := 0; i < 10; i++ {
		k := fmt.Sprintf("key_%d", i)
		v := fmt.Sprintf("value_%d", i*3+123)
		a.Set(k, v)
	}

	s := a.String()
	a.Reset()
	a.Parse(s)

	for i := 0; i < 10; i++ {
		k := fmt.Sprintf("key_%d", i)
		expectedV := fmt.Sprintf("value_%d", i*3+123)
		v := a.Peek(k)
		if string(v) != expectedV {
			t.Fatalf("unexpected value %q for key %q. Expecting %q", v, k, expectedV)
		}
	}

	ReleaseArgs(a)
}

func TestArgsPeekMulti(t *testing.T) {
	t.Parallel()

	var a Args
	a.Parse("foo=123&bar=121&foo=321&foo=&barz=sdf")

	vv := a.PeekMulti("foo")
	expectedVV := [][]byte{
		[]byte("123"),
		[]byte("321"),
		[]byte(nil),
	}
	if !reflect.DeepEqual(vv, expectedVV) {
		t.Fatalf("unexpected vv\n%#v\nExpecting\n%#v\n", vv, expectedVV)
	}

	vv = a.PeekMulti("aaaa")
	if len(vv) > 0 {
		t.Fatalf("expecting empty result for non-existing key. Got %#v", vv)
	}

	vv = a.PeekMulti("bar")
	expectedVV = [][]byte{[]byte("121")}
	if !reflect.DeepEqual(vv, expectedVV) {
		t.Fatalf("unexpected vv\n%#v\nExpecting\n%#v\n", vv, expectedVV)
	}
}

func TestArgsEscape(t *testing.T) {
	t.Parallel()

	testArgsEscape(t, "foo", "bar", "foo=bar")

	// Test all characters
	k := "f.o,1:2/4"
	v := make([]byte, 256)
	for i := 0; i < 256; i++ {
		v[i] = byte(i)
	}
	u := url.Values{}
	u.Add(k, string(v))
	testArgsEscape(t, k, string(v), u.Encode())
}

func testArgsEscape(t *testing.T, k, v, expectedS string) {
	var a Args
	a.Set(k, v)
	s := a.String()
	if s != expectedS {
		t.Fatalf("unexpected args %q. Expecting %q. k=%q, v=%q", s, expectedS, k, v)
	}
}

func TestPathEscape(t *testing.T) {
	t.Parallel()

	testPathEscape(t, "/foo/bar")
	testPathEscape(t, "")
	testPathEscape(t, "/")
	testPathEscape(t, "//")
	testPathEscape(t, "*") // See https://github.com/golang/go/issues/11202

	// Test all characters
	pathSegment := make([]byte, 256)
	for i := 0; i < 256; i++ {
		pathSegment[i] = byte(i)
	}
	testPathEscape(t, "/foo/"+string(pathSegment))
}

func testPathEscape(t *testing.T, s string) {
	u := url.URL{Path: s}
	expectedS := u.EscapedPath()
	res := string(appendQuotedPath(nil, []byte(s)))
	if res != expectedS {
		t.Fatalf("unexpected args %q. Expecting %q.", res, expectedS)
	}
}

func TestArgsWriteTo(t *testing.T) {
	t.Parallel()

	s := "foo=bar&baz=123&aaa=bbb"

	var a Args
	a.Parse(s)

	var w bytebufferpool.ByteBuffer
	n, err := a.WriteTo(&w)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if n != int64(len(s)) {
		t.Fatalf("unexpected n: %d. Expecting %d", n, len(s))
	}
	result := string(w.B)
	if result != s {
		t.Fatalf("unexpected result %q. Expecting %q", result, s)
	}
}

func TestArgsGetBool(t *testing.T) {
	t.Parallel()

	testArgsGetBool(t, "", false)
	testArgsGetBool(t, "0", false)
	testArgsGetBool(t, "n", false)
	testArgsGetBool(t, "no", false)
	testArgsGetBool(t, "1", true)
	testArgsGetBool(t, "y", true)
	testArgsGetBool(t, "yes", true)

	testArgsGetBool(t, "123", false)
	testArgsGetBool(t, "foobar", false)
}

func testArgsGetBool(t *testing.T, value string, expectedResult bool) {
	var a Args
	a.Parse("v=" + value)

	result := a.GetBool("v")
	if result != expectedResult {
		t.Fatalf("unexpected result %v. Expecting %v for value %q", result, expectedResult, value)
	}
}

func TestArgsUint(t *testing.T) {
	t.Parallel()

	var a Args
	a.SetUint("foo", 123)
	a.SetUint("bar", 0)
	a.SetUint("aaaa", 34566)

	expectedS := "foo=123&bar=0&aaaa=34566"
	s := string(a.QueryString())
	if s != expectedS {
		t.Fatalf("unexpected args %q. Expecting %q", s, expectedS)
	}

	if a.GetUintOrZero("foo") != 123 {
		t.Fatalf("unexpected arg value %d. Expecting %d", a.GetUintOrZero("foo"), 123)
	}
	if a.GetUintOrZero("bar") != 0 {
		t.Fatalf("unexpected arg value %d. Expecting %d", a.GetUintOrZero("bar"), 0)
	}
	if a.GetUintOrZero("aaaa") != 34566 {
		t.Fatalf("unexpected arg value %d. Expecting %d", a.GetUintOrZero("aaaa"), 34566)
	}

	if string(a.Peek("foo")) != "123" {
		t.Fatalf("unexpected arg value %q. Expecting %q", a.Peek("foo"), "123")
	}
	if string(a.Peek("bar")) != "0" {
		t.Fatalf("unexpected arg value %q. Expecting %q", a.Peek("bar"), "0")
	}
	if string(a.Peek("aaaa")) != "34566" {
		t.Fatalf("unexpected arg value %q. Expecting %q", a.Peek("aaaa"), "34566")
	}
}

func TestArgsCopyTo(t *testing.T) {
	t.Parallel()

	var a Args

	// empty args
	testCopyTo(t, &a)

	a.Set("foo", "bar")
	testCopyTo(t, &a)

	a.Set("xxx", "yyy")
	a.AddNoValue("ba")
	testCopyTo(t, &a)

	a.Del("foo")
	testCopyTo(t, &a)
}

func testCopyTo(t *testing.T, a *Args) {
	keys := make(map[string]struct{})
	for k := range a.All() {
		keys[string(k)] = struct{}{}
	}

	var b Args
	a.CopyTo(&b)

	if !reflect.DeepEqual(a, &b) {
		t.Fatalf("ArgsCopyTo fail, a: \n%+v\nb: \n%+v\n", a, &b)
	}

	for k := range b.All() {
		if _, ok := keys[string(k)]; !ok {
			t.Fatalf("unexpected key %q after copying from %q", k, a.String())
		}
		delete(keys, string(k))
	}
	if len(keys) > 0 {
		t.Fatalf("missing keys %#v after copying from %q", keys, a.String())
	}
}

func TestArgsVisitAll(t *testing.T) {
	t.Parallel()

	var a Args
	a.Set("foo", "bar")

	i := 0
	a.VisitAll(func(k, v []byte) {
		if string(k) != "foo" {
			t.Fatalf("unexpected key %q. Expected %q", k, "foo")
		}
		if string(v) != "bar" {
			t.Fatalf("unexpected value %q. Expected %q", v, "bar")
		}
		i++
	})
	if i != 1 {
		t.Fatalf("unexpected number of VisitAll calls: %d. Expected %d", i, 1)
	}
}

func TestArgsStringCompose(t *testing.T) {
	t.Parallel()

	var a Args
	a.Set("foo", "bar")
	a.Set("aa", "bbb")
	a.Set("привет", "мир")
	a.SetNoValue("bb")
	a.Set("", "xxxx")
	a.Set("cvx", "")
	a.SetNoValue("novalue")

	expectedS := "foo=bar&aa=bbb&%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82=%D0%BC%D0%B8%D1%80&bb&=xxxx&cvx=&novalue"
	s := a.String()
	if s != expectedS {
		t.Fatalf("Unexpected string %q. Expected %q", s, expectedS)
	}
}

func TestArgsString(t *testing.T) {
	t.Parallel()

	var a Args

	testArgsString(t, &a, "")
	testArgsString(t, &a, "foobar")
	testArgsString(t, &a, "foo=bar")
	testArgsString(t, &a, "foo=bar&baz=sss")
	testArgsString(t, &a, "")
	testArgsString(t, &a, "f+o=x.x%2A-_8x%D0%BF%D1%80%D0%B8%D0%B2%D0%B5aaa&sdf=ss")
	testArgsString(t, &a, "=asdfsdf")
}

func testArgsString(t *testing.T, a *Args, s string) {
	a.Parse(s)
	s1 := a.String()
	if s != s1 {
		t.Fatalf("Unexpected args %q. Expected %q", s1, s)
	}
}

func TestArgsSetGetDel(t *testing.T) {
	t.Parallel()

	var a Args

	if len(a.Peek("foo")) > 0 {
		t.Fatalf("Unexpected value: %q", a.Peek("foo"))
	}
	if len(a.Peek("")) > 0 {
		t.Fatalf("Unexpected value: %q", a.Peek(""))
	}
	a.Del("xxx")

	for j := 0; j < 3; j++ {
		for i := 0; i < 10; i++ {
			k := fmt.Sprintf("foo%d", i)
			v := fmt.Sprintf("bar_%d", i)
			a.Set(k, v)
			if string(a.Peek(k)) != v {
				t.Fatalf("Unexpected value: %q. Expected %q", a.Peek(k), v)
			}
		}
	}
	for i := 0; i < 10; i++ {
		k := fmt.Sprintf("foo%d", i)
		v := fmt.Sprintf("bar_%d", i)
		if string(a.Peek(k)) != v {
			t.Fatalf("Unexpected value: %q. Expected %q", a.Peek(k), v)
		}
		a.Del(k)
		if len(a.Peek(k)) != 0 {
			t.Fatalf("Unexpected value: %q. Expected %q", a.Peek(k), "")
		}
	}

	a.Parse("aaa=xxx&bb=aa")
	if len(a.Peek("foo0")) != 0 {
		t.Fatalf("Unexpected value %q", a.Peek("foo0"))
	}
	if string(a.Peek("aaa")) != "xxx" {
		t.Fatalf("Unexpected value %q. Expected %q", a.Peek("aaa"), "xxx")
	}
	if string(a.Peek("bb")) != "aa" {
		t.Fatalf("Unexpected value %q. Expected %q", a.Peek("bb"), "aa")
	}

	for i := 0; i < 10; i++ {
		k := fmt.Sprintf("xx%d", i)
		v := fmt.Sprintf("yy%d", i)
		a.Set(k, v)
		if string(a.Peek(k)) != v {
			t.Fatalf("Unexpected value: %q. Expected %q", a.Peek(k), v)
		}
	}
	for i := 5; i < 10; i++ {
		k := fmt.Sprintf("xx%d", i)
		v := fmt.Sprintf("yy%d", i)
		if string(a.Peek(k)) != v {
			t.Fatalf("Unexpected value: %q. Expected %q", a.Peek(k), v)
		}
		a.Del(k)
		if len(a.Peek(k)) != 0 {
			t.Fatalf("Unexpected value: %q. Expected %q", a.Peek(k), "")
		}
	}
}

func TestArgsParse(t *testing.T) {
	t.Parallel()

	var a Args

	// empty args
	testArgsParse(t, &a, "", 0, "foo=", "bar=", "=")

	// arg without value
	testArgsParse(t, &a, "foo1", 1, "foo=", "bar=", "=")

	// arg without value, but with equal sign
	testArgsParse(t, &a, "foo2=", 1, "foo=", "bar=", "=")

	// arg with value
	testArgsParse(t, &a, "foo3=bar1", 1, "foo3=bar1", "bar=", "=")

	// empty key
	testArgsParse(t, &a, "=bar2", 1, "foo=", "=bar2", "bar2=")

	// missing kv
	testArgsParse(t, &a, "&&&&", 0, "foo=", "bar=", "=")

	// multiple values with the same key
	testArgsParse(t, &a, "x=1&x=2&x=3", 3, "x=1")

	// multiple args
	testArgsParse(t, &a, "&&&qw=er&tyx=124&&&zxc_ss=2234&&", 3, "qw=er", "tyx=124", "zxc_ss=2234")

	// multiple args without values
	testArgsParse(t, &a, "&&a&&b&&bar&baz", 4, "a=", "b=", "bar=", "baz=")

	// values with '='
	testArgsParse(t, &a, "zz=1&k=v=v=a=a=s", 2, "k=v=v=a=a=s", "zz=1")

	// mixed '=' and '&'
	testArgsParse(t, &a, "sss&z=dsf=&df", 3, "sss=", "z=dsf=", "df=")

	// encoded args
	testArgsParse(t, &a, "f+o%20o=%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82+test", 1, "f o o=привет test")

	// invalid percent encoding
	testArgsParse(t, &a, "f%=x&qw%z=d%0k%20p&%%20=%%%20x", 3, "f%=x", "qw%z=d%0k p", "% =%% x")

	// special chars
	testArgsParse(t, &a, "a.b,c:d/e=f.g,h:i/q", 1, "a.b,c:d/e=f.g,h:i/q")
}

func TestArgsHas(t *testing.T) {
	t.Parallel()

	var a Args

	// single arg
	testArgsHas(t, &a, "foo", "foo")
	testArgsHasNot(t, &a, "foo", "bar", "baz", "")

	// multi args without values
	testArgsHas(t, &a, "foo&bar", "foo", "bar")
	testArgsHasNot(t, &a, "foo&bar", "", "aaaa")

	// multi args
	testArgsHas(t, &a, "b=xx&=aaa&c=", "b", "", "c")
	testArgsHasNot(t, &a, "b=xx&=aaa&c=", "xx", "aaa", "foo")

	// encoded args
	testArgsHas(t, &a, "a+b=c+d%20%20e", "a b")
	testArgsHasNot(t, &a, "a+b=c+d", "a+b", "c+d")
}

func testArgsHas(t *testing.T, a *Args, s string, expectedKeys ...string) {
	a.Parse(s)
	for _, key := range expectedKeys {
		if !a.Has(key) {
			t.Fatalf("Missing key %q in %q", key, s)
		}
	}
}

func testArgsHasNot(t *testing.T, a *Args, s string, unexpectedKeys ...string) {
	a.Parse(s)
	for _, key := range unexpectedKeys {
		if a.Has(key) {
			t.Fatalf("Unexpected key %q in %q", key, s)
		}
	}
}

func testArgsParse(t *testing.T, a *Args, s string, expectedLen int, expectedArgs ...string) {
	a.Parse(s)
	if a.Len() != expectedLen {
		t.Fatalf("Unexpected args len %d. Expected %d. s=%q", a.Len(), expectedLen, s)
	}
	for _, xx := range expectedArgs {
		tmp := strings.SplitN(xx, "=", 2)
		k := tmp[0]
		v := tmp[1]
		buf := a.Peek(k)
		if string(buf) != v {
			t.Fatalf("Unexpected value for key=%q: %q. Expected %q. s=%q", k, buf, v, s)
		}
	}
}

func TestArgsDeleteAll(t *testing.T) {
	t.Parallel()
	var a Args
	a.Add("q1", "foo")
	a.Add("q1", "bar")
	a.Add("q1", "baz")
	a.Add("q1", "quux")
	a.Add("q2", "1234")
	a.Del("q1")
	if a.Len() != 1 || a.Has("q1") {
		t.Fatalf("Expected q1 arg to be completely deleted. Current Args: %q", a.String())
	}
}

func TestIssue932(t *testing.T) {
	t.Parallel()
	var a []argsKV

	a = setArg(a, "t1", "ok", argsHasValue)
	a = setArg(a, "t2", "", argsHasValue)
	a = setArg(a, "t1", "", argsHasValue)
	a = setArgBytes(a, s2b("t3"), []byte{}, argsHasValue)
	a = setArgBytes(a, s2b("t4"), nil, argsHasValue)

	if peekArgStr(a, "t1") == nil {
		t.Error("nil not expected for t1")
	}
	if peekArgStr(a, "t2") == nil {
		t.Error("nil not expected for t2")
	}
	if peekArgStr(a, "t3") == nil {
		t.Error("nil not expected for t3")
	}
	if peekArgStr(a, "t4") != nil {
		t.Error("nil expected for t4")
	}
}
