// Copyright 2013-2022 The Cobra Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cobra

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

const (
	activeHelpMessage  = "This is an activeHelp message"
	activeHelpMessage2 = "This is the rest of the activeHelp message"
)

func TestActiveHelpAlone(t *testing.T) {
	rootCmd := &Command{
		Use: "root",
		Run: emptyRun,
	}

	activeHelpFunc := func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
		comps := AppendActiveHelp(nil, activeHelpMessage)
		return comps, ShellCompDirectiveDefault
	}

	// Test that activeHelp can be added to a root command
	rootCmd.ValidArgsFunction = activeHelpFunc

	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "")
	if err != nil {
		t.Errorf("Unexpected error: %v", err)
	}

	expected := strings.Join([]string{
		fmt.Sprintf("%s%s", activeHelpMarker, activeHelpMessage),
		":0",
		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")

	if output != expected {
		t.Errorf("expected: %q, got: %q", expected, output)
	}

	rootCmd.ValidArgsFunction = nil

	// Test that activeHelp can be added to a child command
	childCmd := &Command{
		Use:   "thechild",
		Short: "The child command",
		Run:   emptyRun,
	}
	rootCmd.AddCommand(childCmd)

	childCmd.ValidArgsFunction = activeHelpFunc

	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "thechild", "")
	if err != nil {
		t.Errorf("Unexpected error: %v", err)
	}

	expected = strings.Join([]string{
		fmt.Sprintf("%s%s", activeHelpMarker, activeHelpMessage),
		":0",
		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")

	if output != expected {
		t.Errorf("expected: %q, got: %q", expected, output)
	}
}

func TestActiveHelpWithComps(t *testing.T) {
	rootCmd := &Command{
		Use: "root",
		Run: emptyRun,
	}

	childCmd := &Command{
		Use:   "thechild",
		Short: "The child command",
		Run:   emptyRun,
	}
	rootCmd.AddCommand(childCmd)

	// Test that activeHelp can be added following other completions
	childCmd.ValidArgsFunction = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
		comps := []string{"first", "second"}
		comps = AppendActiveHelp(comps, activeHelpMessage)
		return comps, ShellCompDirectiveDefault
	}

	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "thechild", "")
	if err != nil {
		t.Errorf("Unexpected error: %v", err)
	}

	expected := strings.Join([]string{
		"first",
		"second",
		fmt.Sprintf("%s%s", activeHelpMarker, activeHelpMessage),
		":0",
		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")

	if output != expected {
		t.Errorf("expected: %q, got: %q", expected, output)
	}

	// Test that activeHelp can be added preceding other completions
	childCmd.ValidArgsFunction = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
		var comps []string
		comps = AppendActiveHelp(comps, activeHelpMessage)
		comps = append(comps, []string{"first", "second"}...)
		return comps, ShellCompDirectiveDefault
	}

	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "thechild", "")
	if err != nil {
		t.Errorf("Unexpected error: %v", err)
	}

	expected = strings.Join([]string{
		fmt.Sprintf("%s%s", activeHelpMarker, activeHelpMessage),
		"first",
		"second",
		":0",
		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")

	if output != expected {
		t.Errorf("expected: %q, got: %q", expected, output)
	}

	// Test that activeHelp can be added interleaved with other completions
	childCmd.ValidArgsFunction = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
		comps := []string{"first"}
		comps = AppendActiveHelp(comps, activeHelpMessage)
		comps = append(comps, "second")
		return comps, ShellCompDirectiveDefault
	}

	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "thechild", "")
	if err != nil {
		t.Errorf("Unexpected error: %v", err)
	}

	expected = strings.Join([]string{
		"first",
		fmt.Sprintf("%s%s", activeHelpMarker, activeHelpMessage),
		"second",
		":0",
		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")

	if output != expected {
		t.Errorf("expected: %q, got: %q", expected, output)
	}
}

func TestMultiActiveHelp(t *testing.T) {
	rootCmd := &Command{
		Use: "root",
		Run: emptyRun,
	}

	childCmd := &Command{
		Use:   "thechild",
		Short: "The child command",
		Run:   emptyRun,
	}
	rootCmd.AddCommand(childCmd)

	// Test that multiple activeHelp message can be added
	childCmd.ValidArgsFunction = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
		comps := AppendActiveHelp(nil, activeHelpMessage)
		comps = AppendActiveHelp(comps, activeHelpMessage2)
		return comps, ShellCompDirectiveNoFileComp
	}

	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "thechild", "")
	if err != nil {
		t.Errorf("Unexpected error: %v", err)
	}

	expected := strings.Join([]string{
		fmt.Sprintf("%s%s", activeHelpMarker, activeHelpMessage),
		fmt.Sprintf("%s%s", activeHelpMarker, activeHelpMessage2),
		":4",
		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")

	if output != expected {
		t.Errorf("expected: %q, got: %q", expected, output)
	}

	// Test that multiple activeHelp messages can be used along with completions
	childCmd.ValidArgsFunction = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
		comps := []string{"first"}
		comps = AppendActiveHelp(comps, activeHelpMessage)
		comps = append(comps, "second")
		comps = AppendActiveHelp(comps, activeHelpMessage2)
		return comps, ShellCompDirectiveNoFileComp
	}

	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "thechild", "")
	if err != nil {
		t.Errorf("Unexpected error: %v", err)
	}

	expected = strings.Join([]string{
		"first",
		fmt.Sprintf("%s%s", activeHelpMarker, activeHelpMessage),
		"second",
		fmt.Sprintf("%s%s", activeHelpMarker, activeHelpMessage2),
		":4",
		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")

	if output != expected {
		t.Errorf("expected: %q, got: %q", expected, output)
	}
}

func TestActiveHelpForFlag(t *testing.T) {
	rootCmd := &Command{
		Use: "root",
		Run: emptyRun,
	}
	flagname := "flag"
	rootCmd.Flags().String(flagname, "", "A flag")

	// Test that multiple activeHelp message can be added
	_ = rootCmd.RegisterFlagCompletionFunc(flagname, func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
		comps := []string{"first"}
		comps = AppendActiveHelp(comps, activeHelpMessage)
		comps = append(comps, "second")
		comps = AppendActiveHelp(comps, activeHelpMessage2)
		return comps, ShellCompDirectiveNoFileComp
	})

	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--flag", "")
	if err != nil {
		t.Errorf("Unexpected error: %v", err)
	}

	expected := strings.Join([]string{
		"first",
		fmt.Sprintf("%s%s", activeHelpMarker, activeHelpMessage),
		"second",
		fmt.Sprintf("%s%s", activeHelpMarker, activeHelpMessage2),
		":4",
		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")

	if output != expected {
		t.Errorf("expected: %q, got: %q", expected, output)
	}
}

func TestConfigActiveHelp(t *testing.T) {
	rootCmd := &Command{
		Use: "root",
		Run: emptyRun,
	}

	childCmd := &Command{
		Use:   "thechild",
		Short: "The child command",
		Run:   emptyRun,
	}
	rootCmd.AddCommand(childCmd)

	activeHelpCfg := "someconfig,anotherconfig"
	// Set the variable that the user would be setting
	os.Setenv(activeHelpEnvVar(rootCmd.Name()), activeHelpCfg)

	childCmd.ValidArgsFunction = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
		receivedActiveHelpCfg := GetActiveHelpConfig(cmd)
		if receivedActiveHelpCfg != activeHelpCfg {
			t.Errorf("expected activeHelpConfig: %q, but got: %q", activeHelpCfg, receivedActiveHelpCfg)
		}
		return nil, ShellCompDirectiveDefault
	}

	_, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "thechild", "")
	if err != nil {
		t.Errorf("Unexpected error: %v", err)
	}

	// Test active help config for a flag
	activeHelpCfg = "a config for a flag"
	// Set the variable that the completions scripts will be setting
	os.Setenv(activeHelpEnvVar(rootCmd.Name()), activeHelpCfg)

	flagname := "flag"
	childCmd.Flags().String(flagname, "", "A flag")

	// Test that multiple activeHelp message can be added
	_ = childCmd.RegisterFlagCompletionFunc(flagname, func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
		receivedActiveHelpCfg := GetActiveHelpConfig(cmd)
		if receivedActiveHelpCfg != activeHelpCfg {
			t.Errorf("expected activeHelpConfig: %q, but got: %q", activeHelpCfg, receivedActiveHelpCfg)
		}
		return nil, ShellCompDirectiveDefault
	})

	_, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "thechild", "--flag", "")
	if err != nil {
		t.Errorf("Unexpected error: %v", err)
	}
}

