package cmd

import (
	"fmt"
	"io"
	"slices"
	"sort"
	"strings"

	"github.com/spf13/cobra"
	"github.com/tidbcloud/clinic-cli/internal/config"
)

const (
	completionShellBash       = "bash"
	completionShellZsh        = "zsh"
	completionShellFish       = "fish"
	completionShellPowerShell = "powershell"
)

var (
	supportedCompletionShells = []string{
		completionShellBash,
		completionShellZsh,
		completionShellFish,
		completionShellPowerShell,
	}

	familySubcommands = map[string][]string{
		"auth":       {"set-key", "unset-key", "set-url", "show"},
		"cluster":    {"search", "search-id", "get", "get-by-id", "topology"},
		"completion": supportedCompletionShells,
		"data":       {"query", "schema"},
		"env":        {"use", "current", "list"},
		"events":     {"list"},
		"loki":       {"query", "instance-map", "labels", "label-values"},
		"metrics":    {"query", "range", "series"},
		"raw":        {"request"},
	}
)

func completionCommandHandler(_ *RuntimeContext, args []string, stdout io.Writer) error {
	command := newCompletionCommand(stdout)
	if err := executeCobraCommand(command, args); err != nil {
		return normalizeFamilyCommandError(err, "completion")
	}
	return nil
}

func newCompletionCommand(stdout io.Writer) *cobra.Command {
	command := newCommand(commandMetadata{
		Use:   "completion <bash|zsh|fish|powershell>",
		Short: "Generate shell completion scripts.",
		Long: `Generate shell completion scripts.

Arguments:
  <bash|zsh|fish|powershell>  Shell type to generate completion script for.`,
		Example: "  clinic completion bash > ~/.local/share/bash-completion/completions/clinic\n  clinic completion zsh > \"${fpath[1]}/_clinic\"",
	}, stdout)
	command.Args = cobra.ExactArgs(1)
	command.RunE = func(_ *cobra.Command, args []string) error {
		return runCompletion(args, stdout)
	}
	command.ValidArgsFunction = func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
		if len(args) > 0 {
			return nil, cobra.ShellCompDirectiveNoFileComp
		}
		return filterByPrefix(supportedCompletionShells, toComplete), cobra.ShellCompDirectiveNoFileComp
	}
	return command
}

func runCompletion(args []string, stdout io.Writer) error {
	root := defaultRootRunner().newRootCommand(io.Discard, io.Discard)
	switch strings.ToLower(args[0]) {
	case completionShellBash:
		return root.GenBashCompletionV2(stdout, true)
	case completionShellZsh:
		return root.GenZshCompletion(stdout)
	case completionShellFish:
		return root.GenFishCompletion(stdout, true)
	case completionShellPowerShell:
		return root.GenPowerShellCompletionWithDesc(stdout)
	default:
		return fmt.Errorf("unsupported shell %q; supported shells: bash, zsh, fish, powershell", args[0])
	}
}

func configureRootCompletion(command *cobra.Command, loadConfig func() (*config.Config, error)) {
	registerRootFlagCompletions(command, loadConfig)
}

func registerRootFlagCompletions(command *cobra.Command, loadConfig func() (*config.Config, error)) {
	_ = command.RegisterFlagCompletionFunc("env", func(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
		cfg := loadConfigForCompletion(loadConfig)
		return completionEnvironments(cfg, toComplete), cobra.ShellCompDirectiveNoFileComp
	})

	_ = command.RegisterFlagCompletionFunc("profile", func(c *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
		cfg := loadConfigForCompletion(loadConfig)
		env := completionSelectedEnv(c.Root(), cfg)
		return completionProfiles(cfg, env, toComplete, false), cobra.ShellCompDirectiveNoFileComp
	})
}

func configureRootSubcommandCompletion(command *cobra.Command, family string, loadConfig func() (*config.Config, error)) {
	command.ValidArgsFunction = func(c *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
		cfg := loadConfigForCompletion(loadConfig)
		return completeFamilyArgs(c, family, cfg, args, toComplete)
	}
}

func configureEnvCommandCompletion(command *cobra.Command, loadConfig func() (*config.Config, error)) {
	if useCommand := findSubcommand(command, "use"); useCommand != nil {
		useCommand.ValidArgsFunction = func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
			if len(args) > 0 {
				return nil, cobra.ShellCompDirectiveNoFileComp
			}
			cfg := loadConfigForCompletion(loadConfig)
			return completionEnvironments(cfg, toComplete), cobra.ShellCompDirectiveNoFileComp
		}
	}
}

func configureAuthCommandCompletion(command *cobra.Command, loadConfig func() (*config.Config, error)) {
	for _, name := range []string{"set-key", "unset-key", "set-url", "show"} {
		subcommand := findSubcommand(command, name)
		if subcommand == nil {
			continue
		}
		_ = subcommand.RegisterFlagCompletionFunc("env", func(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
			cfg := loadConfigForCompletion(loadConfig)
			return completionEnvironments(cfg, toComplete), cobra.ShellCompDirectiveNoFileComp
		})
	}
}

