package cobra

import (
	"bytes"
	"fmt"
	"os"
	"reflect"
	"strings"
	"testing"

	"github.com/spf13/pflag"
)

// test to ensure hidden commands run as intended
func TestHiddenCommandExecutes(t *testing.T) {

	// ensure that outs does not already equal what the command will be setting it
	// to, if it did this test would not actually be testing anything...
	if outs == "hidden" {
		t.Errorf("outs should NOT EQUAL hidden")
	}

	cmdHidden.Execute()

	// upon running the command, the value of outs should now be 'hidden'
	if outs != "hidden" {
		t.Errorf("Hidden command failed to run!")
	}
}

// test to ensure hidden commands do not show up in usage/help text
func TestHiddenCommandIsHidden(t *testing.T) {
	if cmdHidden.IsAvailableCommand() {
		t.Errorf("Hidden command found!")
	}
}

func TestStripFlags(t *testing.T) {
	tests := []struct {
		input  []string
		output []string
	}{
		{
			[]string{"foo", "bar"},
			[]string{"foo", "bar"},
		},
		{
			[]string{"foo", "--bar", "-b"},
			[]string{"foo"},
		},
		{
			[]string{"-b", "foo", "--bar", "bar"},
			[]string{},
		},
		{
			[]string{"-i10", "echo"},
			[]string{"echo"},
		},
		{
			[]string{"-i=10", "echo"},
			[]string{"echo"},
		},
		{
			[]string{"--int=100", "echo"},
			[]string{"echo"},
		},
		{
			[]string{"-ib", "echo", "-bfoo", "baz"},
			[]string{"echo", "baz"},
		},
		{
			[]string{"-i=baz", "bar", "-i", "foo", "blah"},
			[]string{"bar", "blah"},
		},
		{
			[]string{"--int=baz", "-bbar", "-i", "foo", "blah"},
			[]string{"blah"},
		},
		{
			[]string{"--cat", "bar", "-i", "foo", "blah"},
			[]string{"bar", "blah"},
		},
		{
			[]string{"-c", "bar", "-i", "foo", "blah"},
			[]string{"bar", "blah"},
		},
		{
			[]string{"--persist", "bar"},
			[]string{"bar"},
		},
		{
			[]string{"-p", "bar"},
			[]string{"bar"},
		},
	}

	cmdPrint := &Command{
		Use:   "print [string to print]",
		Short: "Print anything to the screen",
		Long:  `an utterly useless command for testing.`,
		Run: func(cmd *Command, args []string) {
			tp = args
		},
	}

	var flagi int
	var flagstr string
	var flagbool bool
	cmdPrint.PersistentFlags().BoolVarP(&flagbool, "persist", "p", false, "help for persistent one")
	cmdPrint.Flags().IntVarP(&flagi, "int", "i", 345, "help message for flag int")
	cmdPrint.Flags().StringVarP(&flagstr, "bar", "b", "bar", "help message for flag string")
	cmdPrint.Flags().BoolVarP(&flagbool, "cat", "c", false, "help message for flag bool")

	for _, test := range tests {
		output := stripFlags(test.input, cmdPrint)
		if !reflect.DeepEqual(test.output, output) {
			t.Errorf("expected: %v, got: %v", test.output, output)
		}
	}
}

func TestDisableFlagParsing(t *testing.T) {
	targs := []string{}
	cmdPrint := &Command{
		DisableFlagParsing: true,
		Run: func(cmd *Command, args []string) {
			targs = args
		},
	}
	args := []string{"cmd", "-v", "-race", "-file", "foo.go"}
	cmdPrint.SetArgs(args)
	err := cmdPrint.Execute()
	if err != nil {
		t.Error(err)
	}
	if !reflect.DeepEqual(args, targs) {
		t.Errorf("expected: %v, got: %v", args, targs)
	}
}

func TestInitHelpFlagMergesFlags(t *testing.T) {
	usage := "custom flag"
	baseCmd := Command{Use: "testcmd"}
	baseCmd.PersistentFlags().Bool("help", false, usage)
	cmd := Command{Use: "do"}
	baseCmd.AddCommand(&cmd)

	cmd.InitDefaultHelpFlag()
	actual := cmd.Flags().Lookup("help").Usage
	if actual != usage {
		t.Fatalf("Expected the help flag from the base command with usage '%s', but got the default with usage '%s'", usage, actual)
	}
}

