package cobra

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

var _ = fmt.Println
var _ = os.Stderr

var tp, te, tt, t1 []string
var flagb1, flagb2, flagb3, flagbr, flagbp bool
var flags1, flags2a, flags2b, flags3 string
var flagi1, flagi2, flagi3, flagir int
var globalFlag1 bool
var flagEcho, rootcalled bool

const strtwoParentHelp = "help message for parent flag strtwo"
const strtwoChildHelp = "help message for child flag strtwo"

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

var cmdEcho = &Command{
	Use:     "echo [string to echo]",
	Aliases: []string{"say"},
	Short:   "Echo anything to the screen",
	Long:    `an utterly useless command for testing.`,
	Run: func(cmd *Command, args []string) {
		te = args
	},
}

var cmdTimes = &Command{
	Use:   "times [# times] [string to echo]",
	Short: "Echo anything to the screen more times",
	Long:  `a slightly useless command for testing.`,
	Run: func(cmd *Command, args []string) {
		tt = args
	},
}

var cmdRootNoRun = &Command{
	Use:   "cobra-test",
	Short: "The root can run it's own function",
	Long:  "The root description for help",
}

var cmdRootSameName = &Command{
	Use:   "print",
	Short: "Root with the same name as a subcommand",
	Long:  "The root description for help",
}

var cmdRootWithRun = &Command{
	Use:   "cobra-test",
	Short: "The root can run it's own function",
	Long:  "The root description for help",
	Run: func(cmd *Command, args []string) {
		rootcalled = true
	},
}

func flagInit() {
	cmdEcho.ResetFlags()
	cmdPrint.ResetFlags()
	cmdTimes.ResetFlags()
	cmdRootNoRun.ResetFlags()
	cmdRootSameName.ResetFlags()
	cmdRootWithRun.ResetFlags()
	cmdRootNoRun.PersistentFlags().StringVarP(&flags2a, "strtwo", "t", "two", strtwoParentHelp)
	cmdEcho.Flags().IntVarP(&flagi1, "intone", "i", 123, "help message for flag intone")
	cmdTimes.Flags().IntVarP(&flagi2, "inttwo", "j", 234, "help message for flag inttwo")
	cmdPrint.Flags().IntVarP(&flagi3, "intthree", "i", 345, "help message for flag intthree")
	cmdEcho.PersistentFlags().StringVarP(&flags1, "strone", "s", "one", "help message for flag strone")
	cmdEcho.PersistentFlags().BoolVarP(&flagbp, "persistentbool", "p", false, "help message for flag persistentbool")
	cmdTimes.PersistentFlags().StringVarP(&flags2b, "strtwo", "t", "2", strtwoChildHelp)
	cmdPrint.PersistentFlags().StringVarP(&flags3, "strthree", "s", "three", "help message for flag strthree")
	cmdEcho.Flags().BoolVarP(&flagb1, "boolone", "b", true, "help message for flag boolone")
	cmdTimes.Flags().BoolVarP(&flagb2, "booltwo", "c", false, "help message for flag booltwo")
	cmdPrint.Flags().BoolVarP(&flagb3, "boolthree", "b", true, "help message for flag boolthree")
}

func commandInit() {
	cmdEcho.ResetCommands()
	cmdPrint.ResetCommands()
	cmdTimes.ResetCommands()
	cmdRootNoRun.ResetCommands()
	cmdRootSameName.ResetCommands()
	cmdRootWithRun.ResetCommands()
}

func initialize() *Command {
	tt, tp, te = nil, nil, nil
	var c = cmdRootNoRun
	flagInit()
	commandInit()
	return c
}

func initializeWithSameName() *Command {
	tt, tp, te = nil, nil, nil
	var c = cmdRootSameName
	flagInit()
	commandInit()
	return c
}

func initializeWithRootCmd() *Command {
	cmdRootWithRun.ResetCommands()
	tt, tp, te, rootcalled = nil, nil, nil, false
	flagInit()
	cmdRootWithRun.Flags().BoolVarP(&flagbr, "boolroot", "b", false, "help message for flag boolroot")
	cmdRootWithRun.Flags().IntVarP(&flagir, "introot", "i", 321, "help message for flag introot")
	commandInit()
	return cmdRootWithRun
}

type resulter struct {
	Error   error
	Output  string
	Command *Command
}

