package flagutil

import (
	"flag"
	"os"
	"reflect"
	"testing"
	"time"
)

var (
	fooFlagString   ArrayString
	fooFlagDuration ArrayDuration
	fooFlagBool     ArrayBool
	fooFlagInt      ArrayInt
	fooFlagBytes    ArrayBytes
)

func init() {
	os.Args = append(os.Args, "-fooFlagString=foo", "-fooFlagString=bar")
	os.Args = append(os.Args, "-fooFlagDuration=10s", "-fooFlagDuration=5m")
	os.Args = append(os.Args, "-fooFlagBool=true", "-fooFlagBool=false,true", "-fooFlagBool")
	os.Args = append(os.Args, "-fooFlagInt=1", "-fooFlagInt=2,3")
	os.Args = append(os.Args, "-fooFlagBytes=10MB", "-fooFlagBytes=23,10kib")
	flag.Var(&fooFlagString, "fooFlagString", "test")
	flag.Var(&fooFlagDuration, "fooFlagDuration", "test")
	flag.Var(&fooFlagBool, "fooFlagBool", "test")
	flag.Var(&fooFlagInt, "fooFlagInt", "test")
	flag.Var(&fooFlagBytes, "fooFlagBytes", "test")
}

func TestMain(m *testing.M) {
	flag.Parse()
	os.Exit(m.Run())
}

func TestArrayString(t *testing.T) {
	expected := ArrayString{
		"foo",
		"bar",
	}
	if !reflect.DeepEqual(expected, fooFlagString) {
		t.Fatalf("unexpected flag values; got\n%q\nwant\n%q", fooFlagString, expected)
	}
}

func TestArrayString_Set(t *testing.T) {
	f := func(s, expectedResult string) {
		t.Helper()
		var a ArrayString
		if err := a.Set(s); err != nil {
			t.Fatalf("unexpected error: %s", err)
		}
		result := a.String()
		if result != expectedResult {
			t.Fatalf("unexpected values parsed;\ngot\n%s\nwant\n%s", result, expectedResult)
		}
	}
	// Zero args
	f("", "")

	// Single arg
	f(`foo`, `foo`)
	f(`fo"o`, `"fo\"o"`)
	f(`fo'o`, `"fo'o"`)
	f(`fo{o`, `"fo{o"`)
	f(`fo[o`, `"fo[o"`)
	f(`fo(o`, `"fo(o"`)

	// Single arg with Prometheus label filters
	f(`foo{bar="baz",x="y"}`, `"foo{bar=\"baz\",x=\"y\"}"`)
	f(`foo{bar="ba}z",x="y"}`, `"foo{bar=\"ba}z\",x=\"y\"}"`)
	f(`foo{bar='baz',x="y"}`, `"foo{bar='baz',x=\"y\"}"`)
	f(`foo{bar='baz',x='y'}`, `"foo{bar='baz',x='y'}"`)
	f(`foo{bar='ba}z',x='y'}`, `"foo{bar='ba}z',x='y'}"`)
	f(`{foo="ba[r",baz='a'}`, `"{foo=\"ba[r\",baz='a'}"`)

	// Single arg with JSON
	f(`[1,2,3]`, `"[1,2,3]"`)
	f(`{"foo":"ba,r",baz:x}`, `"{\"foo\":\"ba,r\",baz:x}"`)

	// Single quoted arg
	f(`"foo"`, `foo`)
	f(`"fo,'o"`, `"fo,'o"`)
	f(`"f\\o,\'\"o"`, `"f\\o,\\'\"o"`)
	f(`"foo{bar='baz',x='y'}"`, `"foo{bar='baz',x='y'}"`)
	f(`'foo'`, `foo`)
	f(`'fo,"o'`, `"fo,\"o"`)
	f(`'f\\o,\'\"o'`, `"f\\o,'\\\"o"`)
	f(`'foo{bar="baz",x="y"}'`, `"foo{bar=\"baz\",x=\"y\"}"`)

	// Multiple args
	f(`foo,bar,baz`, `foo,bar,baz`)
	f(`"foo",'bar',{[(ba'",z"`, `foo,bar,"{[(ba'\",z\""`)
	f(`foo,b"'ar,"baz,d`, `foo,"b\"'ar,\"baz",d`)
	f(`{foo="b,ar"},baz{x="y",z="d"}`, `"{foo=\"b,ar\"}","baz{x=\"y\",z=\"d\"}"`)

	// Empty args
	f(`""`, ``)
	f(`''`, ``)
	f(`,`, `,`)
	f(`,foo,,ba"r,`, `,foo,,"ba\"r",`)

	// Special chars inside double quotes
	f(`"foo,b\nar"`, `"foo,b\nar"`)
	f(`"foo\x23bar"`, "foo\x23bar")
}

