package wand

import (
	"fmt"
	"strings"
	"testing"
)

func TestCommandLine(t *testing.T) {
	type f struct {
		One         string
		SecondField int
	}
	ff := func(f f) string {
		return f.One + fmt.Sprint(f.SecondField)
	}

	type b struct{ One, Two, Three, Four bool }
	fb := func(b b) string {
		return fmt.Sprintf("%t;%t;%t;%t", b.One, b.Two, b.Three, b.Four)
	}

	fbp := func(b b, p ...string) string {
		o := fb(b)
		if len(p) == 0 {
			return o
		}

		s := []string{o}
		for _, pi := range p {
			s = append(s, pi)
		}

		return strings.Join(s, ";")
	}

	type m struct {
		One, Two bool
		Three    string
	}

	fm := func(m m) string {
		return fmt.Sprintf("%t;%t;%s", m.One, m.Two, m.Three)
	}

	type l struct {
		One []bool
		Two []string
	}
	fl := func(l l) string {
		var sb []string
		for _, b := range l.One {
			sb = append(sb, fmt.Sprint(b))
		}

		return strings.Join([]string{strings.Join(sb, ","), strings.Join(l.Two, ",")}, ";")
	}

	type lb struct{ One, Two, Three []bool }
	flb := func(lb lb) string {
		var s []string
		for _, b := range [][]bool{lb.One, lb.Two, lb.Three} {
			var sb []string
			for _, bi := range b {
				sb = append(sb, fmt.Sprint(bi))
			}

			s = append(s, strings.Join(sb, ","))
		}

		return strings.Join(s, ";")
	}

	fp := func(f f, a ...string) string {
		o := ff(f)
		return fmt.Sprintf("%s;%s", o, strings.Join(a, ","))
	}

	type d struct{ One2 bool }
	fd := func(d d) string {
		return fmt.Sprint(d.One2)
	}

	t.Run("no args", testExec(testCase{impl: ff, command: "foo"}, "", "0"))

	t.Run("basic options", func(t *testing.T) {
		t.Run("space", testExec(testCase{impl: ff, command: "foo --one baz --second-field 42"}, "", "baz42"))
		t.Run("eq", testExec(testCase{impl: ff, command: "foo --one=baz --second-field=42"}, "", "baz42"))
	})

	t.Run("short options combined, explicit last", func(t *testing.T) {
		t.Run(
			"bool last",
			testExec(
				testCase{
					impl:    ShortForm(Command("foo", fb), "a", "one", "b", "two", "c", "three"),
					command: "foo -abc true",
				},
				"",
				"true;true;true;false",
			),
		)

		t.Run(
			"string last",
			testExec(
				testCase{
					impl:    ShortForm(Command("foo", fm), "a", "one", "b", "two", "c", "three"),
					command: "foo -abc bar",
				},
				"",
				"true;true;bar",
			),
		)
	})

	t.Run("multiple values", func(t *testing.T) {
		t.Run(
			"bools, short",
			testExec(
				testCase{
					impl:    ShortForm(Command("foo", fl), "a", "one"),
					command: "foo -a -a -a",
				},
				"",
				"true,true,true;",
			),
		)

		t.Run(
			"bools, short, combined",
			testExec(
				testCase{
					impl:    ShortForm(Command("foo", fl), "a", "one"),
					command: "foo -aaa",
				},
				"",
				"true,true,true;",
			),
		)

		t.Run(
			"bools, short, explicit",
			testExec(
				testCase{
					impl:    ShortForm(Command("foo", fl), "a", "one"),
					command: "foo -a true -a true -a true",
				},
				"",
				"true,true,true;",
			),
		)

		t.Run(
			"bools, short, combined, last explicit",
			testExec(
				testCase{
					impl:    ShortForm(Command("foo", fl), "a", "one"),
					command: "foo -aaa true",
				},
				"",
				"true,true,true;",
			),
		)

		t.Run(
			"bools, long",
			testExec(
				testCase{
					impl:    fl,
					command: "foo --one --one --one",
				},
				"",
				"true,true,true;",
			),
		)

		t.Run(
			"bools, long, explicit",
			testExec(
				testCase{
					impl:    fl,
					command: "foo --one true --one true --one true",
				},
				"",
				"true,true,true;"),
		)

		t.Run(
			"mixd, short",
			testExec(
				testCase{
					impl:    ShortForm(Command("foo", fl), "a", "one", "b", "two"),
					command: "foo -a -b bar",
				},
				"",
				"true;bar",
			),
		)

		t.Run(
			"mixed, short, combined",
			testExec(
				testCase{
					impl:    ShortForm(Command("foo", fl), "a", "one", "b", "two"),
					command: "foo -ab bar",
				},
				"",
				"true;bar",
			),
		)

		t.Run(
			"mixed, long",
			testExec(
				testCase{
					impl:    fl,
					command: "foo --one --two bar",
				},
				"",
				"true;bar",
			),
		)

		t.Run(
			"mixed, long, explicit",
			testExec(
				testCase{
					impl:    fl,
					command: "foo --one true --two bar",
				},
				"",
				"true;bar",
			),
		)
	})

	t.Run("implicit bool option", func(t *testing.T) {
		t.Run(
			"short",
			testExec(
				testCase{
					impl:    ShortForm(Command("foo", fb), "a", "one"),
					command: "foo -a",
				},
				"",
				"true;false;false;false",
			),
		)

		t.Run(
			"short, multiple",
			testExec(
				testCase{
					impl:    ShortForm(Command("foo", fb), "a", "one", "b", "two", "c", "three"),
					command: "foo -a -b -c",
				},
				"",
				"true;true;true;false",
			),
		)

		t.Run(
			"short, combined",
			testExec(
				testCase{
					impl:    ShortForm(Command("foo", fb), "a", "one", "b", "two", "c", "three"),
					command: "foo -abc",
				},
				"",
				"true;true;true;false",
			),
		)

		t.Run(
			"short, combined, multiple",
			testExec(
				testCase{
					impl:    ShortForm(Command("foo", fb), "a", "one", "b", "two", "c", "three", "d", "four"),
					command: "foo -ab -cd",
				},
				"",
				"true;true;true;true",
			),
		)

		t.Run(
			"short, multiple values",
			testExec(
				testCase{
					impl:    ShortForm(Command("foo", flb), "a", "one", "b", "two", "c", "three"),
					command: "foo -aba -cab",
				},
				"",
				"true,true,true;true,true;true",
			),
		)

		t.Run(
			"long",
			testExec(
				testCase{
					impl:    fb,
					command: "foo --one",
				},
				"",
				"true;false;false;false",
			),
		)

		t.Run(
			"long, multiple",
			testExec(
				testCase{
					impl:    fb,
					command: "foo --one --two --three",
				},
				"",
				"true;true;true;false",
			),
		)

		t.Run(
			"long, multiple values",
			testExec(
				testCase{
					impl:    flb,
					command: "foo --one --two --one",
				},
				"",
				"true,true;true;",
			),
		)
	})

	t.Run("explicit bool option", func(t *testing.T) {
		t.Run(
			"short, true",
			testExec(
				testCase{
					impl:    ShortForm(Command("foo", fb), "a", "one"),
					command: "foo -a true",
				},
				"",
				"true;false;false;false",
			),
		)

		t.Run(
			"short, false",
			testExec(
				testCase{
					impl:    ShortForm(Command("foo", fb), "a", "one"),
					command: "foo -a false",
				},
				"",
				"false;false;false;false",
			),
		)

		t.Run(
			"short, with eq",
			testExec(
				testCase{
					impl:    ShortForm(Command("foo", fb), "a", "one"),
					command: "foo -a=true",
				},
				"",
				"true;false;false;false",
			),
		)

		t.Run(
			"short, true variant, capital",
			testExec(
				testCase{
					impl:    ShortForm(Command("foo", fb), "a", "one"),
					command: "foo -a True",
				},
				"",
				"true;false;false;false",
			),
		)

		t.Run(
			"short, true variant, 1",
			testExec(
				testCase{
					impl:    ShortForm(Command("foo", fb), "a", "one"),
					command: "foo -a 1",
				},
				"",
				"true;false;false;false",
			),
		)

		t.Run(
			"short, false variant, 0",
			testExec(
				testCase{
					impl:    ShortForm(Command("foo", fb), "a", "one"),
					command: "foo -a 0",
				},
				"",
				"false;false;false;false",
			),
		)

		t.Run(
			"short, combined",
			testExec(
				testCase{
					impl:    ShortForm(Command("foo", fb), "a", "one", "b", "two"),
					command: "foo -ab true",
				},
				"",
				"true;true;false;false",
			),
		)

		t.Run(
			"short, combined, multiple",
			testExec(
				testCase{
					impl:    ShortForm(Command("foo", fb), "a", "one", "b", "two", "c", "three", "d", "four"),
					command: "foo -ab true -cd true",
				},
				"",
				"true;true;true;true",
			),
		)

		t.Run(
			"long",
			testExec(
				testCase{
					impl:    fb,
					command: "foo --one true",
				},
				"",
				"true;false;false;false",
			),
		)

		t.Run(
			"long, false",
			testExec(
				testCase{
					impl:    fb,
					command: "foo --one false",
				},
				"",
				"false;false;false;false",
			),
		)

		t.Run(
			"logn, with eq",
			testExec(
				testCase{
					impl:    fb,
					command: "foo --one=true",
				},
				"",
				"true;false;false;false"),
		)

		t.Run(
			"long, mixed, first",
			testExec(
				testCase{
					impl:    fb,
					command: "foo --one false --two",
				},
				"",
				"false;true;false;false",
			),
		)

		t.Run(
			"long, mixed, last",
			testExec(
				testCase{
					impl:    fb,
					command: "foo --one --two false",
				},
				"",
				"true;false;false;false",
			),
		)
	})

	t.Run("expected bool option", func(t *testing.T) {
		t.Run(
			"short, implicit",
			testExec(
				testCase{
					impl:    ShortForm(Command("foo", fb), "a", "one"),
					command: "foo -a",
				},
				"",
				"true;false;false;false"),
		)

		t.Run(
			"short, explicit",
			testExec(
				testCase{
					impl:    ShortForm(Command("foo", fb), "a", "one"),
					command: "foo -a true",
				},
				"",
				"true;false;false;false",
			),
		)

		t.Run(
			"short, automatic positional",
			testExec(
				testCase{
					impl:    ShortForm(Command("foo", fbp), "a", "one"),
					command: "foo -a bar",
				},
				"",
				"true;false;false;false;bar",
			),
		)

		t.Run(
			"short, combined",
			testExec(
				testCase{
					impl:    ShortForm(Command("foo", fb), "a", "one", "b", "two"),
					command: "foo -ab true",
				},
				"",
				"true;true;false;false",
			),
		)

		t.Run(
			"short, combined, automatic positional",
			testExec(
				testCase{
					impl:    ShortForm(Command("foo", fbp), "a", "one", "b", "two"),
					command: "foo -ab bar",
				},
				"",
				"true;true;false;false;bar"),
		)

		t.Run(
			"long, implicit",
			testExec(
				testCase{
					impl:    fb,
					command: "foo --one",
				},
				"",
				"true;false;false;false",
			),
		)

		t.Run(
			"long, explicit",
			testExec(
				testCase{
					impl:    fb,
					command: "foo --one true",
				},
				"",
				"true;false;false;false",
			),
		)

		t.Run(
			"long, automatic positional",
			testExec(
				testCase{
					impl:    fbp,
					command: "foo --one bar",
				},
				"",
				"true;false;false;false;bar",
			),
		)
	})

	t.Run("positional", func(t *testing.T) {
		t.Run(
			"basic",
			testExec(
				testCase{
					impl:    fp,
					command: "foo bar baz",
				},
				"",
				"0;bar,baz",
			),
		)

		t.Run(
			"explicit",
			testExec(
				testCase{
					impl:    fp,
					command: "foo -- bar baz",
				},
				"",
				"0;bar,baz",
			),
		)

		t.Run(
			"mixed",
			testExec(
				testCase{
					impl:    fp,
					command: "foo bar -- baz",
				},
				"",
				"0;bar,baz",
			),
		)

		t.Run(
			"with option",
			testExec(
				testCase{
					impl:    fp,
					command: "foo bar --second-field 42 baz",
				},
				"",
				"42;bar,baz",
			),
		)

		t.Run(
			"with bool option at the end",
			testExec(
				testCase{
					impl:    fbp,
					command: "foo bar baz --one",
				},
				"",
				"true;false;false;false;bar;baz",
			),
		)

		t.Run(
			"with expected bool, implicit",
			testExec(
				testCase{
					impl:    fbp,
					command: "foo bar --one baz",
				},
				"",
				"true;false;false;false;bar;baz",
			),
		)

		t.Run(
			"with expected bool, explicit",
			testExec(
				testCase{
					impl:    fbp,
					command: "foo bar --one true baz",
				},
				"",
				"true;false;false;false;bar;baz",
			),
		)

		t.Run(
			"option format",
			testExec(
				testCase{
					impl:    fbp,
					command: "foo -- --one",
				},
				"",
				"false;false;false;false;--one",
			),
		)
	})

	t.Run("example", func(t *testing.T) {
		type s struct {
			Foo  bool
			Bar  []bool
			Qux  bool
			Quux string
		}

		fs := func(s s, a1, a2 string) string {
			var sbar []string
			for _, b := range s.Bar {
				sbar = append(sbar, fmt.Sprint(b))
			}

			return fmt.Sprintf("%t;%s;%t;%s;%s;%s", s.Foo, strings.Join(sbar, ","), s.Qux, s.Quux, a1, a2)
		}

		t.Run(
			"full",
			testExec(
				testCase{
					impl:    ShortForm(Command("foo", fs), "a", "foo", "b", "bar"),
					command: "foo -ab --bar baz -b --qux --quux corge -- grault",
				},
				"",
				"true;true,true,true;true;corge;baz;grault",
			),
		)
	})

	t.Run("expected or unexpected", func(t *testing.T) {
		t.Run(
			"capital letters",
			testExec(
				testCase{
					impl:    fp,
					command: "foo --One bar",
				},
				"",
				"0;--One,bar",
			),
		)

		t.Run(
			"digit in option name",
			testExec(
				testCase{
					impl:    fd,
					command: "foo --one-2",
				},
				"",
				"true",
			),
		)

		t.Run(
			"dash in option name",
			testExec(
				testCase{
					impl:    ff,
					command: "foo --second-field 42",
				},
				"",
				"42",
			),
		)

		t.Run(
			"unpexpected character",
			testExec(
				testCase{
					impl:    fp,
					command: "foo --one#",
				},
				"",
				"0;--one#",
			),
		)

		t.Run(
			"invalid short option set",
			testExec(
				testCase{
					impl:    ShortForm(Command("foo", fp), "a", "one", "b", "one", "c", "second-field"),
					command: "foo -aBc",
				},
				"",
				"0;-aBc",
			),
		)

		t.Run(
			"positional separator, no value",
			testExec(
				testCase{
					impl:    fp,
					command: "foo --one bar --",
				},
				"",
				"bar0;",
			),
		)

		t.Run(
			"positional separator, expecting value",
			testExec(
				testCase{
					impl:    fp,
					command: "foo --one --",
				},
				"--one",
				"",
			),
		)

		t.Run(
			"shot flag set, expecting value",
			testExec(
				testCase{
					impl:    ShortForm(Command("foo", fp), "b", "second-field"),
					command: "foo --one -b",
				},
				"--one",
				"",
			),
		)
	})

	t.Run("preserve order", func(t *testing.T) {
		t.Run(
			"bools",
			testExec(
				testCase{
					impl:    fl,
					command: "foo --one --one false --one",
				},
				"",
				"true,false,true;",
			),
		)

		t.Run(
			"strings",
			testExec(
				testCase{
					impl:    fl,
					command: "foo --two 1 --two 2 --two 3",
				},
				"",
				";1,2,3",
			),
		)
	})

	t.Run("select subcommand", func(t *testing.T) {
		t.Run(
			"named",
			testExec(
				testCase{
					impl:    Group("foo", Command("bar", ff), Command("baz", ff)),
					command: "foo baz",
				},
				"",
				"0",
			),
		)

		t.Run(
			"default",
			testExec(
				testCase{
					impl:    Group("foo", Command("bar", ff), Default(Command("baz", ff))),
					command: "foo",
				},
				"",
				"0",
			),
		)
	})

	t.Run("help option", func(t *testing.T) {
		t.Run(
			"short form not defined",
			testExec(
				testCase{
					impl:    fm,
					command: "foo -h",
				},
				"foo help",
				"",
			),
		)

		t.Run(
			"short form not help",
			testExec(
				testCase{
					impl:    ShortForm(Command("foo", fm), "h", "one"),
					command: "foo -h",
				},
				"",
				"true;false;",
			),
		)

		t.Run(
			"short form",
			testExec(
				testCase{
					impl:     ShortForm(Command("foo", fm), "h", "help"),
					command:  "foo -h",
					contains: true,
				},
				"",
				"Synopsis",
			),
		)

		t.Run(
			"long form",
			testExec(
				testCase{
					impl:     fm,
					command:  "foo --help",
					contains: true,
				},
				"",
				"Synopsis",
			),
		)
	})
}