func fullSetupTest(input string) resulter {
	c := initializeWithRootCmd()

	return fullTester(c, input)
}

func noRRSetupTest(input string) resulter {
	c := initialize()

	return fullTester(c, input)
}

func rootOnlySetupTest(input string) resulter {
	c := initializeWithRootCmd()

	return simpleTester(c, input)
}

func simpleTester(c *Command, input string) resulter {
	buf := new(bytes.Buffer)
	// Testing flag with invalid input
	c.SetOutput(buf)
	c.SetArgs(strings.Split(input, " "))

	err := c.Execute()
	output := buf.String()

	return resulter{err, output, c}
}

func fullTester(c *Command, input string) resulter {
	buf := new(bytes.Buffer)
	// Testing flag with invalid input
	c.SetOutput(buf)
	cmdEcho.AddCommand(cmdTimes)
	c.AddCommand(cmdPrint, cmdEcho)
	c.SetArgs(strings.Split(input, " "))

	err := c.Execute()
	output := buf.String()

	return resulter{err, output, c}
}

func checkResultContains(t *testing.T, x resulter, check string) {
	if !strings.Contains(x.Output, check) {
		t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", check, x.Output)
	}
}

func checkResultOmits(t *testing.T, x resulter, check string) {
	if strings.Contains(x.Output, check) {
		t.Errorf("Unexpected response.\nExpecting to omit: \n %q\nGot:\n %q\n", check, x.Output)
	}
}

func checkOutputContains(t *testing.T, c *Command, check string) {
	buf := new(bytes.Buffer)
	c.SetOutput(buf)
	c.Execute()

	if !strings.Contains(buf.String(), check) {
		t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", check, buf.String())
	}
}

func TestSingleCommand(t *testing.T) {
	noRRSetupTest("print one two")

	if te != nil || tt != nil {
		t.Error("Wrong command called")
	}
	if tp == nil {
		t.Error("Wrong command called")
	}
	if strings.Join(tp, " ") != "one two" {
		t.Error("Command didn't parse correctly")
	}
}

func TestChildCommand(t *testing.T) {
	noRRSetupTest("echo times one two")

	if te != nil || tp != nil {
		t.Error("Wrong command called")
	}
	if tt == nil {
		t.Error("Wrong command called")
	}
	if strings.Join(tt, " ") != "one two" {
		t.Error("Command didn't parse correctly")
	}
}

func TestCommandAlias(t *testing.T) {
	noRRSetupTest("say times one two")

	if te != nil || tp != nil {
		t.Error("Wrong command called")
	}
	if tt == nil {
		t.Error("Wrong command called")
	}
	if strings.Join(tt, " ") != "one two" {
		t.Error("Command didn't parse correctly")
	}
}

func TestPrefixMatching(t *testing.T) {
	EnablePrefixMatching = true
	noRRSetupTest("ech times one two")

	if te != nil || tp != nil {
		t.Error("Wrong command called")
	}
	if tt == nil {
		t.Error("Wrong command called")
	}
	if strings.Join(tt, " ") != "one two" {
		t.Error("Command didn't parse correctly")
	}

	EnablePrefixMatching = false
}

func TestNoPrefixMatching(t *testing.T) {
	EnablePrefixMatching = false

	noRRSetupTest("ech times one two")

	if !(tt == nil && te == nil && tp == nil) {
		t.Error("Wrong command called")
	}
}

func TestAliasPrefixMatching(t *testing.T) {
	EnablePrefixMatching = true
	noRRSetupTest("sa times one two")

	if te != nil || tp != nil {
		t.Error("Wrong command called")
	}
	if tt == nil {
		t.Error("Wrong command called")
	}
	if strings.Join(tt, " ") != "one two" {
		t.Error("Command didn't parse correctly")
	}
	EnablePrefixMatching = false
}

func TestChildSameName(t *testing.T) {
	c := initializeWithSameName()
	c.AddCommand(cmdPrint, cmdEcho)
	c.SetArgs(strings.Split("print one two", " "))
	c.Execute()

	if te != nil || tt != nil {
		t.Error("Wrong command called")
	}
	if tp == nil {
		t.Error("Wrong command called")
	}
	if strings.Join(tp, " ") != "one two" {
		t.Error("Command didn't parse correctly")
	}
}

