package clihelper

import (
	"context"
	"fmt"
	"io"
	"os"
	"strings"
	"unicode/utf8"

	"slices"

	"github.com/gruntwork-io/terragrunt/internal/errors"
	"github.com/posener/complete/cmd/install"
)

// defaultAutocompleteInstallFlag and defaultAutocompleteUninstallFlag are the
// default values for the autocomplete install and uninstall flags.
const (
	defaultAutocompleteInstallFlag   = "install-autocomplete"
	defaultAutocompleteUninstallFlag = "uninstall-autocomplete"

	envCompleteLine = "COMP_LINE"

	maxDashesInFlag = 2
)

var DefaultComplete = defaultComplete //nolint:gochecknoglobals

// AutocompleteInstaller is an interface to be implemented to perform the
// autocomplete installation and uninstallation with a CLI.
//
// This interface is not exported because it only exists for unit tests
// to be able to test that the installation is called properly.
type AutocompleteInstaller interface {
	Install(string) error
	Uninstall(string) error
}

// autocompleteInstaller uses the install package to do the
// install/uninstall.
type autocompleteInstaller struct{}

func (i *autocompleteInstaller) Install(cmd string) error {
	if err := install.Install(cmd); err != nil {
		return errors.New(err)
	}

	return nil
}

func (i *autocompleteInstaller) Uninstall(cmd string) error {
	if err := install.Uninstall(cmd); err != nil {
		return errors.New(err)
	}

	return nil
}

// ShowCompletions prints the lists of commands within a given context
func ShowCompletions(ctx context.Context, cliCtx *Context) error {
	if cmd := cliCtx.Command; cmd != nil && cmd.Complete != nil {
		return cmd.Complete(ctx, cliCtx)
	}

	return DefaultComplete(cliCtx)
}

func defaultComplete(cliCtx *Context) error {
	arg := cliCtx.Args().Last()

	if strings.HasPrefix(arg, "-") {
		if cmd := cliCtx.Command; cmd != nil {
			return printFlagSuggestions(arg, cmd.Flags, cliCtx.Writer)
		}

		return printFlagSuggestions(arg, cliCtx.Flags, cliCtx.Writer)
	}

	if cmd := cliCtx.Command; cmd != nil {
		return printCommandSuggestions(arg, cmd.Subcommands, cliCtx.Writer)
	}

	return printCommandSuggestions(arg, cliCtx.Commands, cliCtx.Writer)
}

func printCommandSuggestions(arg string, commands []*Command, writer io.Writer) error {
	errs := []error{}

	for _, command := range commands {
		if command.Hidden {
			continue
		}

		for _, name := range command.Names() {
			if name != "" && (arg == "" || strings.HasPrefix(name, arg)) {
				_, err := fmt.Fprintln(writer, name)
				errs = append(errs, err)
			}
		}
	}

	if len(errs) > 0 {
		return errors.Join(errs...)
	}

	return nil
}

func printFlagSuggestions(arg string, flags []Flag, writer io.Writer) error {
	cur := strings.TrimLeft(arg, "-")

	errs := []error{}

	for _, flag := range flags {
		for _, name := range flag.Names() {
			name = strings.TrimSpace(name)
			// this will get total count utf8 letters in flag name
			count := min(utf8.RuneCountInString(name), maxDashesInFlag)
			// if flag name has more than one utf8 letter and last argument in cli has -- prefix then
			// skip flag completion for short flags example -v or -x
			if strings.HasPrefix(arg, "--") && count == 1 {
				continue
			}
			// match if last argument matches this flag and it is not repeated
			if strings.HasPrefix(name, cur) && cur != name && !cliArgContains(name) {
				flagCompletion := fmt.Sprintf("%s%s", strings.Repeat("-", count), name)

				_, err := fmt.Fprintln(writer, flagCompletion)
				errs = append(errs, err)
			}
		}
	}

	if len(errs) > 0 {
		return errors.Join(errs...)
	}

	return nil
}

func cliArgContains(flagName string) bool {
	for name := range strings.SplitSeq(flagName, ",") {
		name = strings.TrimSpace(name)

		count := min(utf8.RuneCountInString(name), maxDashesInFlag)

		flag := fmt.Sprintf("%s%s", strings.Repeat("-", count), name)
		if slices.Contains(os.Args, flag) {
			return true
		}
	}

	return false
}