func configureClusterCommandCompletion(command *cobra.Command) {
	searchCommand := findSubcommand(command, "search")
	if searchCommand == nil {
		return
	}
	_ = searchCommand.RegisterFlagCompletionFunc("deploy-type", func(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
		return filterByPrefix(knownDeployTypeV2Values, toComplete), cobra.ShellCompDirectiveNoFileComp
	})
}

func configureProfileCommandCompletion(command *cobra.Command, loadConfig func() (*config.Config, error)) {
	for _, item := range []struct {
		name                string
		includeCurrentAlias bool
	}{
		{name: "open"},
		{name: "use"},
		{name: "set", includeCurrentAlias: true},
		{name: "delete"},
	} {
		subcommand := findSubcommand(command, item.name)
		if subcommand == nil {
			continue
		}
		includeCurrentAlias := item.includeCurrentAlias
		subcommand.ValidArgsFunction = func(c *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
			if len(args) > 0 {
				return nil, cobra.ShellCompDirectiveNoFileComp
			}
			cfg := loadConfigForCompletion(loadConfig)
			selectedEnv := completionSelectedEnv(c.Root(), cfg)
			return completionProfiles(cfg, selectedEnv, toComplete, includeCurrentAlias), cobra.ShellCompDirectiveNoFileComp
		}
	}
}

func configureSwitchCommandCompletion(command *cobra.Command, loadConfig func() (*config.Config, error)) {
	command.ValidArgsFunction = func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
		cfg := loadConfigForCompletion(loadConfig)
		return completeSwitchArgs(cfg, args, toComplete)
	}
}

func findSubcommand(command *cobra.Command, name string) *cobra.Command {
	for _, child := range command.Commands() {
		if child.Name() == name {
			return child
		}
	}
	return nil
}

func completeFamilyArgs(command *cobra.Command, family string, cfg *config.Config, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
	switch family {
	case "switch":
		return completeSwitchArgs(cfg, args, toComplete)
	case "env":
		return completeEnvArgs(cfg, args, toComplete)
	default:
		subcommands := familySubcommands[family]
		if len(args) == 0 && len(subcommands) > 0 {
			return filterByPrefix(subcommands, toComplete), cobra.ShellCompDirectiveNoFileComp
		}
		return nil, cobra.ShellCompDirectiveNoFileComp
	}
}

func completeSwitchArgs(cfg *config.Config, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
	switch len(args) {
	case 0:
		return completionEnvironments(cfg, toComplete), cobra.ShellCompDirectiveNoFileComp
	case 1:
		return completionProfiles(cfg, args[0], toComplete, false), cobra.ShellCompDirectiveNoFileComp
	default:
		return nil, cobra.ShellCompDirectiveNoFileComp
	}
}

func completeEnvArgs(cfg *config.Config, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
	if len(args) == 0 {
		return filterByPrefix(familySubcommands["env"], toComplete), cobra.ShellCompDirectiveNoFileComp
	}
	if len(args) == 1 && args[0] == "use" {
		return completionEnvironments(cfg, toComplete), cobra.ShellCompDirectiveNoFileComp
	}
	return nil, cobra.ShellCompDirectiveNoFileComp
}

func completionSelectedEnv(root *cobra.Command, cfg *config.Config) string {
	if root != nil {
		if value, err := root.PersistentFlags().GetString("env"); err == nil && value != "" {
			return value
		}
	}
	if cfg != nil && cfg.ActiveEnv != "" {
		return cfg.ActiveEnv
	}
	return "prod"
}

func completionEnvironments(cfg *config.Config, toComplete string) []string {
	if cfg == nil || len(cfg.Environments) == 0 {
		return filterByPrefix(orderedEnvironments, toComplete)
	}

	items := make([]string, 0, len(cfg.Environments))
	for name := range cfg.Environments {
		items = append(items, name)
	}
	sort.Strings(items)
	return filterByPrefix(items, toComplete)
}

func completionProfiles(cfg *config.Config, env, toComplete string, includeCurrentAlias bool) []string {
	items := make([]string, 0)
	if cfg != nil {
		if container, ok := cfg.EnvProfiles[env]; ok {
			items = make([]string, 0, len(container.Profiles))
			for name := range container.Profiles {
				items = append(items, name)
			}
		}
	}

	if includeCurrentAlias && !slices.Contains(items, profileSetCurrentAlias) {
		items = append(items, profileSetCurrentAlias)
	}

	sort.Strings(items)
	return filterByPrefix(items, toComplete)
}

func filterByPrefix(items []string, toComplete string) []string {
	filtered := make([]string, 0, len(items))
	for _, item := range items {
		if strings.HasPrefix(item, toComplete) {
			filtered = append(filtered, item)
		}
	}
	return filtered
}

func loadConfigForCompletion(loadConfig func() (*config.Config, error)) *config.Config {
	if loadConfig == nil {
		return nil
	}
	cfg, err := loadConfig()
	if err != nil {
		return nil
	}
	return cfg
}