func TestFlagLong(t *testing.T) {
	noRRSetupTest("echo --intone=13 something here")

	if strings.Join(te, " ") != "something here" {
		t.Errorf("flags didn't leave proper args remaining..%s given", te)
	}
	if flagi1 != 13 {
		t.Errorf("int flag didn't get correct value, had %d", flagi1)
	}
	if flagi2 != 234 {
		t.Errorf("default flag value changed, 234 expected, %d given", flagi2)
	}
}

func TestFlagShort(t *testing.T) {
	noRRSetupTest("echo -i13 something here")

	if strings.Join(te, " ") != "something here" {
		t.Errorf("flags didn't leave proper args remaining..%s given", te)
	}
	if flagi1 != 13 {
		t.Errorf("int flag didn't get correct value, had %d", flagi1)
	}
	if flagi2 != 234 {
		t.Errorf("default flag value changed, 234 expected, %d given", flagi2)
	}

	noRRSetupTest("echo -i 13 something here")

	if strings.Join(te, " ") != "something here" {
		t.Errorf("flags didn't leave proper args remaining..%s given", te)
	}
	if flagi1 != 13 {
		t.Errorf("int flag didn't get correct value, had %d", flagi1)
	}
	if flagi2 != 234 {
		t.Errorf("default flag value changed, 234 expected, %d given", flagi2)
	}

	noRRSetupTest("print -i99 one two")

	if strings.Join(tp, " ") != "one two" {
		t.Errorf("flags didn't leave proper args remaining..%s given", tp)
	}
	if flagi3 != 99 {
		t.Errorf("int flag didn't get correct value, had %d", flagi3)
	}
	if flagi1 != 123 {
		t.Errorf("default flag value changed on different command with same shortname, 234 expected, %d given", flagi2)
	}
}

func TestChildCommandFlags(t *testing.T) {
	noRRSetupTest("echo times -j 99 one two")

	if strings.Join(tt, " ") != "one two" {
		t.Errorf("flags didn't leave proper args remaining..%s given", tt)
	}

	// Testing with flag that shouldn't be persistent
	r := noRRSetupTest("echo times -j 99 -i77 one two")

	if r.Error == nil {
		t.Errorf("invalid flag should generate error")
	}

	if !strings.Contains(r.Output, "unknown shorthand") {
		t.Errorf("Wrong error message displayed, \n %s", r.Output)
	}

	if flagi2 != 99 {
		t.Errorf("flag value should be 99, %d given", flagi2)
	}

	if flagi1 != 123 {
		t.Errorf("unset flag should have default value, expecting 123, given %d", flagi1)
	}

	// Testing with flag only existing on child
	r = noRRSetupTest("echo -j 99 -i77 one two")

	if r.Error == nil {
		t.Errorf("invalid flag should generate error")
	}

	if !strings.Contains(r.Output, "unknown shorthand flag") {
		t.Errorf("Wrong error message displayed, \n %s", r.Output)
	}

	// Testing with persistent flag overwritten by child
	noRRSetupTest("echo times --strtwo=child one two")

	if flags2b != "child" {
		t.Errorf("flag value should be child, %s given", flags2b)
	}

	if flags2a != "two" {
		t.Errorf("unset flag should have default value, expecting two, given %s", flags2a)
	}

	// Testing flag with invalid input
	r = noRRSetupTest("echo -i10E")

	if r.Error == nil {
		t.Errorf("invalid input should generate error")
	}

	if !strings.Contains(r.Output, "invalid argument \"10E\" for -i10E") {
		t.Errorf("Wrong error message displayed, \n %s", r.Output)
	}
}

func TestTrailingCommandFlags(t *testing.T) {
	x := fullSetupTest("echo two -x")

	if x.Error == nil {
		t.Errorf("invalid flag should generate error")
	}
}

func TestInvalidSubCommandFlags(t *testing.T) {
	cmd := initializeWithRootCmd()
	cmd.AddCommand(cmdTimes)

	result := simpleTester(cmd, "times --inttwo=2 --badflag=bar")

	checkResultContains(t, result, "unknown flag: --badflag")

	if strings.Contains(result.Output, "unknown flag: --inttwo") {
		t.Errorf("invalid --badflag flag shouldn't fail on 'unknown' --inttwo flag")
	}

}

