package completion

import (
	"bufio"
	"os"
	"strings"
	"unicode"

	"github.com/containers/common/pkg/capabilities"
	"github.com/spf13/cobra"
)

// FlagCompletions - hold flag completion functions to be applied later with CompleteCommandFlags()
type FlagCompletions map[string]func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective)

// CompleteCommandFlags - Add completion functions for each flagname in FlagCompletions.
func CompleteCommandFlags(cmd *cobra.Command, flags FlagCompletions) {
	for flagName, completionFunc := range flags {
		_ = cmd.RegisterFlagCompletionFunc(flagName, completionFunc)
	}
}

/* Autocomplete Functions for cobra ValidArgsFunction */

// AutocompleteNone - Block the default shell completion (no paths)
func AutocompleteNone(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
	return nil, cobra.ShellCompDirectiveNoFileComp
}

// AutocompleteDefault - Use the default shell completion,
// allows path completion.
func AutocompleteDefault(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
	return nil, cobra.ShellCompDirectiveDefault
}

// AutocompleteCapabilities - Autocomplete linux capabilities options.
// Used by --cap-add and --cap-drop.
func AutocompleteCapabilities(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
	caps := capabilities.AllCapabilities()

	// convertCase will convert a string to lowercase only if the user input is lowercase
	convertCase := func(s string) string { return s }
	if len(toComplete) > 0 && unicode.IsLower(rune(toComplete[0])) {
		convertCase = strings.ToLower
	}

	// offset is used to trim "CAP_" if the user doesn't type CA... or ca...
	offset := 0
	if !strings.HasPrefix(toComplete, convertCase("CA")) {
		// setting the offset to 4 is safe since each cap starts with CAP_
		offset = 4
	}

	completions := make([]string, 0, len(caps))
	for _, cap := range caps {
		completions = append(completions, convertCase(cap)[offset:])
	}

	// add ALL here which is also a valid argument
	completions = append(completions, convertCase(capabilities.All))
	return completions, cobra.ShellCompDirectiveNoFileComp
}

// autocompleteSubIDName - autocomplete the names in /etc/subuid or /etc/subgid
func autocompleteSubIDName(filename string) ([]string, cobra.ShellCompDirective) {
	file, err := os.Open(filename)
	if err != nil {
		return nil, cobra.ShellCompDirectiveError
	}
	defer file.Close()

	var names []string
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		name := strings.SplitN(scanner.Text(), ":", 2)[0]
		names = append(names, name)
	}
	if err = scanner.Err(); err != nil {
		return nil, cobra.ShellCompDirectiveError
	}

	return names, cobra.ShellCompDirectiveNoFileComp
}

// AutocompleteSubgidName - Autocomplete subgidname based on the names in the /etc/subgid file.
func AutocompleteSubgidName(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
	return autocompleteSubIDName("/etc/subgid")
}

// AutocompleteSubuidName - Autocomplete subuidname based on the names in the /etc/subuid file.
func AutocompleteSubuidName(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
	return autocompleteSubIDName("/etc/subuid")
}

// AutocompleteArch - Autocomplete platform supported by container engines
func AutocompletePlatform(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
	completions := []string{
		"linux/386",
		"linux/amd64",
		"linux/arm",
		"linux/arm64",
		"linux/ppc64",
		"linux/ppc64le",
		"linux/mips",
		"linux/mipsle",
		"linux/mips64",
		"linux/mips64le",
		"linux/riscv64",
		"linux/s390x",
		"windows/386",
		"windows/amd64",
		"windows/arm",
	}
	return completions, cobra.ShellCompDirectiveNoFileComp
}

// AutocompleteArch - Autocomplete architectures supported by container engines
func AutocompleteArch(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
	completions := []string{
		"386",
		"amd64",
		"arm",
		"arm64",
		"ppc64",
		"ppc64le",
		"mips",
		"mipsle",
		"mips64",
		"mips64le",
		"riscv64",
		"s390x",
	}

	return completions, cobra.ShellCompDirectiveNoFileComp
}

// AutocompleteOS - Autocomplete OS supported by container engines
func AutocompleteOS(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
	completions := []string{"linux", "windows"}
	return completions, cobra.ShellCompDirectiveNoFileComp
}

// AutocompleteJSONFormat - Autocomplete format flag option.
// -> "json"
func AutocompleteJSONFormat(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
	return []string{"json"}, cobra.ShellCompDirectiveNoFileComp
}

// AutocompleteOneArg - Autocomplete one random arg
func AutocompleteOneArg(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
	if len(args) == 1 {
		return nil, cobra.ShellCompDirectiveDefault
	}
	return nil, cobra.ShellCompDirectiveNoFileComp
}
