package cli

import (
	"bytes"
	"errors"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"reflect"
	"strings"
	"testing"
)

var (
	lastExitCode = 0
	fakeOsExiter = func(rc int) {
		lastExitCode = rc
	}
	fakeErrWriter = &bytes.Buffer{}
)

func init() {
	OsExiter = fakeOsExiter
	ErrWriter = fakeErrWriter
}

type opCounts struct {
	Total, BashComplete, OnUsageError, Before, CommandNotFound, Action, After, SubCommand int
}

func ExampleApp_Run() {
	// set args for examples sake
	os.Args = []string{"greet", "--name", "Jeremy"}

	app := NewApp()
	app.Name = "greet"
	app.Flags = []Flag{
		StringFlag{Name: "name", Value: "bob", Usage: "a name to say"},
	}
	app.Action = func(c *Context) error {
		fmt.Printf("Hello %v\n", c.String("name"))
		return nil
	}
	app.UsageText = "app [first_arg] [second_arg]"
	app.Author = "Harrison"
	app.Email = "harrison@lolwut.com"
	app.Authors = []Author{{Name: "Oliver Allen", Email: "oliver@toyshop.com"}}
	_ = app.Run(os.Args)
	// Output:
	// Hello Jeremy
}

func ExampleApp_Run_subcommand() {
	// set args for examples sake
	os.Args = []string{"say", "hi", "english", "--name", "Jeremy"}
	app := NewApp()
	app.Name = "say"
	app.Commands = []Command{
		{
			Name:        "hello",
			Aliases:     []string{"hi"},
			Usage:       "use it to see a description",
			Description: "This is how we describe hello the function",
			Subcommands: []Command{
				{
					Name:        "english",
					Aliases:     []string{"en"},
					Usage:       "sends a greeting in english",
					Description: "greets someone in english",
					Flags: []Flag{
						StringFlag{
							Name:  "name",
							Value: "Bob",
							Usage: "Name of the person to greet",
						},
					},
					Action: func(c *Context) error {
						fmt.Println("Hello,", c.String("name"))
						return nil
					},
				},
			},
		},
	}

	_ = app.Run(os.Args)
	// Output:
	// Hello, Jeremy
}

func ExampleApp_Run_appHelp() {
	// set args for examples sake
	os.Args = []string{"greet", "help"}

	app := NewApp()
	app.Name = "greet"
	app.Version = "0.1.0"
	app.Description = "This is how we describe greet the app"
	app.Authors = []Author{
		{Name: "Harrison", Email: "harrison@lolwut.com"},
		{Name: "Oliver Allen", Email: "oliver@toyshop.com"},
	}
	app.Flags = []Flag{
		StringFlag{Name: "name", Value: "bob", Usage: "a name to say"},
	}
	app.Commands = []Command{
		{
			Name:        "describeit",
			Aliases:     []string{"d"},
			Usage:       "use it to see a description",
			Description: "This is how we describe describeit the function",
			Action: func(c *Context) error {
				fmt.Printf("i like to describe things")
				return nil
			},
		},
	}
	_ = app.Run(os.Args)
	// Output:
	// NAME:
	//    greet - A new cli application
	//
	// USAGE:
	//    greet [global options] command [command options] [arguments...]
	//
	// VERSION:
	//    0.1.0
	//
	// DESCRIPTION:
	//    This is how we describe greet the app
	//
	// AUTHORS:
	//    Harrison <harrison@lolwut.com>
	//    Oliver Allen <oliver@toyshop.com>
	//
	// COMMANDS:
	//    describeit, d  use it to see a description
	//    help, h        Shows a list of commands or help for one command
	//
	// GLOBAL OPTIONS:
	//    --name value   a name to say (default: "bob")
	//    --help, -h     show help
	//    --version, -v  print the version
}

func ExampleApp_Run_commandHelp() {
	// set args for examples sake
	os.Args = []string{"greet", "h", "describeit"}

	app := NewApp()
	app.Name = "greet"
	app.Flags = []Flag{
		StringFlag{Name: "name", Value: "bob", Usage: "a name to say"},
	}
	app.Commands = []Command{
		{
			Name:        "describeit",
			Aliases:     []string{"d"},
			Usage:       "use it to see a description",
			Description: "This is how we describe describeit the function",
			Action: func(c *Context) error {
				fmt.Printf("i like to describe things")
				return nil
			},
		},
	}
	_ = app.Run(os.Args)
	// Output:
	// NAME:
	//    greet describeit - use it to see a description
	//
	// USAGE:
	//    greet describeit [arguments...]
	//
	// DESCRIPTION:
	//    This is how we describe describeit the function
}

func ExampleApp_Run_noAction() {
	app := App{}
	app.Name = "greet"
	_ = app.Run([]string{"greet"})
	// Output:
	// NAME:
	//    greet
	//
	// USAGE:
	//     [global options] command [command options] [arguments...]
	//
	// COMMANDS:
	//    help, h  Shows a list of commands or help for one command
	//
	// GLOBAL OPTIONS:
	//    --help, -h     show help
	//    --version, -v  print the version
}

func ExampleApp_Run_subcommandNoAction() {
	app := App{}
	app.Name = "greet"
	app.Commands = []Command{
		{
			Name:        "describeit",
			Aliases:     []string{"d"},
			Usage:       "use it to see a description",
			Description: "This is how we describe describeit the function",
		},
	}
	_ = app.Run([]string{"greet", "describeit"})
	// Output:
	// NAME:
	//     describeit - use it to see a description
	//
	// USAGE:
	//     describeit [arguments...]
	//
	// DESCRIPTION:
	//    This is how we describe describeit the function

}

func ExampleApp_Run_bashComplete_withShortFlag() {
	os.Args = []string{"greet", "-", "--generate-bash-completion"}

	app := NewApp()
	app.Name = "greet"
	app.EnableBashCompletion = true
	app.Flags = []Flag{
		IntFlag{
			Name: "other,o",
		},
		StringFlag{
			Name: "xyz,x",
		},
	}

	_ = app.Run(os.Args)
	// Output:
	// --other
	// -o
	// --xyz
	// -x
	// --help
	// -h
	// --version
	// -v
}

func ExampleApp_Run_bashComplete_withLongFlag() {
	os.Args = []string{"greet", "--s", "--generate-bash-completion"}

	app := NewApp()
	app.Name = "greet"
	app.EnableBashCompletion = true
	app.Flags = []Flag{
		IntFlag{
			Name: "other,o",
		},
		StringFlag{
			Name: "xyz,x",
		},
		StringFlag{
			Name: "some-flag,s",
		},
		StringFlag{
			Name: "similar-flag",
		},
	}

	_ = app.Run(os.Args)
	// Output:
	// --some-flag
	// --similar-flag
}
func ExampleApp_Run_bashComplete_withMultipleLongFlag() {
	os.Args = []string{"greet", "--st", "--generate-bash-completion"}

	app := NewApp()
	app.Name = "greet"
	app.EnableBashCompletion = true
	app.Flags = []Flag{
		IntFlag{
			Name: "int-flag,i",
		},
		StringFlag{
			Name: "string,s",
		},
		StringFlag{
			Name: "string-flag-2",
		},
		StringFlag{
			Name: "similar-flag",
		},
		StringFlag{
			Name: "some-flag",
		},
	}

	_ = app.Run(os.Args)
	// Output:
	// --string
	// --string-flag-2
}