func TestCommandsAreSorted(t *testing.T) {
	EnableCommandSorting = true

	originalNames := []string{"middle", "zlast", "afirst"}
	expectedNames := []string{"afirst", "middle", "zlast"}

	var tmpCommand = &Command{Use: "tmp"}

	for _, name := range originalNames {
		tmpCommand.AddCommand(&Command{Use: name})
	}

	for i, c := range tmpCommand.Commands() {
		if expectedNames[i] != c.Name() {
			t.Errorf("expected: %s, got: %s", expectedNames[i], c.Name())
		}
	}

	EnableCommandSorting = true
}

func TestEnableCommandSortingIsDisabled(t *testing.T) {
	EnableCommandSorting = false

	originalNames := []string{"middle", "zlast", "afirst"}

	var tmpCommand = &Command{Use: "tmp"}

	for _, name := range originalNames {
		tmpCommand.AddCommand(&Command{Use: name})
	}

	for i, c := range tmpCommand.Commands() {
		if originalNames[i] != c.Name() {
			t.Errorf("expected: %s, got: %s", originalNames[i], c.Name())
		}
	}

	EnableCommandSorting = true
}

func TestSetOutput(t *testing.T) {
	cmd := &Command{}
	cmd.SetOutput(nil)
	if out := cmd.OutOrStdout(); out != os.Stdout {
		t.Fatalf("expected setting output to nil to revert back to stdout, got %v", out)
	}
}

func TestFlagErrorFunc(t *testing.T) {
	cmd := &Command{
		Use: "print",
		RunE: func(cmd *Command, args []string) error {
			return nil
		},
	}
	expectedFmt := "This is expected: %s"

	cmd.SetFlagErrorFunc(func(c *Command, err error) error {
		return fmt.Errorf(expectedFmt, err)
	})
	cmd.SetArgs([]string{"--bogus-flag"})
	cmd.SetOutput(new(bytes.Buffer))

	err := cmd.Execute()

	expected := fmt.Sprintf(expectedFmt, "unknown flag: --bogus-flag")
	if err.Error() != expected {
		t.Errorf("expected %v, got %v", expected, err.Error())
	}
}

// TestSortedFlags checks,
// if cmd.LocalFlags() is unsorted when cmd.Flags().SortFlags set to false.
// Related to https://github.com/spf13/cobra/issues/404.
func TestSortedFlags(t *testing.T) {
	cmd := &Command{}
	cmd.Flags().SortFlags = false
	names := []string{"C", "B", "A", "D"}
	for _, name := range names {
		cmd.Flags().Bool(name, false, "")
	}

	i := 0
	cmd.LocalFlags().VisitAll(func(f *pflag.Flag) {
		if i == len(names) {
			return
		}
		if isStringInStringSlice(f.Name, names) {
			if names[i] != f.Name {
				t.Errorf("Incorrect order. Expected %v, got %v", names[i], f.Name)
			}
			i++
		}
	})
}

// contains checks, if s is in ss.
func isStringInStringSlice(s string, ss []string) bool {
	for _, v := range ss {
		if v == s {
			return true
		}
	}
	return false
}

// TestHelpFlagInHelp checks,
// if '--help' flag is shown in help for child (executing `parent help child`),
// that has no other flags.
// Related to https://github.com/spf13/cobra/issues/302.
func TestHelpFlagInHelp(t *testing.T) {
	output := new(bytes.Buffer)
	parent := &Command{Use: "parent", Run: func(*Command, []string) {}}
	parent.SetOutput(output)

	child := &Command{Use: "child", Run: func(*Command, []string) {}}
	parent.AddCommand(child)

	parent.SetArgs([]string{"help", "child"})
	err := parent.Execute()
	if err != nil {
		t.Fatal(err)
	}

	if !strings.Contains(output.String(), "[flags]") {
		t.Errorf("\nExpecting to contain: %v\nGot: %v", "[flags]", output.String())
	}
}

