package wand

import (
	"errors"
	"io"
	"testing"
)

func TestApply(t *testing.T) {
	t.Run("input", func(t *testing.T) {
		type s0 struct {
			Foo string
			Bar string
		}

		type s2 struct {
			Foo bool
			Bar bool
		}

		f0 := func(a s0) string { return a.Foo + a.Bar }
		f0a := func(a ...s0) int { return len(a) }
		f0b := func(a *s0) string { return a.Foo + a.Bar }
		f0c := func(a []s0) int { return len(a) }
		f0d := func(a *[]**s0) int { return len(*a) }
		f0io := func(out io.Writer, a s0, in io.Reader) { io.Copy(out, in); out.Write([]byte(a.Foo + a.Bar)) }
		f1 := func(a, b, c int) int { return a + b + c }
		f1a := func(a, b int, c ...int) int { return a + b + len(c) }
		f2 := func(a s2) bool { return a.Foo != a.Bar }
		t.Run("config", testExec(testCase{impl: f0, command: "foo", conf: "foo=bar"}, "", "bar"))
		t.Run("env", testExec(testCase{impl: f0, command: "foo", env: "foo_foo=bar"}, "", "bar"))
		t.Run("options", testExec(testCase{impl: f0, command: "foo --foo bar"}, "", "bar"))
		t.Run(
			"env overrides config",
			testExec(
				testCase{impl: f0, command: "foo", conf: "foo=bar\nbar=baz", env: "foo_foo=qux"},
				"",
				"quxbaz",
			),
		)

		t.Run(
			"options override env and config",
			testExec(
				testCase{impl: f0, command: "foo --bar quux", conf: "foo=bar\nbar=baz", env: "foo_foo=qux"},
				"",
				"quxquux",
			),
		)

		t.Run("variadic structure not set", testExec(testCase{impl: f0a, command: "foo"}, "", "0"))
		t.Run(
			"variadic structure from env",
			testExec(testCase{impl: f0a, command: "foo", env: "foo_foo=bar"}, "", "1"),
		)

		t.Run(
			"variadic structure from options",
			testExec(testCase{impl: f0a, command: "foo --foo bar"}, "", "1"),
		)

		t.Run(
			"variadic with multiple entries not allowed",
			testExec(testCase{impl: f0a, command: "foo --foo bar --foo baz"}, "expected only one value", ""),
		)

		t.Run(
			"pointer",
			testExec(testCase{impl: f0b, command: "foo --foo bar"}, "", "bar"),
		)

		t.Run(
			"list",
			testExec(testCase{impl: f0c, command: "foo --foo bar"}, "", "1"),
		)

		t.Run(
			"list with multiple entries not allowed",
			testExec(testCase{impl: f0c, command: "foo --foo bar --foo baz"}, "expected only one value", ""),
		)

		t.Run(
			"variadic list pointer",
			testExec(testCase{impl: f0d, command: "foo --foo bar"}, "", "1"),
		)

		t.Run(
			"reader and writer set",
			testExec(
				testCase{impl: f0io, stdin: "foobar", command: "foo --foo baz --bar qux"},
				"",
				"foobarbazqux",
			),
		)

		t.Run("positional", testExec(testCase{impl: f1, command: "foo 1 2 3"}, "", "6"))
		t.Run(
			"variadic positional",
			testExec(testCase{impl: f1a, command: "foo 1 2 3 4 5"}, "", "6"),
		)

		t.Run("boolean options", testExec(testCase{impl: f2, command: "foo --foo --bar"}, "", "false"))
		t.Run(
			"short form options",
			testExec(
				testCase{impl: ShortForm(Command("foo", f0), "f", "foo"), command: "foo -f bar"},
				"",
				"bar",
			),
		)
	})

	t.Run("output", func(t *testing.T) {
		f0 := func() {}
		f1 := func() int { return 42 }
		f2 := func() (int, int, int) { return 21, 42, 84 }
		f3 := func() error { return nil }
		f4 := func() error { return errors.New("test error") }
		f5 := func() (int, int, error) { return 42, 84, nil }
		f6 := func() (int, int, error) { return 42, 84, errors.New("test error") }
		t.Run("no output", testExec(testCase{impl: f0, command: "foo"}, "", ""))
		t.Run("non-error output", testExec(testCase{impl: f1, command: "foo"}, "", "42"))
		t.Run("multiple outputs", testExec(testCase{impl: f2, command: "foo"}, "", "21\n42\n84"))
		t.Run("error output no error", testExec(testCase{impl: f3, command: "foo"}, "", ""))
		t.Run("error output error", testExec(testCase{impl: f4, command: "foo"}, "test error", ""))
		t.Run("mixed output no error", testExec(testCase{impl: f5, command: "foo"}, "", "42\n84"))
		t.Run("mixed output error", testExec(testCase{impl: f6, command: "foo"}, "test error", ""))
	})
}