func ExampleApp_Run_bashComplete() {
	// set args for examples sake
	os.Args = []string{"greet", "--generate-bash-completion"}

	app := NewApp()
	app.Name = "greet"
	app.EnableBashCompletion = true
	app.Commands = []Command{
		{
			Name:        "describeit",
			Aliases:     []string{"d"},
			Usage:       "use it to see a description",
			Description: "This is how we describe describeit the function",
			Action: func(c *Context) error {
				fmt.Printf("i like to describe things")
				return nil
			},
		}, {
			Name:        "next",
			Usage:       "next example",
			Description: "more stuff to see when generating bash completion",
			Action: func(c *Context) error {
				fmt.Printf("the next example")
				return nil
			},
		},
	}

	_ = app.Run(os.Args)
	// Output:
	// describeit
	// d
	// next
	// help
	// h
}

func ExampleApp_Run_zshComplete() {
	// set args for examples sake
	os.Args = []string{"greet", "--generate-bash-completion"}
	_ = os.Setenv("_CLI_ZSH_AUTOCOMPLETE_HACK", "1")

	app := NewApp()
	app.Name = "greet"
	app.EnableBashCompletion = true
	app.Commands = []Command{
		{
			Name:        "describeit",
			Aliases:     []string{"d"},
			Usage:       "use it to see a description",
			Description: "This is how we describe describeit the function",
			Action: func(c *Context) error {
				fmt.Printf("i like to describe things")
				return nil
			},
		}, {
			Name:        "next",
			Usage:       "next example",
			Description: "more stuff to see when generating bash completion",
			Action: func(c *Context) error {
				fmt.Printf("the next example")
				return nil
			},
		},
	}

	_ = app.Run(os.Args)
	// Output:
	// describeit:use it to see a description
	// d:use it to see a description
	// next:next example
	// help:Shows a list of commands or help for one command
	// h:Shows a list of commands or help for one command
}

func TestApp_Run(t *testing.T) {
	s := ""

	app := NewApp()
	app.Action = func(c *Context) error {
		s = s + c.Args().First()
		return nil
	}

	err := app.Run([]string{"command", "foo"})
	expect(t, err, nil)
	err = app.Run([]string{"command", "bar"})
	expect(t, err, nil)
	expect(t, s, "foobar")
}

var commandAppTests = []struct {
	name     string
	expected bool
}{
	{"foobar", true},
	{"batbaz", true},
	{"b", true},
	{"f", true},
	{"bat", false},
	{"nothing", false},
}

func TestApp_Command(t *testing.T) {
	app := NewApp()
	fooCommand := Command{Name: "foobar", Aliases: []string{"f"}}
	batCommand := Command{Name: "batbaz", Aliases: []string{"b"}}
	app.Commands = []Command{
		fooCommand,
		batCommand,
	}

	for _, test := range commandAppTests {
		expect(t, app.Command(test.name) != nil, test.expected)
	}
}

func TestApp_Setup_defaultsWriter(t *testing.T) {
	app := &App{}
	app.Setup()
	expect(t, app.Writer, os.Stdout)
}

func TestApp_CommandWithArgBeforeFlags(t *testing.T) {
	var parsedOption, firstArg string

	app := NewApp()
	command := Command{
		Name: "cmd",
		Flags: []Flag{
			StringFlag{Name: "option", Value: "", Usage: "some option"},
		},
		Action: func(c *Context) error {
			parsedOption = c.String("option")
			firstArg = c.Args().First()
			return nil
		},
	}
	app.Commands = []Command{command}

	_ = app.Run([]string{"", "cmd", "my-arg", "--option", "my-option"})

	expect(t, parsedOption, "my-option")
	expect(t, firstArg, "my-arg")
}

func TestApp_CommandWithArgBeforeBoolFlags(t *testing.T) {
	var parsedOption, parsedSecondOption, firstArg string
	var parsedBool, parsedSecondBool bool

	app := NewApp()
	command := Command{
		Name: "cmd",
		Flags: []Flag{
			StringFlag{Name: "option", Value: "", Usage: "some option"},
			StringFlag{Name: "secondOption", Value: "", Usage: "another option"},
			BoolFlag{Name: "boolflag", Usage: "some bool"},
			BoolFlag{Name: "b", Usage: "another bool"},
		},
		Action: func(c *Context) error {
			parsedOption = c.String("option")
			parsedSecondOption = c.String("secondOption")
			parsedBool = c.Bool("boolflag")
			parsedSecondBool = c.Bool("b")
			firstArg = c.Args().First()
			return nil
		},
	}
	app.Commands = []Command{command}

	_ = app.Run([]string{"", "cmd", "my-arg", "--boolflag", "--option", "my-option", "-b", "--secondOption", "fancy-option"})

	expect(t, parsedOption, "my-option")
	expect(t, parsedSecondOption, "fancy-option")
	expect(t, parsedBool, true)
	expect(t, parsedSecondBool, true)
	expect(t, firstArg, "my-arg")
}

func TestApp_RunAsSubcommandParseFlags(t *testing.T) {
	var context *Context

	a := NewApp()
	a.Commands = []Command{
		{
			Name: "foo",
			Action: func(c *Context) error {
				context = c
				return nil
			},
			Flags: []Flag{
				StringFlag{
					Name:  "lang",
					Value: "english",
					Usage: "language for the greeting",
				},
			},
			Before: func(_ *Context) error { return nil },
		},
	}
	_ = a.Run([]string{"", "foo", "--lang", "spanish", "abcd"})

	expect(t, context.Args().Get(0), "abcd")
	expect(t, context.String("lang"), "spanish")
}

func TestApp_RunAsSubCommandIncorrectUsage(t *testing.T) {
	a := App{
		Flags: []Flag{
			StringFlag{Name: "--foo"},
		},
		Writer: bytes.NewBufferString(""),
	}

	set := flag.NewFlagSet("", flag.ContinueOnError)
	_ = set.Parse([]string{"", "---foo"})
	c := &Context{flagSet: set}

	err := a.RunAsSubcommand(c)

	expect(t, err, errors.New("bad flag syntax: ---foo"))
}

func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) {
	var parsedOption string
	var args []string

	app := NewApp()
	command := Command{
		Name: "cmd",
		Flags: []Flag{
			StringFlag{Name: "option", Value: "", Usage: "some option"},
		},
		Action: func(c *Context) error {
			parsedOption = c.String("option")
			args = c.Args()
			return nil
		},
	}
	app.Commands = []Command{command}

	_ = app.Run([]string{"", "cmd", "my-arg", "--option", "my-option", "--", "--notARealFlag"})

	expect(t, parsedOption, "my-option")
	expect(t, args[0], "my-arg")
	expect(t, args[1], "--")
	expect(t, args[2], "--notARealFlag")
}

func TestApp_CommandWithDash(t *testing.T) {
	var args []string

	app := NewApp()
	command := Command{
		Name: "cmd",
		Action: func(c *Context) error {
			args = c.Args()
			return nil
		},
	}
	app.Commands = []Command{command}

	_ = app.Run([]string{"", "cmd", "my-arg", "-"})

	expect(t, args[0], "my-arg")
	expect(t, args[1], "-")
}

func TestApp_CommandWithNoFlagBeforeTerminator(t *testing.T) {
	var args []string

	app := NewApp()
	command := Command{
		Name: "cmd",
		Action: func(c *Context) error {
			args = c.Args()
			return nil
		},
	}
	app.Commands = []Command{command}

	_ = app.Run([]string{"", "cmd", "my-arg", "--", "notAFlagAtAll"})

	expect(t, args[0], "my-arg")
	expect(t, args[1], "--")
	expect(t, args[2], "notAFlagAtAll")
}