func TestArrayString_GetOptionalArg(t *testing.T) {
	f := func(s string, argIdx int, expectedValue string) {
		t.Helper()
		var a ArrayString
		if err := a.Set(s); err != nil {
			t.Fatalf("unexpected error: %s", err)
		}
		v := a.GetOptionalArg(argIdx)
		if v != expectedValue {
			t.Fatalf("unexpected value; got %q; want %q", v, expectedValue)
		}
	}
	f("", 0, "")
	f("", 1, "")
	f("foo", 0, "foo")
	f("foo", 23, "foo")
	f("foo,bar", 0, "foo")
	f("foo,bar", 1, "bar")
	f("foo,bar", 2, "")
}

func TestArrayString_String(t *testing.T) {
	f := func(s string) {
		t.Helper()
		var a ArrayString
		if err := a.Set(s); err != nil {
			t.Fatalf("unexpected error: %s", err)
		}
		result := a.String()
		if result != s {
			t.Fatalf("unexpected string;\ngot\n%s\nwant\n%s", result, s)
		}
	}
	f("")
	f("foo")
	f("foo,bar")
	f(",")
	f(",foo,")
	f(`", foo","b\"ar",`)
	f(`,"\nfoo\\",bar`)
	f(`"foo{bar=~\"baz\",a!=\"b\"}","{a='b,{[(c'}"`)
}

func TestArrayDuration(t *testing.T) {
	expected := ArrayDuration{
		a: []time.Duration{
			time.Second * 10,
			time.Minute * 5,
		},
	}
	if !reflect.DeepEqual(expected, fooFlagDuration) {
		t.Fatalf("unexpected flag values; got\n%s\nwant\n%s", &fooFlagDuration, &expected)
	}
}

func TestArrayDuration_Set(t *testing.T) {
	f := func(s, expectedResult string) {
		t.Helper()
		var a ArrayDuration
		a.defaultValue = 42 * time.Second
		if err := a.Set(s); err != nil {
			t.Fatalf("unexpected error: %s", err)
		}
		result := a.String()
		if result != expectedResult {
			t.Fatalf("unexpected values parsed;\ngot\n%q\nwant\n%q", result, expectedResult)
		}
	}
	f("", "42s")
	f(`1m`, `1m0s`)
	f(`5m,1s,1h`, `5m0s,1s,1h0m0s`)
	f(`5m,,1h`, `5m0s,42s,1h0m0s`)
}

func TestArrayDuration_GetOptionalArg(t *testing.T) {
	f := func(s string, argIdx int, defaultValue, expectedValue time.Duration) {
		t.Helper()
		var a ArrayDuration
		a.defaultValue = defaultValue
		if err := a.Set(s); err != nil {
			t.Fatalf("unexpected error: %s", err)
		}
		v := a.GetOptionalArg(argIdx)
		if v != expectedValue {
			t.Fatalf("unexpected value; got %q; want %q", v, expectedValue)
		}
	}
	f("", 0, time.Second, time.Second)
	f("", 1, time.Minute, time.Minute)
	f("10s,1m", 1, time.Minute, time.Minute)
	f("10s", 3, time.Minute, time.Second*10)
}

func TestArrayDuration_String(t *testing.T) {
	f := func(s string) {
		t.Helper()
		var a ArrayDuration
		if err := a.Set(s); err != nil {
			t.Fatalf("unexpected error: %s", err)
		}
		result := a.String()
		if result != s {
			t.Fatalf("unexpected string;\ngot\n%s\nwant\n%s", result, s)
		}
	}
	f("10s,1m0s")
	f("5m0s,1s")
}

func TestArrayBool(t *testing.T) {
	expected := ArrayBool{
		true, false, true, true,
	}
	if !reflect.DeepEqual(expected, fooFlagBool) {
		t.Fatalf("unexpected flag values; got\n%v\nwant\n%v", fooFlagBool, expected)
	}
}

func TestArrayBool_Set(t *testing.T) {
	f := func(s, expectedResult string) {
		t.Helper()
		var a ArrayBool
		if err := a.Set(s); err != nil {
			t.Fatalf("unexpected error: %s", err)
		}
		result := a.String()
		if result != expectedResult {
			t.Fatalf("unexpected values parsed;\ngot\n%v\nwant\n%v", result, expectedResult)
		}
	}
	f("", "false")
	f(`true`, `true`)
	f(`false,True,False`, `false,true,false`)
	f(`1,,False`, `true,false,false`)
}

func TestArrayBool_GetOptionalArg(t *testing.T) {
	f := func(s string, argIdx int, expectedValue bool) {
		t.Helper()
		var a ArrayBool
		if err := a.Set(s); err != nil {
			t.Fatalf("unexpected error: %s", err)
		}
		v := a.GetOptionalArg(argIdx)
		if v != expectedValue {
			t.Fatalf("unexpected value; got %v; want %v", v, expectedValue)
		}
	}
	f("", 0, false)
	f("", 1, false)
	f("true,true,false", 1, true)
	f("true", 2, true)
}