func TestPersistentFlags(t *testing.T) {
	fullSetupTest("echo -s something -p more here")

	// persistentFlag should act like normal flag on it's own command
	if strings.Join(te, " ") != "more here" {
		t.Errorf("flags didn't leave proper args remaining..%s given", te)
	}
	if flags1 != "something" {
		t.Errorf("string flag didn't get correct value, had %v", flags1)
	}
	if !flagbp {
		t.Errorf("persistent bool flag not parsed correctly. Expected true, had %v", flagbp)
	}

	// persistentFlag should act like normal flag on it's own command
	fullSetupTest("echo times -s again -c -p test here")

	if strings.Join(tt, " ") != "test here" {
		t.Errorf("flags didn't leave proper args remaining..%s given", tt)
	}

	if flags1 != "again" {
		t.Errorf("string flag didn't get correct value, had %v", flags1)
	}

	if !flagb2 {
		t.Errorf("local flag not parsed correctly. Expected true, had %v", flagb2)
	}
	if !flagbp {
		t.Errorf("persistent bool flag not parsed correctly. Expected true, had %v", flagbp)
	}
}

func TestHelpCommand(t *testing.T) {
	c := fullSetupTest("help echo")
	checkResultContains(t, c, cmdEcho.Long)

	r := fullSetupTest("help echo times")
	checkResultContains(t, r, cmdTimes.Long)
}

func TestChildCommandHelp(t *testing.T) {
	c := noRRSetupTest("print --help")
	checkResultContains(t, c, strtwoParentHelp)
	r := noRRSetupTest("echo times --help")
	checkResultContains(t, r, strtwoChildHelp)
}

func TestRunnableRootCommand(t *testing.T) {
	fullSetupTest("")

	if rootcalled != true {
		t.Errorf("Root Function was not called")
	}
}

func TestRunnableRootCommandNilInput(t *testing.T) {
	empty_arg := make([]string, 0)
	c := initializeWithRootCmd()

	buf := new(bytes.Buffer)
	// Testing flag with invalid input
	c.SetOutput(buf)
	cmdEcho.AddCommand(cmdTimes)
	c.AddCommand(cmdPrint, cmdEcho)
	c.SetArgs(empty_arg)

	c.Execute()

	if rootcalled != true {
		t.Errorf("Root Function was not called")
	}
}

func TestRunnableRootCommandEmptyInput(t *testing.T) {
	args := make([]string, 3)
	args[0] = ""
	args[1] = "--introot=12"
	args[2] = ""
	c := initializeWithRootCmd()

	buf := new(bytes.Buffer)
	// Testing flag with invalid input
	c.SetOutput(buf)
	cmdEcho.AddCommand(cmdTimes)
	c.AddCommand(cmdPrint, cmdEcho)
	c.SetArgs(args)

	c.Execute()

	if rootcalled != true {
		t.Errorf("Root Function was not called.\n\nOutput was:\n\n%s\n", buf)
	}
}

func TestInvalidSubcommandWhenArgsAllowed(t *testing.T) {
	fullSetupTest("echo invalid-sub")

	if te[0] != "invalid-sub" {
		t.Errorf("Subcommand didn't work...")
	}
}

func TestRootFlags(t *testing.T) {
	fullSetupTest("-i 17 -b")

	if flagbr != true {
		t.Errorf("flag value should be true, %v given", flagbr)
	}

	if flagir != 17 {
		t.Errorf("flag value should be 17, %d given", flagir)
	}
}

func TestRootHelp(t *testing.T) {
	x := fullSetupTest("--help")

	checkResultContains(t, x, "Available Commands:")
	checkResultContains(t, x, "for more information about a command")

	if strings.Contains(x.Output, "unknown flag: --help") {
		t.Errorf("--help shouldn't trigger an error, Got: \n %s", x.Output)
	}

	if strings.Contains(x.Output, cmdEcho.Use) {
		t.Errorf("--help shouldn't display subcommand's usage, Got: \n %s", x.Output)
	}

	x = fullSetupTest("echo --help")

	if strings.Contains(x.Output, cmdTimes.Use) {
		t.Errorf("--help shouldn't display subsubcommand's usage, Got: \n %s", x.Output)
	}

	checkResultContains(t, x, "Available Commands:")
	checkResultContains(t, x, "for more information about a command")

	if strings.Contains(x.Output, "unknown flag: --help") {
		t.Errorf("--help shouldn't trigger an error, Got: \n %s", x.Output)
	}

}