func TestApp_VisibleCommands(t *testing.T) {
	app := NewApp()
	app.Commands = []Command{
		{
			Name:     "frob",
			HelpName: "foo frob",
			Action:   func(_ *Context) error { return nil },
		},
		{
			Name:     "frib",
			HelpName: "foo frib",
			Hidden:   true,
			Action:   func(_ *Context) error { return nil },
		},
	}

	app.Setup()
	expected := []Command{
		app.Commands[0],
		app.Commands[2], // help
	}
	actual := app.VisibleCommands()
	expect(t, len(expected), len(actual))
	for i, actualCommand := range actual {
		expectedCommand := expected[i]

		if expectedCommand.Action != nil {
			// comparing func addresses is OK!
			expect(t, fmt.Sprintf("%p", expectedCommand.Action), fmt.Sprintf("%p", actualCommand.Action))
		}

		// nil out funcs, as they cannot be compared
		// (https://github.com/golang/go/issues/8554)
		expectedCommand.Action = nil
		actualCommand.Action = nil

		if !reflect.DeepEqual(expectedCommand, actualCommand) {
			t.Errorf("expected\n%#v\n!=\n%#v", expectedCommand, actualCommand)
		}
	}
}

func TestApp_UseShortOptionHandling(t *testing.T) {
	var one, two bool
	var name string
	expected := "expectedName"

	app := NewApp()
	app.UseShortOptionHandling = true
	app.Flags = []Flag{
		BoolFlag{Name: "one, o"},
		BoolFlag{Name: "two, t"},
		StringFlag{Name: "name, n"},
	}
	app.Action = func(c *Context) error {
		one = c.Bool("one")
		two = c.Bool("two")
		name = c.String("name")
		return nil
	}

	app.Run([]string{"", "-on", expected})
	expect(t, one, true)
	expect(t, two, false)
	expect(t, name, expected)
}

func TestApp_UseShortOptionHandling_missing_value(t *testing.T) {
	app := NewApp()
	app.UseShortOptionHandling = true
	app.Flags = []Flag{
		StringFlag{Name: "name, n"},
	}

	err := app.Run([]string{"", "-n"})
	expect(t, err, errors.New("flag needs an argument: -n"))
}

func TestApp_UseShortOptionHandlingCommand(t *testing.T) {
	var one, two bool
	var name string
	expected := "expectedName"

	app := NewApp()
	app.UseShortOptionHandling = true
	command := Command{
		Name: "cmd",
		Flags: []Flag{
			BoolFlag{Name: "one, o"},
			BoolFlag{Name: "two, t"},
			StringFlag{Name: "name, n"},
		},
		Action: func(c *Context) error {
			one = c.Bool("one")
			two = c.Bool("two")
			name = c.String("name")
			return nil
		},
	}
	app.Commands = []Command{command}

	app.Run([]string{"", "cmd", "-on", expected})
	expect(t, one, true)
	expect(t, two, false)
	expect(t, name, expected)
}

func TestApp_UseShortOptionHandlingCommand_missing_value(t *testing.T) {
	app := NewApp()
	app.UseShortOptionHandling = true
	command := Command{
		Name: "cmd",
		Flags: []Flag{
			StringFlag{Name: "name, n"},
		},
	}
	app.Commands = []Command{command}

	err := app.Run([]string{"", "cmd", "-n"})
	expect(t, err, errors.New("flag needs an argument: -n"))
}

func TestApp_UseShortOptionHandlingSubCommand(t *testing.T) {
	var one, two bool
	var name string
	expected := "expectedName"

	app := NewApp()
	app.UseShortOptionHandling = true
	command := Command{
		Name: "cmd",
	}
	subCommand := Command{
		Name: "sub",
		Flags: []Flag{
			BoolFlag{Name: "one, o"},
			BoolFlag{Name: "two, t"},
			StringFlag{Name: "name, n"},
		},
		Action: func(c *Context) error {
			one = c.Bool("one")
			two = c.Bool("two")
			name = c.String("name")
			return nil
		},
	}
	command.Subcommands = []Command{subCommand}
	app.Commands = []Command{command}

	err := app.Run([]string{"", "cmd", "sub", "-on", expected})
	expect(t, err, nil)
	expect(t, one, true)
	expect(t, two, false)
	expect(t, name, expected)
}

func TestApp_UseShortOptionHandlingSubCommand_missing_value(t *testing.T) {
	app := NewApp()
	app.UseShortOptionHandling = true
	command := Command{
		Name: "cmd",
	}
	subCommand := Command{
		Name: "sub",
		Flags: []Flag{
			StringFlag{Name: "name, n"},
		},
	}
	command.Subcommands = []Command{subCommand}
	app.Commands = []Command{command}

	err := app.Run([]string{"", "cmd", "sub", "-n"})
	expect(t, err, errors.New("flag needs an argument: -n"))
}

func TestApp_Float64Flag(t *testing.T) {
	var meters float64

	app := NewApp()
	app.Flags = []Flag{
		Float64Flag{Name: "height", Value: 1.5, Usage: "Set the height, in meters"},
	}
	app.Action = func(c *Context) error {
		meters = c.Float64("height")
		return nil
	}

	_ = app.Run([]string{"", "--height", "1.93"})
	expect(t, meters, 1.93)
}

func TestApp_ParseSliceFlags(t *testing.T) {
	var parsedIntSlice []int
	var parsedStringSlice []string

	app := NewApp()
	command := Command{
		Name: "cmd",
		Flags: []Flag{
			IntSliceFlag{Name: "p", Value: &IntSlice{}, Usage: "set one or more ip addr"},
			StringSliceFlag{Name: "ip", Value: &StringSlice{}, Usage: "set one or more ports to open"},
		},
		Action: func(c *Context) error {
			parsedIntSlice = c.IntSlice("p")
			parsedStringSlice = c.StringSlice("ip")
			return nil
		},
	}
	app.Commands = []Command{command}

	_ = app.Run([]string{"", "cmd", "my-arg", "-p", "22", "-p", "80", "-ip", "8.8.8.8", "-ip", "8.8.4.4"})

	IntsEquals := func(a, b []int) bool {
		if len(a) != len(b) {
			return false
		}
		for i, v := range a {
			if v != b[i] {
				return false
			}
		}
		return true
	}

	StrsEquals := func(a, b []string) bool {
		if len(a) != len(b) {
			return false
		}
		for i, v := range a {
			if v != b[i] {
				return false
			}
		}
		return true
	}
	var expectedIntSlice = []int{22, 80}
	var expectedStringSlice = []string{"8.8.8.8", "8.8.4.4"}

	if !IntsEquals(parsedIntSlice, expectedIntSlice) {
		t.Errorf("%v does not match %v", parsedIntSlice, expectedIntSlice)
	}

	if !StrsEquals(parsedStringSlice, expectedStringSlice) {
		t.Errorf("%v does not match %v", parsedStringSlice, expectedStringSlice)
	}
}

func TestApp_ParseSliceFlagsWithMissingValue(t *testing.T) {
	var parsedIntSlice []int
	var parsedStringSlice []string

	app := NewApp()
	command := Command{
		Name: "cmd",
		Flags: []Flag{
			IntSliceFlag{Name: "a", Usage: "set numbers"},
			StringSliceFlag{Name: "str", Usage: "set strings"},
		},
		Action: func(c *Context) error {
			parsedIntSlice = c.IntSlice("a")
			parsedStringSlice = c.StringSlice("str")
			return nil
		},
	}
	app.Commands = []Command{command}

	_ = app.Run([]string{"", "cmd", "my-arg", "-a", "2", "-str", "A"})

	var expectedIntSlice = []int{2}
	var expectedStringSlice = []string{"A"}

	if parsedIntSlice[0] != expectedIntSlice[0] {
		t.Errorf("%v does not match %v", parsedIntSlice[0], expectedIntSlice[0])
	}

	if parsedStringSlice[0] != expectedStringSlice[0] {
		t.Errorf("%v does not match %v", parsedIntSlice[0], expectedIntSlice[0])
	}
}

