package cliplugins

import (
	"fmt"
	"os"
	"testing"

	"gotest.tools/v3/icmd"
)

// TestRunGoodArgument ensures correct behaviour when running a valid plugin with an `--argument`.
func TestRunGoodArgument(t *testing.T) {
	run, _, cleanup := prepare(t)
	defer cleanup()

	res := icmd.RunCmd(run("helloworld", "--who", "Cleveland"))
	res.Assert(t, icmd.Expected{
		ExitCode: 0,
		Out:      "Hello Cleveland",
	})
}

// TestClashWithGlobalArgs ensures correct behaviour when a plugin
// has an argument with the same name as one of the globals.
func TestClashWithGlobalArgs(t *testing.T) {
	run, _, cleanup := prepare(t)
	defer cleanup()

	for _, tc := range []struct {
		name                     string
		args                     []string
		expectedOut, expectedErr string
	}{
		{
			name:        "short-without-val",
			args:        []string{"-D"},
			expectedOut: "Hello World",
			expectedErr: "Plugin debug mode enabled",
		},
		{
			name:        "long-without-val",
			args:        []string{"--debug"},
			expectedOut: "Hello World",
			expectedErr: "Plugin debug mode enabled",
		},
		{
			name:        "short-with-val",
			args:        []string{"-c", "Christmas"},
			expectedOut: "Merry Christmas",
			expectedErr: icmd.None,
		},
		{
			name:        "short-with-val",
			args:        []string{"--context", "Christmas"},
			expectedOut: "Merry Christmas",
			expectedErr: icmd.None,
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			args := append([]string{"helloworld"}, tc.args...)
			res := icmd.RunCmd(run(args...))
			res.Assert(t, icmd.Expected{
				ExitCode: 0,
				Out:      tc.expectedOut,
				Err:      tc.expectedErr,
			})
		})
	}
}

// TestGlobalArgsOnlyParsedOnce checks that global args are only parsed
// once (cf https://github.com/docker/cli/issues/1801). These tests
// rely on `-H` being a list type (i.e. NewNamedListOptsRef) which
// reject multiple uses dynamically (see `getServerHost()` in
// github.com/docker/cli/cli/command/cli.go) in order to detect this
// scenario.
func TestGlobalArgsOnlyParsedOnce(t *testing.T) {
	run, _, cleanup := prepare(t)
	defer cleanup()

	// We can rely on `$DOCKER_HOST` being set due to the call to
	// `environment.Setup` in our `TestMain`.
	dh := os.Getenv("DOCKER_HOST")

	for _, tc := range []struct {
		name                     string
		args                     []string
		expectedExitCode         int
		expectedOut, expectedErr string
	}{
		{
			// This is checking the precondition wrt -H mentioned in the function comment
			name:             "fails-if-H-used-twice",
			args:             []string{"-H", dh, "-H", dh, "version", "-f", "{{.Client.Version}}"},
			expectedExitCode: 125,
			expectedOut:      icmd.None,
			expectedErr:      fmt.Sprintf(`invalid argument %q for "-H, --host" flag: specify only one -H`, dh),
		},
		{
			name:             "builtin",
			args:             []string{"-H", dh, "version", "-f", "{{.Client.Version}}"},
			expectedExitCode: 0,
			expectedOut:      "", // Will be the client version, but the specifics aren't important so long as stderr is empty.
			expectedErr:      icmd.None,
		},
		{
			name:             "plugin",
			args:             []string{"-H", dh, "helloworld", "apiversion"},
			expectedExitCode: 0,
			expectedOut:      "", // Will be the client version, but the specifics aren't important so long as stderr is empty.
			expectedErr:      icmd.None,
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			res := icmd.RunCmd(run(tc.args...))
			res.Assert(t, icmd.Expected{
				ExitCode: tc.expectedExitCode,
				Out:      tc.expectedOut,
				Err:      tc.expectedErr,
			})
		})
	}
}

// TestUnknownGlobal checks that unknown globals report errors
func TestUnknownGlobal(t *testing.T) {
	run, _, cleanup := prepare(t)
	defer cleanup()

	for name, args := range map[string][]string{
		"no-val":       {"--unknown", "helloworld"},
		"separate-val": {"--unknown", "foo", "helloworld"},
		"joined-val":   {"--unknown=foo", "helloworld"},
	} {
		t.Run(name, func(t *testing.T) {
			res := icmd.RunCmd(run(args...))
			res.Assert(t, icmd.Expected{
				ExitCode: 125,
				Out:      icmd.None,
				Err:      "unknown flag: --unknown",
			})
		})
	}
}

// TestCliPluginsVersion checks that `-v` and friends DTRT
func TestCliPluginsVersion(t *testing.T) {
	run, _, cleanup := prepare(t)
	defer cleanup()

	for _, tc := range []struct {
		name           string
		args           []string
		expCode        int
		expOut, expErr string
	}{
		{
			name:    "global-version",
			args:    []string{"version"},
			expCode: 0,
			expOut:  "Client:\n Version:",
			expErr:  icmd.None,
		},
		{
			name:    "global-version-flag",
			args:    []string{"--version"},
			expCode: 0,
			expOut:  "Docker version",
			expErr:  icmd.None,
		},
		{
			name:    "global-short-version-flag",
			args:    []string{"-v"},
			expCode: 0,
			expOut:  "Docker version",
			expErr:  icmd.None,
		},
		{
			name:    "global-with-unknown-arg",
			args:    []string{"version", "foo"},
			expCode: 1,
			expOut:  icmd.None,
			expErr:  `docker: 'docker version' accepts no arguments`,
		},
		{
			name:    "global-with-plugin-arg",
			args:    []string{"version", "helloworld"},
			expCode: 1,
			expOut:  icmd.None,
			expErr:  `docker: 'docker version' accepts no arguments`,
		},
		{
			name:    "global-version-flag-with-unknown-arg",
			args:    []string{"--version", "foo"},
			expCode: 0,
			expOut:  "Docker version",
			expErr:  icmd.None,
		},
		{
			name:    "global-short-version-flag-with-unknown-arg",
			args:    []string{"-v", "foo"},
			expCode: 0,
			expOut:  "Docker version",
			expErr:  icmd.None,
		},
		{
			name:    "global-version-flag-with-plugin",
			args:    []string{"--version", "helloworld"},
			expCode: 125,
			expOut:  icmd.None,
			expErr:  "unknown flag: --version",
		},
		{
			name:    "global-short-version-flag-with-plugin",
			args:    []string{"-v", "helloworld"},
			expCode: 125,
			expOut:  icmd.None,
			expErr:  "unknown shorthand flag: 'v' in -v",
		},
		{
			name:    "plugin-with-version",
			args:    []string{"helloworld", "version"},
			expCode: 0,
			expOut:  "Hello World",
			expErr:  icmd.None,
		},
		{
			name:    "plugin-with-version-flag",
			args:    []string{"helloworld", "--version"},
			expCode: 125,
			expOut:  icmd.None,
			expErr:  "unknown flag: --version",
		},
		{
			name:    "plugin-with-short-version-flag",
			args:    []string{"helloworld", "-v"},
			expCode: 125,
			expOut:  icmd.None,
			expErr:  "unknown shorthand flag: 'v' in -v",
		},
		{
			name:    "",
			args:    []string{},
			expCode: 0,
			expOut:  "",
			expErr:  "",
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			res := icmd.RunCmd(run(tc.args...))
			res.Assert(t, icmd.Expected{
				ExitCode: tc.expCode,
				Out:      tc.expOut,
				Err:      tc.expErr,
			})
		})
	}
}
