package cmd

import (
	"flag"
	"io/ioutil"
	"os"
	"testing"

	"github.com/prysmaticlabs/prysm/testing/require"
	"github.com/urfave/cli/v2"
)

func TestLoadFlagsFromConfig(t *testing.T) {
	app := cli.App{}
	set := flag.NewFlagSet("test", 0)
	context := cli.NewContext(&app, set, nil)

	require.NoError(t, ioutil.WriteFile("flags_test.yaml", []byte("testflag: 100"), 0666))

	require.NoError(t, set.Parse([]string{"test-command", "--" + ConfigFileFlag.Name, "flags_test.yaml"}))
	command := &cli.Command{
		Name: "test-command",
		Flags: WrapFlags([]cli.Flag{
			&cli.StringFlag{
				Name: ConfigFileFlag.Name,
			},
			&cli.IntFlag{
				Name:  "testflag",
				Value: 0,
			},
		}),
		Before: func(cliCtx *cli.Context) error {
			return LoadFlagsFromConfig(cliCtx, cliCtx.Command.Flags)
		},
		Action: func(cliCtx *cli.Context) error {
			require.Equal(t, 100, cliCtx.Int("testflag"))
			return nil
		},
	}
	require.NoError(t, command.Run(context))
	require.NoError(t, os.Remove("flags_test.yaml"))
}

func TestValidateNoArgs(t *testing.T) {
	app := &cli.App{
		Before: ValidateNoArgs,
		Action: func(c *cli.Context) error {
			return nil
		},
		Flags: []cli.Flag{
			&cli.StringFlag{
				Name: "foo",
			},
		},
		Commands: []*cli.Command{
			{
				Name: "bar",
				Subcommands: []*cli.Command{
					{
						Name: "subComm1",
						Subcommands: []*cli.Command{
							{
								Name: "subComm3",
							},
						},
					},
					{
						Name: "subComm2",
						Subcommands: []*cli.Command{
							{
								Name: "subComm4",
							},
						},
					},
				},
			},
		},
	}

	// It should not work with a bogus argument
	err := app.Run([]string{"command", "foo"})
	require.ErrorContains(t, "unrecognized argument: foo", err)
	// It should work with registered flags
	err = app.Run([]string{"command", "--foo=bar"})
	require.NoError(t, err)
	// It should work with subcommands.
	err = app.Run([]string{"command", "bar"})
	require.NoError(t, err)
	// It should fail on unregistered flag (default logic in urfave/cli).
	err = app.Run([]string{"command", "bar", "--baz"})
	require.ErrorContains(t, "flag provided but not defined", err)

	// Handle Nested Subcommands

	err = app.Run([]string{"command", "bar", "subComm1"})
	require.NoError(t, err)

	err = app.Run([]string{"command", "bar", "subComm2"})
	require.NoError(t, err)

	// Should fail from unknown subcommands.
	err = app.Run([]string{"command", "bar", "subComm3"})
	require.ErrorContains(t, "unrecognized argument: subComm3", err)

	err = app.Run([]string{"command", "bar", "subComm4"})
	require.ErrorContains(t, "unrecognized argument: subComm4", err)

	// Should fail with invalid double nested subcommands.
	err = app.Run([]string{"command", "bar", "subComm1", "subComm2"})
	require.ErrorContains(t, "unrecognized argument: subComm2", err)

	err = app.Run([]string{"command", "bar", "subComm1", "subComm4"})
	require.ErrorContains(t, "unrecognized argument: subComm4", err)

	err = app.Run([]string{"command", "bar", "subComm2", "subComm1"})
	require.ErrorContains(t, "unrecognized argument: subComm1", err)

	err = app.Run([]string{"command", "bar", "subComm2", "subComm3"})
	require.ErrorContains(t, "unrecognized argument: subComm3", err)

	// Should pass with correct nested double subcommands.
	err = app.Run([]string{"command", "bar", "subComm1", "subComm3"})
	require.NoError(t, err)

	err = app.Run([]string{"command", "bar", "subComm2", "subComm4"})
	require.NoError(t, err)
}

func TestValidateNoArgs_SubcommandFlags(t *testing.T) {
	app := &cli.App{
		Before: ValidateNoArgs,
		Action: func(c *cli.Context) error {
			return nil
		},
		Flags: []cli.Flag{
			&cli.StringFlag{
				Name: "foo",
			},
		},
		Commands: []*cli.Command{
			{
				Name: "bar",
				Subcommands: []*cli.Command{
					{
						Name: "subComm1",
						Subcommands: []*cli.Command{
							{
								Name: "subComm3",
							},
						},
						Flags: []cli.Flag{
							&cli.StringFlag{
								Name: "barfoo2",
							},
							&cli.BoolFlag{
								Name: "barfoo99",
							},
						},
					},
					{
						Name: "subComm2",
						Subcommands: []*cli.Command{
							{
								Name: "subComm4",
							},
						},
						Flags: []cli.Flag{
							&cli.StringFlag{
								Name: "barfoo3",
							},
							&cli.BoolFlag{
								Name: "barfoo100",
							},
						},
					},
				},
				Flags: []cli.Flag{
					&cli.StringFlag{
						Name: "barfoo1",
					},
				},
			},
		},
	}

	// It should not work with a bogus argument
	err := app.Run([]string{"command", "foo"})
	require.ErrorContains(t, "unrecognized argument: foo", err)
	// It should work with registered flags
	err = app.Run([]string{"command", "--foo=bar"})
	require.NoError(t, err)

	// It should work with registered flags with spaces.
	err = app.Run([]string{"command", "--foo", "bar"})
	require.NoError(t, err)

	// Handle Nested Subcommands and its flags

	err = app.Run([]string{"command", "bar", "--barfoo1=xyz"})
	require.NoError(t, err)

	err = app.Run([]string{"command", "bar", "--barfoo1", "xyz"})
	require.NoError(t, err)

	// Should pass with correct nested double subcommands.
	err = app.Run([]string{"command", "bar", "subComm1", "--barfoo2=xyz"})
	require.NoError(t, err)

	err = app.Run([]string{"command", "bar", "subComm1", "--barfoo2", "xyz"})
	require.NoError(t, err)

	err = app.Run([]string{"command", "bar", "subComm2", "--barfoo3=xyz"})
	require.NoError(t, err)

	err = app.Run([]string{"command", "bar", "subComm2", "--barfoo3", "xyz"})
	require.NoError(t, err)

	err = app.Run([]string{"command", "bar", "subComm2", "--barfoo3"})
	require.ErrorContains(t, "flag needs an argument", err)

	err = app.Run([]string{"command", "bar", "subComm1", "--barfoo99"})
	require.NoError(t, err)

	// Test edge case with boolean flags, as they do not require spaced arguments.
	app.CommandNotFound = func(context *cli.Context, s string) {
		require.Equal(t, "garbage", s)
	}
	err = app.Run([]string{"command", "bar", "subComm1", "--barfoo99", "garbage"})
	require.ErrorContains(t, "unrecognized argument: garbage", err)

	err = app.Run([]string{"command", "bar", "subComm1", "--barfoo99", "garbage", "subComm3"})
	require.ErrorContains(t, "unrecognized argument: garbage", err)

	err = app.Run([]string{"command", "bar", "subComm2", "--barfoo100", "garbage"})
	require.ErrorContains(t, "unrecognized argument: garbage", err)

	err = app.Run([]string{"command", "bar", "subComm2", "--barfoo100", "garbage", "subComm4"})
	require.ErrorContains(t, "unrecognized argument: garbage", err)
}