func TestApp_DefaultStdout(t *testing.T) {
	app := NewApp()

	if app.Writer != os.Stdout {
		t.Error("Default output writer not set.")
	}
}

type mockWriter struct {
	written []byte
}

func (fw *mockWriter) Write(p []byte) (n int, err error) {
	if fw.written == nil {
		fw.written = p
	} else {
		fw.written = append(fw.written, p...)
	}

	return len(p), nil
}

func (fw *mockWriter) GetWritten() (b []byte) {
	return fw.written
}

func TestApp_SetStdout(t *testing.T) {
	w := &mockWriter{}

	app := NewApp()
	app.Name = "test"
	app.Writer = w

	err := app.Run([]string{"help"})

	if err != nil {
		t.Fatalf("Run error: %s", err)
	}

	if len(w.written) == 0 {
		t.Error("App did not write output to desired writer.")
	}
}

func TestApp_BeforeFunc(t *testing.T) {
	counts := &opCounts{}
	beforeError := fmt.Errorf("fail")
	var err error

	app := NewApp()

	app.Before = func(c *Context) error {
		counts.Total++
		counts.Before = counts.Total
		s := c.String("opt")
		if s == "fail" {
			return beforeError
		}

		return nil
	}

	app.Commands = []Command{
		{
			Name: "sub",
			Action: func(c *Context) error {
				counts.Total++
				counts.SubCommand = counts.Total
				return nil
			},
		},
	}

	app.Flags = []Flag{
		StringFlag{Name: "opt"},
	}

	// run with the Before() func succeeding
	err = app.Run([]string{"command", "--opt", "succeed", "sub"})

	if err != nil {
		t.Fatalf("Run error: %s", err)
	}

	if counts.Before != 1 {
		t.Errorf("Before() not executed when expected")
	}

	if counts.SubCommand != 2 {
		t.Errorf("Subcommand not executed when expected")
	}

	// reset
	counts = &opCounts{}

	// run with the Before() func failing
	err = app.Run([]string{"command", "--opt", "fail", "sub"})

	// should be the same error produced by the Before func
	if err != beforeError {
		t.Errorf("Run error expected, but not received")
	}

	if counts.Before != 1 {
		t.Errorf("Before() not executed when expected")
	}

	if counts.SubCommand != 0 {
		t.Errorf("Subcommand executed when NOT expected")
	}

	// reset
	counts = &opCounts{}

	afterError := errors.New("fail again")
	app.After = func(_ *Context) error {
		return afterError
	}

	// run with the Before() func failing, wrapped by After()
	err = app.Run([]string{"command", "--opt", "fail", "sub"})

	// should be the same error produced by the Before func
	if _, ok := err.(MultiError); !ok {
		t.Errorf("MultiError expected, but not received")
	}

	if counts.Before != 1 {
		t.Errorf("Before() not executed when expected")
	}

	if counts.SubCommand != 0 {
		t.Errorf("Subcommand executed when NOT expected")
	}
}

func TestApp_AfterFunc(t *testing.T) {
	counts := &opCounts{}
	afterError := fmt.Errorf("fail")
	var err error

	app := NewApp()

	app.After = func(c *Context) error {
		counts.Total++
		counts.After = counts.Total
		s := c.String("opt")
		if s == "fail" {
			return afterError
		}

		return nil
	}

	app.Commands = []Command{
		{
			Name: "sub",
			Action: func(c *Context) error {
				counts.Total++
				counts.SubCommand = counts.Total
				return nil
			},
		},
	}

	app.Flags = []Flag{
		StringFlag{Name: "opt"},
	}

	// run with the After() func succeeding
	err = app.Run([]string{"command", "--opt", "succeed", "sub"})

	if err != nil {
		t.Fatalf("Run error: %s", err)
	}

	if counts.After != 2 {
		t.Errorf("After() not executed when expected")
	}

	if counts.SubCommand != 1 {
		t.Errorf("Subcommand not executed when expected")
	}

	// reset
	counts = &opCounts{}

	// run with the Before() func failing
	err = app.Run([]string{"command", "--opt", "fail", "sub"})

	// should be the same error produced by the Before func
	if err != afterError {
		t.Errorf("Run error expected, but not received")
	}

	if counts.After != 2 {
		t.Errorf("After() not executed when expected")
	}

	if counts.SubCommand != 1 {
		t.Errorf("Subcommand not executed when expected")
	}
}

func TestAppNoHelpFlag(t *testing.T) {
	oldFlag := HelpFlag
	defer func() {
		HelpFlag = oldFlag
	}()

	HelpFlag = BoolFlag{}

	app := NewApp()
	app.Writer = ioutil.Discard
	err := app.Run([]string{"test", "-h"})

	if err != flag.ErrHelp {
		t.Errorf("expected error about missing help flag, but got: %s (%T)", err, err)
	}
}