func TestDisableActiveHelp(t *testing.T) {
	rootCmd := &Command{
		Use: "root",
		Run: emptyRun,
	}

	childCmd := &Command{
		Use:   "thechild",
		Short: "The child command",
		Run:   emptyRun,
	}
	rootCmd.AddCommand(childCmd)

	// Test the disabling of activeHelp using the specific program
	// environment variable that the completions scripts will be setting.
	// Make sure the disabling value is "0" by hard-coding it in the tests;
	// this is for backwards-compatibility as programs will be using this value.
	os.Setenv(activeHelpEnvVar(rootCmd.Name()), "0")

	childCmd.ValidArgsFunction = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
		comps := []string{"first"}
		comps = AppendActiveHelp(comps, activeHelpMessage)
		return comps, ShellCompDirectiveDefault
	}

	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "thechild", "")
	if err != nil {
		t.Errorf("Unexpected error: %v", err)
	}
	os.Unsetenv(activeHelpEnvVar(rootCmd.Name()))

	// Make sure there is no ActiveHelp in the output
	expected := strings.Join([]string{
		"first",
		":0",
		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")

	if output != expected {
		t.Errorf("expected: %q, got: %q", expected, output)
	}

	// Now test the global disabling of ActiveHelp
	os.Setenv(activeHelpGlobalEnvVar, "0")
	// Set the specific variable, to make sure it is ignored when the global env
	// var is set properly
	os.Setenv(activeHelpEnvVar(rootCmd.Name()), "1")

	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "thechild", "")
	if err != nil {
		t.Errorf("Unexpected error: %v", err)
	}

	// Make sure there is no ActiveHelp in the output
	expected = strings.Join([]string{
		"first",
		":0",
		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")

	if output != expected {
		t.Errorf("expected: %q, got: %q", expected, output)
	}

	// Make sure that if the global env variable is set to anything else than
	// the disable value it is ignored
	os.Setenv(activeHelpGlobalEnvVar, "on")
	// Set the specific variable, to make sure it is used (while ignoring the global env var)
	activeHelpCfg := "1"
	os.Setenv(activeHelpEnvVar(rootCmd.Name()), activeHelpCfg)

	childCmd.ValidArgsFunction = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
		receivedActiveHelpCfg := GetActiveHelpConfig(cmd)
		if receivedActiveHelpCfg != activeHelpCfg {
			t.Errorf("expected activeHelpConfig: %q, but got: %q", activeHelpCfg, receivedActiveHelpCfg)
		}
		return nil, ShellCompDirectiveDefault
	}

	_, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "thechild", "")
	if err != nil {
		t.Errorf("Unexpected error: %v", err)
	}
}