// TestMergeCommandLineToFlags checks,
// if pflag.CommandLine is correctly merged to c.Flags() after first call
// of c.mergePersistentFlags.
// Related to https://github.com/spf13/cobra/issues/443.
func TestMergeCommandLineToFlags(t *testing.T) {
	pflag.Bool("boolflag", false, "")
	c := &Command{Use: "c", Run: func(*Command, []string) {}}
	c.mergePersistentFlags()
	if c.Flags().Lookup("boolflag") == nil {
		t.Fatal("Expecting to have flag from CommandLine in c.Flags()")
	}

	// Reset pflag.CommandLine flagset.
	pflag.CommandLine = pflag.NewFlagSet(os.Args[0], pflag.ExitOnError)
}

// TestUseDeprecatedFlags checks,
// if cobra.Execute() prints a message, if a deprecated flag is used.
// Related to https://github.com/spf13/cobra/issues/463.
func TestUseDeprecatedFlags(t *testing.T) {
	c := &Command{Use: "c", Run: func(*Command, []string) {}}
	output := new(bytes.Buffer)
	c.SetOutput(output)
	c.Flags().BoolP("deprecated", "d", false, "deprecated flag")
	c.Flags().MarkDeprecated("deprecated", "This flag is deprecated")

	c.SetArgs([]string{"c", "-d"})
	if err := c.Execute(); err != nil {
		t.Error("Unexpected error:", err)
	}
	if !strings.Contains(output.String(), "This flag is deprecated") {
		t.Errorf("Expected to contain deprecated message, but got %q", output.String())
	}
}

// TestSetHelpCommand checks, if SetHelpCommand works correctly.
func TestSetHelpCommand(t *testing.T) {
	c := &Command{Use: "c", Run: func(*Command, []string) {}}
	output := new(bytes.Buffer)
	c.SetOutput(output)
	c.SetArgs([]string{"help"})

	// Help will not be shown, if c has no subcommands.
	c.AddCommand(&Command{
		Use: "empty",
		Run: func(cmd *Command, args []string) {},
	})

	correctMessage := "WORKS"
	c.SetHelpCommand(&Command{
		Use:   "help [command]",
		Short: "Help about any command",
		Long: `Help provides help for any command in the application.
	Simply type ` + c.Name() + ` help [path to command] for full details.`,
		Run: func(c *Command, args []string) { c.Print(correctMessage) },
	})

	if err := c.Execute(); err != nil {
		t.Error("Unexpected error:", err)
	}

	if output.String() != correctMessage {
		t.Errorf("Expected to contain %q message, but got %q", correctMessage, output.String())
	}
}

func TestTraverseWithParentFlags(t *testing.T) {
	cmd := &Command{
		Use:              "do",
		TraverseChildren: true,
	}
	cmd.Flags().String("foo", "", "foo things")
	cmd.Flags().BoolP("goo", "g", false, "foo things")

	sub := &Command{Use: "next"}
	sub.Flags().String("add", "", "add things")
	cmd.AddCommand(sub)

	c, args, err := cmd.Traverse([]string{"-g", "--foo", "ok", "next", "--add"})
	if err != nil {
		t.Fatalf("Expected no error: %s", err)
	}
	if len(args) != 1 && args[0] != "--add" {
		t.Fatalf("wrong args %s", args)
	}
	if c.Name() != sub.Name() {
		t.Fatalf("wrong command %q expected %q", c.Name(), sub.Name())
	}
}

func TestTraverseNoParentFlags(t *testing.T) {
	cmd := &Command{
		Use:              "do",
		TraverseChildren: true,
	}
	cmd.Flags().String("foo", "", "foo things")

	sub := &Command{Use: "next"}
	sub.Flags().String("add", "", "add things")
	cmd.AddCommand(sub)

	c, args, err := cmd.Traverse([]string{"next"})
	if err != nil {
		t.Fatalf("Expected no error: %s", err)
	}
	if len(args) != 0 {
		t.Fatalf("wrong args %s", args)
	}
	if c.Name() != sub.Name() {
		t.Fatalf("wrong command %q expected %q", c.Name(), sub.Name())
	}
}