func TestRequiredFlagAppRunBehavior(t *testing.T) {
	tdata := []struct {
		testCase        string
		appFlags        []Flag
		appRunInput     []string
		appCommands     []Command
		expectedAnError bool
	}{
		// assertion: empty input, when a required flag is present, errors
		{
			testCase:        "error_case_empty_input_with_required_flag_on_app",
			appRunInput:     []string{"myCLI"},
			appFlags:        []Flag{StringFlag{Name: "requiredFlag", Required: true}},
			expectedAnError: true,
		},
		{
			testCase:    "error_case_empty_input_with_required_flag_on_command",
			appRunInput: []string{"myCLI", "myCommand"},
			appCommands: []Command{{
				Name:  "myCommand",
				Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}},
			}},
			expectedAnError: true,
		},
		{
			testCase:    "error_case_empty_input_with_required_flag_on_subcommand",
			appRunInput: []string{"myCLI", "myCommand", "mySubCommand"},
			appCommands: []Command{{
				Name: "myCommand",
				Subcommands: []Command{{
					Name:  "mySubCommand",
					Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}},
				}},
			}},
			expectedAnError: true,
		},
		// assertion: inputing --help, when a required flag is present, does not error
		{
			testCase:    "valid_case_help_input_with_required_flag_on_app",
			appRunInput: []string{"myCLI", "--help"},
			appFlags:    []Flag{StringFlag{Name: "requiredFlag", Required: true}},
		},
		{
			testCase:    "valid_case_help_input_with_required_flag_on_command",
			appRunInput: []string{"myCLI", "myCommand", "--help"},
			appCommands: []Command{{
				Name:  "myCommand",
				Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}},
			}},
		},
		{
			testCase:    "valid_case_help_input_with_required_flag_on_subcommand",
			appRunInput: []string{"myCLI", "myCommand", "mySubCommand", "--help"},
			appCommands: []Command{{
				Name: "myCommand",
				Subcommands: []Command{{
					Name:  "mySubCommand",
					Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}},
				}},
			}},
		},
		// assertion: giving optional input, when a required flag is present, errors
		{
			testCase:        "error_case_optional_input_with_required_flag_on_app",
			appRunInput:     []string{"myCLI", "--optional", "cats"},
			appFlags:        []Flag{StringFlag{Name: "requiredFlag", Required: true}, StringFlag{Name: "optional"}},
			expectedAnError: true,
		},
		{
			testCase:    "error_case_optional_input_with_required_flag_on_command",
			appRunInput: []string{"myCLI", "myCommand", "--optional", "cats"},
			appCommands: []Command{{
				Name:  "myCommand",
				Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}, StringFlag{Name: "optional"}},
			}},
			expectedAnError: true,
		},
		{
			testCase:    "error_case_optional_input_with_required_flag_on_subcommand",
			appRunInput: []string{"myCLI", "myCommand", "mySubCommand", "--optional", "cats"},
			appCommands: []Command{{
				Name: "myCommand",
				Subcommands: []Command{{
					Name:  "mySubCommand",
					Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}, StringFlag{Name: "optional"}},
				}},
			}},
			expectedAnError: true,
		},
		// assertion: when a required flag is present, inputting that required flag does not error
		{
			testCase:    "valid_case_required_flag_input_on_app",
			appRunInput: []string{"myCLI", "--requiredFlag", "cats"},
			appFlags:    []Flag{StringFlag{Name: "requiredFlag", Required: true}},
		},
		{
			testCase:    "valid_case_required_flag_input_on_command",
			appRunInput: []string{"myCLI", "myCommand", "--requiredFlag", "cats"},
			appCommands: []Command{{
				Name:  "myCommand",
				Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}},
			}},
		},
		{
			testCase:    "valid_case_required_flag_input_on_subcommand",
			appRunInput: []string{"myCLI", "myCommand", "mySubCommand", "--requiredFlag", "cats"},
			appCommands: []Command{{
				Name: "myCommand",
				Subcommands: []Command{{
					Name:  "mySubCommand",
					Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}},
				}},
			}},
		},
	}
	for _, test := range tdata {
		t.Run(test.testCase, func(t *testing.T) {
			// setup
			app := NewApp()
			app.Flags = test.appFlags
			app.Commands = test.appCommands

			// logic under test
			err := app.Run(test.appRunInput)

			// assertions
			if test.expectedAnError && err == nil {
				t.Errorf("expected an error, but there was none")
			}
			if _, ok := err.(requiredFlagsErr); test.expectedAnError && !ok {
				t.Errorf("expected a requiredFlagsErr, but got: %s", err)
			}
			if !test.expectedAnError && err != nil {
				t.Errorf("did not expected an error, but there was one: %s", err)
			}
		})
	}
}

func TestAppHelpPrinter(t *testing.T) {
	oldPrinter := HelpPrinter
	defer func() {
		HelpPrinter = oldPrinter
	}()

	var wasCalled = false
	HelpPrinter = func(w io.Writer, template string, data interface{}) {
		wasCalled = true
	}

	app := NewApp()
	_ = app.Run([]string{"-h"})

	if wasCalled == false {
		t.Errorf("Help printer expected to be called, but was not")
	}
}

func TestApp_VersionPrinter(t *testing.T) {
	oldPrinter := VersionPrinter
	defer func() {
		VersionPrinter = oldPrinter
	}()

	var wasCalled = false
	VersionPrinter = func(c *Context) {
		wasCalled = true
	}

	app := NewApp()
	ctx := NewContext(app, nil, nil)
	ShowVersion(ctx)

	if wasCalled == false {
		t.Errorf("Version printer expected to be called, but was not")
	}
}

func TestApp_CommandNotFound(t *testing.T) {
	counts := &opCounts{}
	app := NewApp()

	app.CommandNotFound = func(c *Context, command string) {
		counts.Total++
		counts.CommandNotFound = counts.Total
	}

	app.Commands = []Command{
		{
			Name: "bar",
			Action: func(c *Context) error {
				counts.Total++
				counts.SubCommand = counts.Total
				return nil
			},
		},
	}

	_ = app.Run([]string{"command", "foo"})

	expect(t, counts.CommandNotFound, 1)
	expect(t, counts.SubCommand, 0)
	expect(t, counts.Total, 1)
}

func TestApp_OrderOfOperations(t *testing.T) {
	counts := &opCounts{}

	resetCounts := func() { counts = &opCounts{} }

	app := NewApp()
	app.EnableBashCompletion = true
	app.BashComplete = func(c *Context) {
		counts.Total++
		counts.BashComplete = counts.Total
	}

	app.OnUsageError = func(c *Context, err error, isSubcommand bool) error {
		counts.Total++
		counts.OnUsageError = counts.Total
		return errors.New("hay OnUsageError")
	}

	beforeNoError := func(c *Context) error {
		counts.Total++
		counts.Before = counts.Total
		return nil
	}

	beforeError := func(c *Context) error {
		counts.Total++
		counts.Before = counts.Total
		return errors.New("hay Before")
	}

	app.Before = beforeNoError
	app.CommandNotFound = func(c *Context, command string) {
		counts.Total++
		counts.CommandNotFound = counts.Total
	}

	afterNoError := func(c *Context) error {
		counts.Total++
		counts.After = counts.Total
		return nil
	}

	afterError := func(c *Context) error {
		counts.Total++
		counts.After = counts.Total
		return errors.New("hay After")
	}

	app.After = afterNoError
	app.Commands = []Command{
		{
			Name: "bar",
			Action: func(c *Context) error {
				counts.Total++
				counts.SubCommand = counts.Total
				return nil
			},
		},
	}

	app.Action = func(c *Context) error {
		counts.Total++
		counts.Action = counts.Total
		return nil
	}

	_ = app.Run([]string{"command", "--nope"})
	expect(t, counts.OnUsageError, 1)
	expect(t, counts.Total, 1)

	resetCounts()

	_ = app.Run([]string{"command", "--generate-bash-completion"})
	expect(t, counts.BashComplete, 1)
	expect(t, counts.Total, 1)

	resetCounts()

	oldOnUsageError := app.OnUsageError
	app.OnUsageError = nil
	_ = app.Run([]string{"command", "--nope"})
	expect(t, counts.Total, 0)
	app.OnUsageError = oldOnUsageError

	resetCounts()

	_ = app.Run([]string{"command", "foo"})
	expect(t, counts.OnUsageError, 0)
	expect(t, counts.Before, 1)
	expect(t, counts.CommandNotFound, 0)
	expect(t, counts.Action, 2)
	expect(t, counts.After, 3)
	expect(t, counts.Total, 3)

	resetCounts()

	app.Before = beforeError
	_ = app.Run([]string{"command", "bar"})
	expect(t, counts.OnUsageError, 0)
	expect(t, counts.Before, 1)
	expect(t, counts.After, 2)
	expect(t, counts.Total, 2)
	app.Before = beforeNoError

	resetCounts()

	app.After = nil
	_ = app.Run([]string{"command", "bar"})
	expect(t, counts.OnUsageError, 0)
	expect(t, counts.Before, 1)
	expect(t, counts.SubCommand, 2)
	expect(t, counts.Total, 2)
	app.After = afterNoError

	resetCounts()

	app.After = afterError
	err := app.Run([]string{"command", "bar"})
	if err == nil {
		t.Fatalf("expected a non-nil error")
	}
	expect(t, counts.OnUsageError, 0)
	expect(t, counts.Before, 1)
	expect(t, counts.SubCommand, 2)
	expect(t, counts.After, 3)
	expect(t, counts.Total, 3)
	app.After = afterNoError

	resetCounts()

	oldCommands := app.Commands
	app.Commands = nil
	_ = app.Run([]string{"command"})
	expect(t, counts.OnUsageError, 0)
	expect(t, counts.Before, 1)
	expect(t, counts.Action, 2)
	expect(t, counts.After, 3)
	expect(t, counts.Total, 3)
	app.Commands = oldCommands
}

