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

package command

import (
	"fmt"
	"sort"
	"strings"

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

// Ensure ServiceListCommand satisfies the cli.Command interface.
var _ cli.Command = &ServiceListCommand{}

// ServiceListCommand implements cli.Command.
type ServiceListCommand struct {
	Meta
}

// Help satisfies the cli.Command Help function.
func (s *ServiceListCommand) Help() string {
	helpText := `
Usage: nomad service list [options]

  List is used to list the currently registered services.

  If ACLs are enabled, this command requires a token with the 'read-job'
  capabilities for the namespace of all services. Any namespaces that the token
  does not have access to will have its services filtered from the results.

General Options:

  ` + generalOptionsUsage(usageOptsDefault) + `

Service List Options:

  -json
    Output the services in JSON format.

  -t
    Format and display the services using a Go template.
`
	return strings.TrimSpace(helpText)
}

// Synopsis satisfies the cli.Command Synopsis function.
func (s *ServiceListCommand) Synopsis() string {
	return "Display all registered Nomad services"
}

func (s *ServiceListCommand) AutocompleteFlags() complete.Flags {
	return mergeAutocompleteFlags(s.Meta.AutocompleteFlags(FlagSetClient),
		complete.Flags{
			"-json": complete.PredictNothing,
			"-t":    complete.PredictAnything,
		})
}

// Name returns the name of this command.
func (s *ServiceListCommand) Name() string { return "service list" }

// Run satisfies the cli.Command Run function.
func (s *ServiceListCommand) Run(args []string) int {

	var (
		json       bool
		tmpl, name string
	)

	flags := s.Meta.FlagSet(s.Name(), FlagSetClient)
	flags.Usage = func() { s.Ui.Output(s.Help()) }
	flags.BoolVar(&json, "json", false, "")
	flags.StringVar(&name, "name", "", "")
	flags.StringVar(&tmpl, "t", "", "")
	if err := flags.Parse(args); err != nil {
		return 1
	}

	if args = flags.Args(); len(args) > 0 {
		s.Ui.Error("This command takes no arguments")
		s.Ui.Error(commandErrorText(s))
		return 1
	}

	client, err := s.Meta.Client()
	if err != nil {
		s.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
		return 1
	}

	list, _, err := client.Services().List(nil)
	if err != nil {
		s.Ui.Error(fmt.Sprintf("Error listing service registrations: %s", err))
		return 1
	}

	if len(list) == 0 {
		s.Ui.Output("No service registrations found")
		return 0
	}

	if json || len(tmpl) > 0 {
		out, err := Format(json, tmpl, list)
		if err != nil {
			s.Ui.Error(err.Error())
			return 1
		}
		s.Ui.Output(out)
		return 0
	}

	s.formatOutput(list)
	return 0
}

func (s *ServiceListCommand) formatOutput(regs []*api.ServiceRegistrationListStub) {

	// Create objects to hold sorted a sorted namespace array and a mapping, so
	// we can perform service lookups on a namespace basis.
	sortedNamespaces := make([]string, len(regs))
	namespacedServices := make(map[string][]*api.ServiceRegistrationStub)

	for i, namespaceServices := range regs {
		sortedNamespaces[i] = namespaceServices.Namespace
		namespacedServices[namespaceServices.Namespace] = namespaceServices.Services
	}

	// Sort the namespaces.
	sort.Strings(sortedNamespaces)

	// The table always starts with the service name.
	outputTable := []string{"Service Name"}

	// If the request was made using the wildcard namespace, include this in
	// the output.
	if s.Meta.namespace == api.AllNamespacesNamespace {
		outputTable[0] += "|Namespace"
	}

	// The tags come last and are always present.
	outputTable[0] += "|Tags"

	for _, ns := range sortedNamespaces {

		// Grab the services belonging to this namespace.
		services := namespacedServices[ns]

		// Create objects to hold sorted a sorted service name array and a
		// mapping, so we can perform service tag lookups on a name basis.
		sortedNames := make([]string, len(services))
		serviceTags := make(map[string][]string)

		for i, service := range services {
			sortedNames[i] = service.ServiceName
			serviceTags[service.ServiceName] = service.Tags
		}

		// Sort the service names.
		sort.Strings(sortedNames)

		for _, serviceName := range sortedNames {

			// Grab the service tags, and sort these for good measure.
			tags := serviceTags[serviceName]
			sort.Strings(tags)

			// Build the output array entry.
			regOutput := serviceName

			if s.Meta.namespace == api.AllNamespacesNamespace {
				regOutput += "|" + ns
			}
			regOutput += "|" + fmt.Sprintf("[%s]", strings.Join(tags, ","))
			outputTable = append(outputTable, regOutput)
		}
	}

	s.Ui.Output(formatList(outputTable))
}