func TestTraverseWithBadParentFlags(t *testing.T) {
	cmd := &Command{
		Use:              "do",
		TraverseChildren: true,
	}
	sub := &Command{Use: "next"}
	sub.Flags().String("add", "", "add things")
	cmd.AddCommand(sub)

	expected := "got unknown flag: --add"

	c, _, err := cmd.Traverse([]string{"--add", "ok", "next"})
	if err == nil || strings.Contains(err.Error(), expected) {
		t.Fatalf("Expected error %s got %s", expected, err)
	}
	if c != nil {
		t.Fatalf("Expected nil command")
	}
}

func TestTraverseWithBadChildFlag(t *testing.T) {
	cmd := &Command{
		Use:              "do",
		TraverseChildren: true,
	}
	cmd.Flags().String("foo", "", "foo things")

	sub := &Command{Use: "next"}
	cmd.AddCommand(sub)

	// Expect no error because the last commands args shouldn't be parsed in
	// Traverse
	c, args, err := cmd.Traverse([]string{"next", "--add"})
	if err != nil {
		t.Fatalf("Expected no error: %s", err)
	}
	if len(args) != 1 && args[0] != "--add" {
		t.Fatalf("wrong args %s", args)
	}
	if c.Name() != sub.Name() {
		t.Fatalf("wrong command %q expected %q", c.Name(), sub.Name())
	}
}

func TestTraverseWithTwoSubcommands(t *testing.T) {
	cmd := &Command{
		Use:              "do",
		TraverseChildren: true,
	}

	sub := &Command{
		Use:              "sub",
		TraverseChildren: true,
	}
	cmd.AddCommand(sub)

	subsub := &Command{
		Use: "subsub",
	}
	sub.AddCommand(subsub)

	c, _, err := cmd.Traverse([]string{"sub", "subsub"})
	if err != nil {
		t.Fatalf("Expected no error: %s", err)
	}
	if c.Name() != subsub.Name() {
		t.Fatalf("wrong command %q expected %q", c.Name(), subsub.Name())
	}
}

func TestRequiredFlags(t *testing.T) {
	c := &Command{Use: "c", Run: func(*Command, []string) {}}
	output := new(bytes.Buffer)
	c.SetOutput(output)
	c.Flags().String("foo1", "", "required foo1")
	c.MarkFlagRequired("foo1")
	c.Flags().String("foo2", "", "required foo2")
	c.MarkFlagRequired("foo2")
	c.Flags().String("bar", "", "optional bar")

	expected := fmt.Sprintf("Required flag(s) %q, %q have/has not been set", "foo1", "foo2")

	if err := c.Execute(); err != nil {
		if err.Error() != expected {
			t.Errorf("expected %v, got %v", expected, err.Error())
		}
	}
}

func TestPersistentRequiredFlags(t *testing.T) {
	parent := &Command{Use: "parent", Run: func(*Command, []string) {}}
	output := new(bytes.Buffer)
	parent.SetOutput(output)
	parent.PersistentFlags().String("foo1", "", "required foo1")
	parent.MarkPersistentFlagRequired("foo1")
	parent.PersistentFlags().String("foo2", "", "required foo2")
	parent.MarkPersistentFlagRequired("foo2")
	parent.Flags().String("foo3", "", "optional foo3")

	child := &Command{Use: "child", Run: func(*Command, []string) {}}
	child.Flags().String("bar1", "", "required bar1")
	child.MarkFlagRequired("bar1")
	child.Flags().String("bar2", "", "required bar2")
	child.MarkFlagRequired("bar2")
	child.Flags().String("bar3", "", "optional bar3")

	parent.AddCommand(child)
	parent.SetArgs([]string{"child"})

	expected := fmt.Sprintf("Required flag(s) %q, %q, %q, %q have/has not been set", "bar1", "bar2", "foo1", "foo2")

	if err := parent.Execute(); err != nil {
		if err.Error() != expected {
			t.Errorf("expected %v, got %v", expected, err.Error())
		}
	}
}

// TestUpdateName checks if c.Name() updates on changed c.Use.
// Related to https://github.com/spf13/cobra/pull/422#discussion_r143918343.
func TestUpdateName(t *testing.T) {
	c := &Command{Use: "name xyz"}
	originalName := c.Name()

	c.Use = "changedName abc"
	if originalName == c.Name() || c.Name() != "changedName" {
		t.Error("c.Name() should be updated on changed c.Use")
	}
}