func TestApp_Run_CommandWithSubcommandHasHelpTopic(t *testing.T) {
	var subcommandHelpTopics = [][]string{
		{"command", "foo", "--help"},
		{"command", "foo", "-h"},
		{"command", "foo", "help"},
	}

	for _, flagSet := range subcommandHelpTopics {
		t.Logf("==> checking with flags %v", flagSet)

		app := NewApp()
		buf := new(bytes.Buffer)
		app.Writer = buf

		subCmdBar := Command{
			Name:  "bar",
			Usage: "does bar things",
		}
		subCmdBaz := Command{
			Name:  "baz",
			Usage: "does baz things",
		}
		cmd := Command{
			Name:        "foo",
			Description: "descriptive wall of text about how it does foo things",
			Subcommands: []Command{subCmdBar, subCmdBaz},
			Action:      func(c *Context) error { return nil },
		}

		app.Commands = []Command{cmd}
		err := app.Run(flagSet)

		if err != nil {
			t.Error(err)
		}

		output := buf.String()
		t.Logf("output: %q\n", buf.Bytes())

		if strings.Contains(output, "No help topic for") {
			t.Errorf("expect a help topic, got none: \n%q", output)
		}

		for _, shouldContain := range []string{
			cmd.Name, cmd.Description,
			subCmdBar.Name, subCmdBar.Usage,
			subCmdBaz.Name, subCmdBaz.Usage,
		} {
			if !strings.Contains(output, shouldContain) {
				t.Errorf("want help to contain %q, did not: \n%q", shouldContain, output)
			}
		}
	}
}

func TestApp_Run_SubcommandFullPath(t *testing.T) {
	app := NewApp()
	buf := new(bytes.Buffer)
	app.Writer = buf
	app.Name = "command"
	subCmd := Command{
		Name:  "bar",
		Usage: "does bar things",
	}
	cmd := Command{
		Name:        "foo",
		Description: "foo commands",
		Subcommands: []Command{subCmd},
	}
	app.Commands = []Command{cmd}

	err := app.Run([]string{"command", "foo", "bar", "--help"})
	if err != nil {
		t.Error(err)
	}

	output := buf.String()
	if !strings.Contains(output, "command foo bar - does bar things") {
		t.Errorf("expected full path to subcommand: %s", output)
	}
	if !strings.Contains(output, "command foo bar [arguments...]") {
		t.Errorf("expected full path to subcommand: %s", output)
	}
}

func TestApp_Run_SubcommandHelpName(t *testing.T) {
	app := NewApp()
	buf := new(bytes.Buffer)
	app.Writer = buf
	app.Name = "command"
	subCmd := Command{
		Name:     "bar",
		HelpName: "custom",
		Usage:    "does bar things",
	}
	cmd := Command{
		Name:        "foo",
		Description: "foo commands",
		Subcommands: []Command{subCmd},
	}
	app.Commands = []Command{cmd}

	err := app.Run([]string{"command", "foo", "bar", "--help"})
	if err != nil {
		t.Error(err)
	}

	output := buf.String()
	if !strings.Contains(output, "custom - does bar things") {
		t.Errorf("expected HelpName for subcommand: %s", output)
	}
	if !strings.Contains(output, "custom [arguments...]") {
		t.Errorf("expected HelpName to subcommand: %s", output)
	}
}

func TestApp_Run_CommandHelpName(t *testing.T) {
	app := NewApp()
	buf := new(bytes.Buffer)
	app.Writer = buf
	app.Name = "command"
	subCmd := Command{
		Name:  "bar",
		Usage: "does bar things",
	}
	cmd := Command{
		Name:        "foo",
		HelpName:    "custom",
		Description: "foo commands",
		Subcommands: []Command{subCmd},
	}
	app.Commands = []Command{cmd}

	err := app.Run([]string{"command", "foo", "bar", "--help"})
	if err != nil {
		t.Error(err)
	}

	output := buf.String()
	if !strings.Contains(output, "command foo bar - does bar things") {
		t.Errorf("expected full path to subcommand: %s", output)
	}
	if !strings.Contains(output, "command foo bar [arguments...]") {
		t.Errorf("expected full path to subcommand: %s", output)
	}
}

func TestApp_Run_CommandSubcommandHelpName(t *testing.T) {
	app := NewApp()
	buf := new(bytes.Buffer)
	app.Writer = buf
	app.Name = "base"
	subCmd := Command{
		Name:     "bar",
		HelpName: "custom",
		Usage:    "does bar things",
	}
	cmd := Command{
		Name:        "foo",
		Description: "foo commands",
		Subcommands: []Command{subCmd},
	}
	app.Commands = []Command{cmd}

	err := app.Run([]string{"command", "foo", "--help"})
	if err != nil {
		t.Error(err)
	}

	output := buf.String()
	if !strings.Contains(output, "base foo - foo commands") {
		t.Errorf("expected full path to subcommand: %s", output)
	}
	if !strings.Contains(output, "base foo command [command options] [arguments...]") {
		t.Errorf("expected full path to subcommand: %s", output)
	}
}

func TestApp_Run_Help(t *testing.T) {
	var helpArguments = [][]string{{"boom", "--help"}, {"boom", "-h"}, {"boom", "help"}}

	for _, args := range helpArguments {
		buf := new(bytes.Buffer)

		t.Logf("==> checking with arguments %v", args)

		app := NewApp()
		app.Name = "boom"
		app.Usage = "make an explosive entrance"
		app.Writer = buf
		app.Action = func(c *Context) error {
			buf.WriteString("boom I say!")
			return nil
		}

		err := app.Run(args)
		if err != nil {
			t.Error(err)
		}

		output := buf.String()
		t.Logf("output: %q\n", buf.Bytes())

		if !strings.Contains(output, "boom - make an explosive entrance") {
			t.Errorf("want help to contain %q, did not: \n%q", "boom - make an explosive entrance", output)
		}
	}
}

func TestApp_Run_Version(t *testing.T) {
	var versionArguments = [][]string{{"boom", "--version"}, {"boom", "-v"}}

	for _, args := range versionArguments {
		buf := new(bytes.Buffer)

		t.Logf("==> checking with arguments %v", args)

		app := NewApp()
		app.Name = "boom"
		app.Usage = "make an explosive entrance"
		app.Version = "0.1.0"
		app.Writer = buf
		app.Action = func(c *Context) error {
			buf.WriteString("boom I say!")
			return nil
		}

		err := app.Run(args)
		if err != nil {
			t.Error(err)
		}

		output := buf.String()
		t.Logf("output: %q\n", buf.Bytes())

		if !strings.Contains(output, "0.1.0") {
			t.Errorf("want version to contain %q, did not: \n%q", "0.1.0", output)
		}
	}
}