func TestFlagAccess(t *testing.T) {
	initialize()

	local := cmdTimes.LocalFlags()
	inherited := cmdTimes.InheritedFlags()

	for _, f := range []string{"inttwo", "strtwo", "booltwo"} {
		if local.Lookup(f) == nil {
			t.Errorf("LocalFlags expected to contain %s, Got: nil", f)
		}
	}
	if inherited.Lookup("strone") == nil {
		t.Errorf("InheritedFlags expected to contain strone, Got: nil")
	}
	if inherited.Lookup("strtwo") != nil {
		t.Errorf("InheritedFlags shouldn not contain overwritten flag strtwo")

	}
}

func TestNoNRunnableRootCommandNilInput(t *testing.T) {
	args := make([]string, 0)
	c := initialize()

	buf := new(bytes.Buffer)
	// Testing flag with invalid input
	c.SetOutput(buf)
	cmdEcho.AddCommand(cmdTimes)
	c.AddCommand(cmdPrint, cmdEcho)
	c.SetArgs(args)

	c.Execute()

	if !strings.Contains(buf.String(), cmdRootNoRun.Long) {
		t.Errorf("Expected to get help output, Got: \n %s", buf)
	}
}

func TestRootNoCommandHelp(t *testing.T) {
	x := rootOnlySetupTest("--help")

	checkResultOmits(t, x, "Available Commands:")
	checkResultOmits(t, x, "for more information about a command")

	if strings.Contains(x.Output, "unknown flag: --help") {
		t.Errorf("--help shouldn't trigger an error, Got: \n %s", x.Output)
	}

	x = rootOnlySetupTest("echo --help")

	checkResultOmits(t, x, "Available Commands:")
	checkResultOmits(t, x, "for more information about a command")

	if strings.Contains(x.Output, "unknown flag: --help") {
		t.Errorf("--help shouldn't trigger an error, Got: \n %s", x.Output)
	}
}

func TestRootUnknownCommand(t *testing.T) {
	r := noRRSetupTest("bogus")
	s := "Error: unknown command \"bogus\"\nRun 'cobra-test help' for usage.\n"

	if r.Output != s {
		t.Errorf("Unexpected response.\nExpecting to be:\n %q\nGot:\n %q\n", s, r.Output)
	}
}

func TestFlagsBeforeCommand(t *testing.T) {
	// short without space
	x := fullSetupTest("-i10 echo")
	if x.Error != nil {
		t.Errorf("Valid Input shouldn't have errors, got:\n %q", x.Error)
	}

	// short (int) with equals
	// It appears that pflags doesn't support this...
	// Commenting out until support can be added

	//x = noRRSetupTest("echo -i=10")
	//if x.Error != nil {
	//t.Errorf("Valid Input shouldn't have errors, got:\n %s", x.Error)
	//}

	// long with equals
	x = noRRSetupTest("--intone=123 echo one two")
	if x.Error != nil {
		t.Errorf("Valid Input shouldn't have errors, got:\n %s", x.Error)
	}

	// With parsing error properly reported
	x = fullSetupTest("-i10E echo")
	if !strings.Contains(x.Output, "invalid argument \"10E\" for -i10E") {
		t.Errorf("Wrong error message displayed, \n %s", x.Output)
	}

	//With quotes
	x = fullSetupTest("-s=\"walking\" echo")
	if x.Error != nil {
		t.Errorf("Valid Input shouldn't have errors, got:\n %q", x.Error)
	}

	//With quotes and space
	x = fullSetupTest("-s=\"walking fast\" echo")
	if x.Error != nil {
		t.Errorf("Valid Input shouldn't have errors, got:\n %q", x.Error)
	}

	//With inner quote
	x = fullSetupTest("-s=\"walking \\\"Inner Quote\\\" fast\" echo")
	if x.Error != nil {
		t.Errorf("Valid Input shouldn't have errors, got:\n %q", x.Error)
	}

	//With quotes and space
	x = fullSetupTest("-s=\"walking \\\"Inner Quote\\\" fast\" echo")
	if x.Error != nil {
		t.Errorf("Valid Input shouldn't have errors, got:\n %q", x.Error)
	}

}