// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package command

import (
	"fmt"
	"strings"

	"github.com/hashicorp/nomad/api/contexts"
	"github.com/mitchellh/cli"
	"github.com/posener/complete"
)

type StatusCommand struct {
	Meta

	// Placeholder bool to allow passing of verbose flags to subcommands.
	verbose bool
}

func (c *StatusCommand) Help() string {
	helpText := `
Usage: nomad status [options] <identifier>

  Display the status output for any given resource. The command will
  detect the type of resource being queried and display the appropriate
  status output.

General Options:

  ` + generalOptionsUsage(usageOptsDefault) + `

Status Options:

  -verbose
    Display full information.
`

	return strings.TrimSpace(helpText)
}

func (c *StatusCommand) Synopsis() string {
	return "Display the status output for a resource"
}

func (c *StatusCommand) AutocompleteFlags() complete.Flags {
	return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient),
		complete.Flags{
			"-verbose": complete.PredictNothing,
		})
}

func (c *StatusCommand) AutocompleteArgs() complete.Predictor {
	return complete.PredictFunc(func(a complete.Args) []string {
		client, err := c.Meta.Client()
		if err != nil {
			return nil
		}

		resp, _, err := client.Search().PrefixSearch(a.Last, contexts.All, nil)
		if err != nil {
			return []string{}
		}

		final := make([]string, 0)

		for _, matches := range resp.Matches {
			if len(matches) == 0 {
				continue
			}

			final = append(final, matches...)
		}

		return final
	})
}

func (c *StatusCommand) Run(args []string) int {
	flags := c.Meta.FlagSet("status", FlagSetClient)
	flags.Usage = func() { c.Ui.Output(c.Help()) }
	flags.BoolVar(&c.verbose, "verbose", false, "")

	if err := flags.Parse(args); err != nil {
		c.Ui.Error(fmt.Sprintf("Error parsing arguments: %q", err))
		return 1
	}

	// Store the original arguments so we can pass them to the routed command
	argsCopy := args

	// Check that we got exactly one evaluation ID
	args = flags.Args()

	// Get the HTTP client
	client, err := c.Meta.Client()
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error initializing client: %q", err))
		return 1
	}

	// If no identifier is provided, default to listing jobs
	if len(args) == 0 {
		cmd := &JobStatusCommand{Meta: c.Meta}
		return cmd.Run(argsCopy)
	}

	id := args[len(args)-1]

	// Query for the context associated with the id
	res, _, err := client.Search().PrefixSearch(id, contexts.All, nil)
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error querying search with id: %q", err))
		return 1
	}

	if res.Matches == nil {
		c.Ui.Error(fmt.Sprintf("No matches returned for query: %q", err))
		return 1
	}

	var match contexts.Context
	exactMatches := 0
	for ctx, vers := range res.Matches {
		if len(vers) > 0 && vers[0] == id {
			match = ctx
			exactMatches++
		}
	}

	if exactMatches > 1 {
		c.logMultiMatchError(id, res.Matches)
		return 1
	} else if exactMatches == 0 {
		matchCount := 0
		for ctx, vers := range res.Matches {
			l := len(vers)
			if l == 1 {
				match = ctx
				matchCount++
			}

			// Only a single result should return, as this is a match against a full id
			if matchCount > 1 || l > 1 {
				c.logMultiMatchError(id, res.Matches)
				return 1
			}
		}
	}

	var cmd cli.Command
	switch match {
	case contexts.Evals:
		cmd = &EvalStatusCommand{Meta: c.Meta}
	case contexts.Nodes:
		cmd = &NodeStatusCommand{Meta: c.Meta}
	case contexts.Allocs:
		cmd = &AllocStatusCommand{Meta: c.Meta}
	case contexts.Jobs:
		cmd = &JobStatusCommand{Meta: c.Meta}
	case contexts.Deployments:
		cmd = &DeploymentStatusCommand{Meta: c.Meta}
	case contexts.Namespaces:
		cmd = &NamespaceStatusCommand{Meta: c.Meta}
	case contexts.Quotas:
		cmd = &QuotaStatusCommand{Meta: c.Meta}
	case contexts.Plugins:
		cmd = &PluginStatusCommand{Meta: c.Meta}
	case contexts.Volumes:
		cmd = &VolumeStatusCommand{Meta: c.Meta}
	default:
		c.Ui.Error(fmt.Sprintf("Unable to resolve ID: %q", id))
		return 1
	}

	return cmd.Run(argsCopy)
}

// logMultiMatchError is used to log an error message when multiple matches are
// found. The error message logged displays the matched IDs per context.
func (c *StatusCommand) logMultiMatchError(id string, matches map[contexts.Context][]string) {
	c.Ui.Error(fmt.Sprintf("Multiple matches found for id %q", id))
	for ctx, vers := range matches {
		if len(vers) == 0 {
			continue
		}

		c.Ui.Error(fmt.Sprintf("\n%s:", strings.Title(string(ctx))))
		c.Ui.Error(strings.Join(vers, ", "))
	}
}