func TestApp_Run_Categories(t *testing.T) {
	app := NewApp()
	app.Name = "categories"
	app.HideHelp = true
	app.Commands = []Command{
		{
			Name:     "command1",
			Category: "1",
		},
		{
			Name:     "command2",
			Category: "1",
		},
		{
			Name:     "command3",
			Category: "2",
		},
	}
	buf := new(bytes.Buffer)
	app.Writer = buf

	_ = app.Run([]string{"categories"})

	expect := CommandCategories{
		&CommandCategory{
			Name: "1",
			Commands: []Command{
				app.Commands[0],
				app.Commands[1],
			},
		},
		&CommandCategory{
			Name: "2",
			Commands: []Command{
				app.Commands[2],
			},
		},
	}
	if !reflect.DeepEqual(app.Categories(), expect) {
		t.Fatalf("expected categories %#v, to equal %#v", app.Categories(), expect)
	}

	output := buf.String()
	t.Logf("output: %q\n", buf.Bytes())

	if !strings.Contains(output, "1:\n     command1") {
		t.Errorf("want buffer to include category %q, did not: \n%q", "1:\n     command1", output)
	}
}

func TestApp_VisibleCategories(t *testing.T) {
	app := NewApp()
	app.Name = "visible-categories"
	app.HideHelp = true
	app.Commands = []Command{
		{
			Name:     "command1",
			Category: "1",
			HelpName: "foo command1",
			Hidden:   true,
		},
		{
			Name:     "command2",
			Category: "2",
			HelpName: "foo command2",
		},
		{
			Name:     "command3",
			Category: "3",
			HelpName: "foo command3",
		},
	}

	expected := []*CommandCategory{
		{
			Name: "2",
			Commands: []Command{
				app.Commands[1],
			},
		},
		{
			Name: "3",
			Commands: []Command{
				app.Commands[2],
			},
		},
	}

	app.Setup()
	expect(t, expected, app.VisibleCategories())

	app = NewApp()
	app.Name = "visible-categories"
	app.HideHelp = true
	app.Commands = []Command{
		{
			Name:     "command1",
			Category: "1",
			HelpName: "foo command1",
			Hidden:   true,
		},
		{
			Name:     "command2",
			Category: "2",
			HelpName: "foo command2",
			Hidden:   true,
		},
		{
			Name:     "command3",
			Category: "3",
			HelpName: "foo command3",
		},
	}

	expected = []*CommandCategory{
		{
			Name: "3",
			Commands: []Command{
				app.Commands[2],
			},
		},
	}

	app.Setup()
	expect(t, expected, app.VisibleCategories())

	app = NewApp()
	app.Name = "visible-categories"
	app.HideHelp = true
	app.Commands = []Command{
		{
			Name:     "command1",
			Category: "1",
			HelpName: "foo command1",
			Hidden:   true,
		},
		{
			Name:     "command2",
			Category: "2",
			HelpName: "foo command2",
			Hidden:   true,
		},
		{
			Name:     "command3",
			Category: "3",
			HelpName: "foo command3",
			Hidden:   true,
		},
	}

	expected = []*CommandCategory{}

	app.Setup()
	expect(t, expected, app.VisibleCategories())
}

func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) {
	app := NewApp()
	app.Action = func(c *Context) error { return nil }
	app.Before = func(c *Context) error { return fmt.Errorf("before error") }
	app.After = func(c *Context) error { return fmt.Errorf("after error") }

	err := app.Run([]string{"foo"})
	if err == nil {
		t.Fatalf("expected to receive error from Run, got none")
	}

	if !strings.Contains(err.Error(), "before error") {
		t.Errorf("expected text of error from Before method, but got none in \"%v\"", err)
	}
	if !strings.Contains(err.Error(), "after error") {
		t.Errorf("expected text of error from After method, but got none in \"%v\"", err)
	}
}

func TestApp_Run_SubcommandDoesNotOverwriteErrorFromBefore(t *testing.T) {
	app := NewApp()
	app.Commands = []Command{
		{
			Subcommands: []Command{
				{
					Name: "sub",
				},
			},
			Name:   "bar",
			Before: func(c *Context) error { return fmt.Errorf("before error") },
			After:  func(c *Context) error { return fmt.Errorf("after error") },
		},
	}

	err := app.Run([]string{"foo", "bar"})
	if err == nil {
		t.Fatalf("expected to receive error from Run, got none")
	}

	if !strings.Contains(err.Error(), "before error") {
		t.Errorf("expected text of error from Before method, but got none in \"%v\"", err)
	}
	if !strings.Contains(err.Error(), "after error") {
		t.Errorf("expected text of error from After method, but got none in \"%v\"", err)
	}
}

func TestApp_OnUsageError_WithWrongFlagValue(t *testing.T) {
	app := NewApp()
	app.Flags = []Flag{
		IntFlag{Name: "flag"},
	}
	app.OnUsageError = func(c *Context, err error, isSubcommand bool) error {
		if isSubcommand {
			t.Errorf("Expect no subcommand")
		}
		if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") {
			t.Errorf("Expect an invalid value error, but got \"%v\"", err)
		}
		return errors.New("intercepted: " + err.Error())
	}
	app.Commands = []Command{
		{
			Name: "bar",
		},
	}

	err := app.Run([]string{"foo", "--flag=wrong"})
	if err == nil {
		t.Fatalf("expected to receive error from Run, got none")
	}

	if !strings.HasPrefix(err.Error(), "intercepted: invalid value") {
		t.Errorf("Expect an intercepted error, but got \"%v\"", err)
	}
}

func TestApp_OnUsageError_WithWrongFlagValue_ForSubcommand(t *testing.T) {
	app := NewApp()
	app.Flags = []Flag{
		IntFlag{Name: "flag"},
	}
	app.OnUsageError = func(c *Context, err error, isSubcommand bool) error {
		if isSubcommand {
			t.Errorf("Expect subcommand")
		}
		if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") {
			t.Errorf("Expect an invalid value error, but got \"%v\"", err)
		}
		return errors.New("intercepted: " + err.Error())
	}
	app.Commands = []Command{
		{
			Name: "bar",
		},
	}

	err := app.Run([]string{"foo", "--flag=wrong", "bar"})
	if err == nil {
		t.Fatalf("expected to receive error from Run, got none")
	}

	if !strings.HasPrefix(err.Error(), "intercepted: invalid value") {
		t.Errorf("Expect an intercepted error, but got \"%v\"", err)
	}
}

// A custom flag that conforms to the relevant interfaces, but has none of the
// fields that the other flag types do.
type customBoolFlag struct {
	Nombre string
}

// Don't use the normal FlagStringer
func (c *customBoolFlag) String() string {
	return "***" + c.Nombre + "***"
}

func (c *customBoolFlag) GetName() string {
	return c.Nombre
}

func (c *customBoolFlag) TakesValue() bool {
	return false
}

func (c *customBoolFlag) GetValue() string {
	return "value"
}

func (c *customBoolFlag) GetUsage() string {
	return "usage"
}

func (c *customBoolFlag) Apply(set *flag.FlagSet) {
	set.String(c.Nombre, c.Nombre, "")
}

func TestCustomFlagsUnused(t *testing.T) {
	app := NewApp()
	app.Flags = []Flag{&customBoolFlag{"custom"}}

	err := app.Run([]string{"foo"})
	if err != nil {
		t.Errorf("Run returned unexpected error: %v", err)
	}
}

func TestCustomFlagsUsed(t *testing.T) {
	app := NewApp()
	app.Flags = []Flag{&customBoolFlag{"custom"}}

	err := app.Run([]string{"foo", "--custom=bar"})
	if err != nil {
		t.Errorf("Run returned unexpected error: %v", err)
	}
}

func TestCustomHelpVersionFlags(t *testing.T) {
	app := NewApp()

	// Be sure to reset the global flags
	defer func(helpFlag Flag, versionFlag Flag) {
		HelpFlag = helpFlag
		VersionFlag = versionFlag
	}(HelpFlag, VersionFlag)

	HelpFlag = &customBoolFlag{"help-custom"}
	VersionFlag = &customBoolFlag{"version-custom"}

	err := app.Run([]string{"foo", "--help-custom=bar"})
	if err != nil {
		t.Errorf("Run returned unexpected error: %v", err)
	}
}