func TestArrayBool_String(t *testing.T) {
	f := func(s string) {
		t.Helper()
		var a ArrayBool
		if err := a.Set(s); err != nil {
			t.Fatalf("unexpected error: %s", err)
		}
		result := a.String()
		if result != s {
			t.Fatalf("unexpected string;\ngot\n%s\nwant\n%s", result, s)
		}
	}
	f("true")
	f("true,false")
	f("false,true")
}

func TestArrayInt(t *testing.T) {
	expected := ArrayInt{
		a: []int{1, 2, 3},
	}
	if !reflect.DeepEqual(expected, fooFlagInt) {
		t.Fatalf("unexpected flag values; got\n%d\nwant\n%d", fooFlagInt, expected)
	}
}

func TestArrayInt_Set(t *testing.T) {
	f := func(s, expectedResult string, expectedValues []int) {
		t.Helper()
		var a ArrayInt
		a.defaultValue = 42
		if err := a.Set(s); err != nil {
			t.Fatalf("unexpected error: %s", err)
		}
		result := a.String()
		if result != expectedResult {
			t.Fatalf("unexpected values parsed;\ngot\n%q\nwant\n%q", result, expectedResult)
		}
		values := a.Values()
		if !reflect.DeepEqual(values, expectedValues) {
			t.Fatalf("unexpected values;\ngot\n%d\nwant\n%d", values, expectedValues)
		}
	}
	f("", "42", []int{42})
	f(`1`, `1`, []int{1})
	f(`-2,3,-64`, `-2,3,-64`, []int{-2, 3, -64})
	f(`,,-64,`, `42,42,-64,42`, []int{42, 42, -64, 42})
}

func TestArrayInt_GetOptionalArg(t *testing.T) {
	f := func(s string, argIdx, defaultValue, expectedValue int) {
		t.Helper()
		var a ArrayInt
		a.defaultValue = defaultValue
		if err := a.Set(s); err != nil {
			t.Fatalf("unexpected error: %s", err)
		}
		v := a.GetOptionalArg(argIdx)
		if v != expectedValue {
			t.Fatalf("unexpected value; got %d; want %d", v, expectedValue)
		}
	}
	f("", 0, 123, 123)
	f("", 1, -34, -34)
	f("10,1", 1, 234, 1)
	f("10", 3, -34, 10)
}

func TestArrayInt_String(t *testing.T) {
	f := func(s string) {
		t.Helper()
		var a ArrayInt
		if err := a.Set(s); err != nil {
			t.Fatalf("unexpected error: %s", err)
		}
		result := a.String()
		if result != s {
			t.Fatalf("unexpected string;\ngot\n%s\nwant\n%s", result, s)
		}
	}
	f("10,1")
	f("-5,1,123")
}

func TestArrayBytes(t *testing.T) {
	expected := []int64{10000000, 23, 10240}
	result := make([]int64, len(fooFlagBytes.a))
	for i, b := range fooFlagBytes.a {
		result[i] = b.N
	}
	if !reflect.DeepEqual(expected, result) {
		t.Fatalf("unexpected flag values; got\n%d\nwant\n%d", result, expected)
	}
}

func TestArrayBytes_Set(t *testing.T) {
	f := func(s, expectedResult string) {
		t.Helper()
		var a ArrayBytes
		a.defaultValue = 42
		if err := a.Set(s); err != nil {
			t.Fatalf("unexpected error: %s", err)
		}
		result := a.String()
		if result != expectedResult {
			t.Fatalf("unexpected values parsed;\ngot\n%s\nwant\n%s", result, expectedResult)
		}
	}
	f("", "42")
	f(`1`, `1`)
	f(`-2,3,10kb`, `-2,3,10KB`)
	f(`,,10kb`, `42,42,10KB`)
}

func TestArrayBytes_GetOptionalArg(t *testing.T) {
	f := func(s string, argIdx int, defaultValue, expectedValue int64) {
		t.Helper()
		var a ArrayBytes
		a.defaultValue = defaultValue
		if err := a.Set(s); err != nil {
			t.Fatalf("unexpected error: %s", err)
		}
		v := a.GetOptionalArg(argIdx)
		if v != expectedValue {
			t.Fatalf("unexpected value; got %d; want %d", v, expectedValue)
		}
	}
	f("", 0, 123, 123)
	f("", 1, -34, -34)
	f("10,1", 1, 234, 1)
	f("10,1", 3, 234, 234)
	f("10Kb", 3, -34, 10000)
}

func TestArrayBytes_String(t *testing.T) {
	f := func(s string) {
		t.Helper()
		var a ArrayBytes
		if err := a.Set(s); err != nil {
			t.Fatalf("unexpected error: %s", err)
		}
		result := a.String()
		if result != s {
			t.Fatalf("unexpected string;\ngot\n%s\nwant\n%s", result, s)
		}
	}
	f("10.5KiB,1")
	f("-5,1,123MB")
}