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

package command

import (
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/posener/complete"
)

const (
	HealthCritical = 2
	HealthWarn     = 1
	HealthPass     = 0
	HealthUnknown  = 3
)

type AgentCheckCommand struct {
	Meta
}

func (c *AgentCheckCommand) Help() string {
	helpText := `
Usage: nomad check [options]

  Display state of the Nomad agent. The exit code of the command is Nagios
  compatible and could be used with alerting systems.

General Options:

  ` + generalOptionsUsage(usageOptsDefault|usageOptsNoNamespace) + `

Agent Check Options:

  -min-peers
     Minimum number of peers that a server is expected to know.

  -min-servers
     Minimum number of servers that a client is expected to know.
`

	return strings.TrimSpace(helpText)
}

func (c *AgentCheckCommand) Synopsis() string {
	return "Displays health of the local Nomad agent"
}

func (c *AgentCheckCommand) Name() string { return "check" }

func (c *AgentCheckCommand) Run(args []string) int {
	var minPeers, minServers int

	flags := c.Meta.FlagSet("check", FlagSetClient)
	flags.Usage = func() { c.Ui.Output(c.Help()) }
	flags.IntVar(&minPeers, "min-peers", 0, "")
	flags.IntVar(&minServers, "min-servers", 1, "")

	if err := flags.Parse(args); err != nil {
		return 1
	}

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

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

	info, err := client.Agent().Self()
	if err != nil {
		c.Ui.Output(fmt.Sprintf("unable to query agent info: %v", err))
		return HealthCritical
	}
	if _, ok := info.Stats["nomad"]; ok {
		return c.checkServerHealth(info.Stats, minPeers)
	}

	if clientStats, ok := info.Stats["client"]; ok {
		return c.checkClientHealth(clientStats, minServers)
	}
	return HealthWarn
}

// checkServerHealth returns the health of a server.
// TODO Add more rules for determining server health
func (c *AgentCheckCommand) checkServerHealth(info map[string]map[string]string, minPeers int) int {
	raft := info["raft"]
	knownPeers, err := strconv.Atoi(raft["num_peers"])
	if err != nil {
		c.Ui.Output(fmt.Sprintf("unable to get known peers: %v", err))
		return HealthCritical
	}

	if knownPeers < minPeers {
		c.Ui.Output(fmt.Sprintf("known peers: %v, is less than expected number of peers: %v", knownPeers, minPeers))
		return HealthCritical
	}
	return HealthPass
}

// checkClientHealth returns the health of a client
func (c *AgentCheckCommand) checkClientHealth(clientStats map[string]string, minServers int) int {
	knownServers, err := strconv.Atoi(clientStats["known_servers"])
	if err != nil {
		c.Ui.Output(fmt.Sprintf("unable to get known servers: %v", err))
		return HealthCritical
	}

	heartbeatTTL, err := time.ParseDuration(clientStats["heartbeat_ttl"])
	if err != nil {
		c.Ui.Output(fmt.Sprintf("unable to parse heartbeat TTL: %v", err))
		return HealthCritical
	}

	lastHeartbeat, err := time.ParseDuration(clientStats["last_heartbeat"])
	if err != nil {
		c.Ui.Output(fmt.Sprintf("unable to parse last heartbeat: %v", err))
		return HealthCritical
	}

	if lastHeartbeat > heartbeatTTL {
		c.Ui.Output(fmt.Sprintf("last heartbeat was %q time ago, expected heartbeat ttl: %q", lastHeartbeat, heartbeatTTL))
		return HealthCritical
	}

	if knownServers < minServers {
		c.Ui.Output(fmt.Sprintf("known servers: %v, is less than expected number of servers: %v", knownServers, minServers))
		return HealthCritical
	}

	return HealthPass
}

func (c *AgentCheckCommand) AutocompleteFlags() complete.Flags {
	return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient),
		complete.Flags{
			"-min-peers":   complete.PredictAnything,
			"-min-servers": complete.PredictAnything,
		})
}

func (c *AgentCheckCommand) AutocompleteArgs() complete.Predictor {
	return complete.PredictNothing
}