func TestHandleAction_WithNonFuncAction(t *testing.T) {
	app := NewApp()
	app.Action = 42
	fs, err := flagSet(app.Name, app.Flags)
	if err != nil {
		t.Errorf("error creating FlagSet: %s", err)
	}
	err = HandleAction(app.Action, NewContext(app, fs, nil))

	if err == nil {
		t.Fatalf("expected to receive error from Run, got none")
	}

	exitErr, ok := err.(*ExitError)

	if !ok {
		t.Fatalf("expected to receive a *ExitError")
	}

	if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action type.") {
		t.Fatalf("expected an unknown Action error, but got: %v", exitErr.Error())
	}

	if exitErr.ExitCode() != 2 {
		t.Fatalf("expected error exit code to be 2, but got: %v", exitErr.ExitCode())
	}
}

func TestHandleAction_WithInvalidFuncSignature(t *testing.T) {
	app := NewApp()
	app.Action = func() string { return "" }
	fs, err := flagSet(app.Name, app.Flags)
	if err != nil {
		t.Errorf("error creating FlagSet: %s", err)
	}
	err = HandleAction(app.Action, NewContext(app, fs, nil))

	if err == nil {
		t.Fatalf("expected to receive error from Run, got none")
	}

	exitErr, ok := err.(*ExitError)

	if !ok {
		t.Fatalf("expected to receive a *ExitError")
	}

	if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action type") {
		t.Fatalf("expected an unknown Action error, but got: %v", exitErr.Error())
	}

	if exitErr.ExitCode() != 2 {
		t.Fatalf("expected error exit code to be 2, but got: %v", exitErr.ExitCode())
	}
}

func TestHandleAction_WithInvalidFuncReturnSignature(t *testing.T) {
	app := NewApp()
	app.Action = func(_ *Context) (int, error) { return 0, nil }
	fs, err := flagSet(app.Name, app.Flags)
	if err != nil {
		t.Errorf("error creating FlagSet: %s", err)
	}
	err = HandleAction(app.Action, NewContext(app, fs, nil))

	if err == nil {
		t.Fatalf("expected to receive error from Run, got none")
	}

	exitErr, ok := err.(*ExitError)

	if !ok {
		t.Fatalf("expected to receive a *ExitError")
	}

	if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action type") {
		t.Fatalf("expected an invalid Action signature error, but got: %v", exitErr.Error())
	}

	if exitErr.ExitCode() != 2 {
		t.Fatalf("expected error exit code to be 2, but got: %v", exitErr.ExitCode())
	}
}

func TestHandleExitCoder_Default(t *testing.T) {
	app := NewApp()
	fs, err := flagSet(app.Name, app.Flags)
	if err != nil {
		t.Errorf("error creating FlagSet: %s", err)
	}

	ctx := NewContext(app, fs, nil)
	app.handleExitCoder(ctx, NewExitError("Default Behavior Error", 42))

	output := fakeErrWriter.String()
	if !strings.Contains(output, "Default") {
		t.Fatalf("Expected Default Behavior from Error Handler but got: %s", output)
	}
}

func TestHandleExitCoder_Custom(t *testing.T) {
	app := NewApp()
	fs, err := flagSet(app.Name, app.Flags)
	if err != nil {
		t.Errorf("error creating FlagSet: %s", err)
	}

	app.ExitErrHandler = func(_ *Context, _ error) {
		_, _ = fmt.Fprintln(ErrWriter, "I'm a Custom error handler, I print what I want!")
	}

	ctx := NewContext(app, fs, nil)
	app.handleExitCoder(ctx, NewExitError("Default Behavior Error", 42))

	output := fakeErrWriter.String()
	if !strings.Contains(output, "Custom") {
		t.Fatalf("Expected Custom Behavior from Error Handler but got: %s", output)
	}
}

func TestHandleAction_WithUnknownPanic(t *testing.T) {
	defer func() { refute(t, recover(), nil) }()

	var fn ActionFunc

	app := NewApp()
	app.Action = func(ctx *Context) error {
		_ = fn(ctx)
		return nil
	}
	fs, err := flagSet(app.Name, app.Flags)
	if err != nil {
		t.Errorf("error creating FlagSet: %s", err)
	}
	_ = HandleAction(app.Action, NewContext(app, fs, nil))
}

func TestShellCompletionForIncompleteFlags(t *testing.T) {
	app := NewApp()
	app.Flags = []Flag{
		IntFlag{
			Name: "test-completion",
		},
	}
	app.EnableBashCompletion = true
	app.BashComplete = func(ctx *Context) {
		for _, command := range ctx.App.Commands {
			if command.Hidden {
				continue
			}

			for _, name := range command.Names() {
				_, _ = fmt.Fprintln(ctx.App.Writer, name)
			}
		}

		for _, f := range ctx.App.Flags {
			for _, name := range strings.Split(f.GetName(), ",") {
				if name == BashCompletionFlag.GetName() {
					continue
				}

				switch name = strings.TrimSpace(name); len(name) {
				case 0:
				case 1:
					_, _ = fmt.Fprintln(ctx.App.Writer, "-"+name)
				default:
					_, _ = fmt.Fprintln(ctx.App.Writer, "--"+name)
				}
			}
		}
	}
	app.Action = func(ctx *Context) error {
		return fmt.Errorf("should not get here")
	}
	err := app.Run([]string{"", "--test-completion", "--" + BashCompletionFlag.GetName()})
	if err != nil {
		t.Errorf("app should not return an error: %s", err)
	}
}

func TestHandleActionActuallyWorksWithActions(t *testing.T) {
	var f ActionFunc
	called := false
	f = func(c *Context) error {
		called = true
		return nil
	}

	err := HandleAction(f, nil)

	if err != nil {
		t.Errorf("Should not have errored: %v", err)
	}

	if !called {
		t.Errorf("Function was not called")
	}
}

func TestWhenExitSubCommandWithCodeThenAppQuitUnexpectedly(t *testing.T) {
	testCode := 104

	app := NewApp()
	app.Commands = []Command{
		Command{
			Name: "cmd",
			Subcommands: []Command{
				Command{
					Name: "subcmd",
					Action: func(c *Context) error {
						return NewExitError("exit error", testCode)
					},
				},
			},
		},
	}

	// set user function as ExitErrHandler
	var exitCodeFromExitErrHandler int
	app.ExitErrHandler = func(c *Context, err error) {
		if exitErr, ok := err.(ExitCoder); ok {
			t.Log(exitErr)
			exitCodeFromExitErrHandler = exitErr.ExitCode()
		}
	}

	// keep and restore original OsExiter
	origExiter := OsExiter
	defer func() {
		OsExiter = origExiter
	}()

	// set user function as OsExiter
	var exitCodeFromOsExiter int
	OsExiter = func(exitCode int) {
		exitCodeFromOsExiter = exitCode
	}

	app.Run([]string{
		"myapp",
		"cmd",
		"subcmd",
	})

	if exitCodeFromOsExiter != 0 {
		t.Errorf("exitCodeFromOsExiter should not change, but its value is %v", exitCodeFromOsExiter)
	}

	if exitCodeFromExitErrHandler != testCode {
		t.Errorf("exitCodeFromOsExiter valeu should be %v, but its value is %v", testCode, exitCodeFromExitErrHandler)
	}
}
