package wand

import (
	"fmt"
	"io"
	"testing"
)

func TestCommand(t *testing.T) {
	t.Run("fields", func(t *testing.T) {
		type s0 struct {
			FooBar int
			Foo    struct{ Bar bool }
		}

		type s1 struct {
			Config string
		}

		f0 := func(a s0) int { return a.FooBar }
		t.Run(
			"incompatible field types",
			testExec(testCase{impl: f0, command: "foo"}, "duplicate fields with different types", ""),
		)

		f1 := func(a s1) string { return a.Config }
		t.Run(
			"config option shadowed",
			testExec(
				testCase{impl: f1, mergeConfTyped: []Config{ConfigFromOption()}, command: "foo"},
				"option reserved for config",
				"",
			),
		)
	})

	t.Run("positional", func(t *testing.T) {
		f := func(a, b, c int) int { return a + b + c }
		t.Run("ok", testExec(testCase{impl: Args(Command("foo", f), 3, 3), command: "foo 1 2 3"}, "", "6"))
		t.Run(
			"min less than fixed",
			testExec(
				testCase{impl: Args(Command("foo", f), 2, 3), command: "foo 1 2 3"},
				"minimum positional",
				"",
			),
		)

		t.Run(
			"min more than fixed",
			testExec(
				testCase{impl: Args(Command("foo", f), 4, 4), command: "foo 1 2 3"},
				"minimum positional",
				"",
			),
		)

		t.Run(
			"max less than fixed",
			testExec(
				testCase{impl: Args(Command("foo", f), 0, 2), command: "foo 1 2 3"},
				"maximum positional",
				"",
			),
		)

		fv := func(a, b int, c ...int) int { return a + b + len(c) }
		t.Run("ok variadic", testExec(testCase{impl: Args(Command("foo", fv), 3, 5), command: "foo 1 2 3 4 5"}, "", "6"))

		t.Run(
			"min less than fixed variadic",
			testExec(
				testCase{impl: Args(Command("foo", fv), 1, 5), command: "foo 1 2 3"},
				"minimum positional",
				"",
			),
		)

		t.Run(
			"max less than fixed variadic",
			testExec(
				testCase{impl: Args(Command("foo", fv), 0, 1), command: "foo 1 2 3"},
				"maximum positional",
				"",
			),
		)

		t.Run(
			"min more than max variadic",
			testExec(
				testCase{impl: Args(Command("foo", fv), 4, 3), command: "foo 1 2 3 4 5"},
				"minimum positional",
				"",
			),
		)

		fvio := func(a int, in io.Reader, out io.Writer, b int, c ...int) int { return a + b + len(c) }
		t.Run("ok io", testExec(testCase{impl: Args(Command("foo", fvio), 3, 5), command: "foo 1 2 3 4 5"}, "", "6"))

		t.Run(
			"min less than fixed io",
			testExec(
				testCase{impl: Args(Command("foo", fvio), 1, 5), command: "foo 1 2 3"},
				"minimum positional",
				"",
			),
		)

		t.Run(
			"max less than fixed io",
			testExec(
				testCase{impl: Args(Command("foo", fvio), 0, 1), command: "foo 1 2 3"},
				"maximum positional",
				"",
			),
		)

		t.Run(
			"min more than max io",
			testExec(
				testCase{impl: Args(Command("foo", fvio), 4, 3), command: "foo 1 2 3 4 5"},
				"minimum positional",
				"",
			),
		)

		fio := func(a, b io.Reader) {}
		t.Run(
			"max one io of a kind",
			testExec(testCase{impl: fio, command: "foo"}, "only zero or one reader", ""),
		)
	})

	t.Run("impl", func(t *testing.T) {
		t.Run(
			"must be func",
			testExec(
				testCase{impl: []func(){func() {}}, command: "foo"},
				"implementation must be a function",
				"",
			),
		)

		t.Run(
			"paramters must be bindable",
			testExec(testCase{impl: func(chan int) {}, command: "foo"}, "unsupported parameter type", ""),
		)
	})

	t.Run("command tree", func(t *testing.T) {
		f := func(a, b int) int { return a + b }
		cmd := Command("foo", f)
		cmd = Version(cmd, "v42")
		t.Run("version pass", testExec(testCase{impl: cmd, command: "foo 1 2"}, "", "3"))
		cmd = Command("foo", nil)
		t.Run(
			"no impl and not group",
			testExec(testCase{impl: cmd, command: "foo"}, "command does not have an implementation", ""),
		)

		cmd = Group("foo")
		t.Run(
			"no impl and no subcommands",
			testExec(testCase{impl: cmd, command: "foo"}, "empty command category", ""),
		)

		cmd = Group("foo", Command("bar-baz", func() {}), Command("qux quux", func() {}))
		t.Run(
			"invalid subcommand name",
			testExec(testCase{impl: cmd, command: "foo bar-baz"}, "command name is not a valid", ""),
		)

		cmd = Group("foo", Command("bar", func() {}), Command("bar", func() {}))
		t.Run(
			"duplicate subcommand name",
			testExec(testCase{impl: cmd, command: "foo bar"}, "subcommand name conflict", ""),
		)

		cmd = Command("foo", func() {}, Default(Command("bar", func() {})), Command("bar", func() {}))
		t.Run(
			"implementation conflict",
			testExec(
				testCase{impl: cmd, command: "foo bar"},
				"default subcommand defined for a command that has an explicit implementation",
				"",
			),
		)

		cmd = Group("foo", Default(Command("bar", func() {})), Default(Command("baz", func() {})))
		t.Run(
			"multiple defaults",
			testExec(
				testCase{impl: cmd, command: "foo bar"},
				"multiple default subcommands",
				"",
			),
		)

		cmd = Command("foo", func() {}, Command("bar", func() {}), Command("baz", func(chan int) {}))
		t.Run(
			"invalid subcommand",
			testExec(
				testCase{impl: cmd, command: "foo bar"},
				"foo:",
				"",
			),
		)
	})

	t.Run("short forms", func(t *testing.T) {
		type (
			s0 struct {
				Foo int
				Qux int
			}
			s1 struct{ Bar int }
			s2 struct{ Baz int }
		)

		f0 := func(a s0) int { return a.Foo }
		f1 := func(a s1) int { return a.Bar }
		f2 := func(a s2) int { return a.Baz }
		cmd := ShortForm(Command("foo", f0, ShortForm(Command("bar", f1), "a", "bar")), "a", "foo")
		t.Run(
			"same short form for different options",
			testExec(testCase{impl: cmd, command: "foo"}, "same short form for different options", ""),
		)

		cmd = ShortForm(Command("foo", f0), "a", "foo", "a", "qux")
		t.Run(
			"same short form for different options on the same command",
			testExec(testCase{impl: cmd, command: "foo"}, "same short form for different options", ""),
		)

		cmd = ShortForm(Command("foo", f0, Command("bar", f1)), "a", "foo", "a", "qux")
		t.Run(
			"same short form for different options on the same command with subcommand",
			testExec(testCase{impl: cmd, command: "foo"}, "same short form for different options", ""),
		)

		cmd = Command(
			"foo",
			f0,
			ShortForm(Command("bar", f1), "a", "bar"),
			ShortForm(Command("baz", f2), "a", "baz"),
		)

		t.Run(
			"same short form for different options on different branches in the tree",
			testExec(testCase{impl: cmd, command: "foo"}, "same short form for different options", ""),
		)

		cmd = ShortForm(Command("foo", f0, ShortForm(Command("bar", f1), "b", "baz")), "a", "foo")
		t.Run(
			"unmapped short form",
			testExec(testCase{impl: cmd, command: "foo"}, "unmapped short forms", ""),
		)

		cmd = ShortForm(
			Command(
				"foo",
				f0,
				ShortForm(Command("bar", f1), "b", "baz"),
				ShortForm(Command("qux", f1), "b", "qux"),
			),
			"a",
			"foo",
		)

		t.Run(
			"conflicting unmapped",
			testExec(
				testCase{impl: cmd, command: "foo"},
				"using the same short form for different options",
				"",
			),
		)

		cmd = ShortForm(Command("foo", f0), "ab", "foo")
		t.Run(
			"short form not a single character",
			testExec(testCase{impl: cmd, command: "foo"}, "invalid short form", ""),
		)

		cmd = ShortForm(Command("foo", f0), "6", "foo")
		t.Run(
			"short form not a single character",
			testExec(testCase{impl: cmd, command: "foo"}, "invalid short form", ""),
		)

		cmd = ShortForm(Group("foo", Command("bar", f1)), "b", "bar")
		t.Run(
			"short form with command category",
			testExec(testCase{impl: cmd, command: "foo bar"}, "", "0"),
		)
	})

	t.Run("bool options", func(t *testing.T) {
		type s0 struct {
			Foo int
			Bar bool
			Baz bool
		}
		type s1 struct {
			Foo  int
			Bar  bool
			Baz  bool
			Help bool
		}
		f0 := func(a s0, b, c string) string {
			return fmt.Sprint(a.Foo) + fmt.Sprint(a.Bar) + fmt.Sprint(a.Baz) + b + c
		}

		f1 := func(a s1, b, c string) string {
			return fmt.Sprint(a.Foo) + fmt.Sprint(a.Bar) + fmt.Sprint(a.Baz) + fmt.Sprint(a.Help) + b + c
		}

		cmd := ShortForm(Command("foo", f0), "b", "bar")
		t.Run(
			"no help in fields and no help in short forms",
			testExec(testCase{impl: cmd, command: "foo --foo 42 -b qux --baz quux"}, "", "42truetruequxquux"),
		)

		t.Run(
			"no help in fields and no help in short forms help",
			testExec(testCase{impl: cmd, command: "foo --help", contains: true}, "", "Synopsis"),
		)

		cmd = ShortForm(Command("foo", f0), "h", "help")
		t.Run(
			"help from short forms",
			testExec(testCase{impl: cmd, command: "foo -h", contains: true}, "", "Synopsis"),
		)

		cmd = ShortForm(Command("foo", f1), "b", "bar")
		t.Run(
			"help taken by a field",
			testExec(testCase{impl: cmd, command: "foo --help qux quux"}, "", "0falsefalsetruequxquux"),
		)

		cmd = ShortForm(Command("foo", f1), "h", "bar")
		t.Run(
			"help taken by a field and help short form used for another field",
			testExec(testCase{impl: cmd, command: "foo --help qux -h quux"}, "", "0truefalsetruequxquux"),
		)
	})
}
