package command

import (
	"flag"
	"fmt"
	"strings"

	"github.com/hashicorp/consul/api"
	"github.com/mitchellh/cli"
)

// MaintCommand is a Command implementation that enables or disables
// node or service maintenance mode.
type MaintCommand struct {
	Ui cli.Ui
}

func (c *MaintCommand) Help() string {
	helpText := `
Usage: consul maint [options]

  Places a node or service into maintenance mode. During maintenance mode,
  the node or service will be excluded from all queries through the DNS
  or API interfaces, effectively taking it out of the pool of available
  nodes. This is done by registering an additional critical health check.

  When enabling maintenance mode for a node or service, you may optionally
  specify a reason string. This string will appear in the "Notes" field
  of the critical health check which is registered against the node or
  service. If no reason is provided, a default value will be used.

  Maintenance mode is persistent, and will be restored in the event of an
  agent restart. It is therefore required to disable maintenance mode on
  a given node or service before it will be placed back into the pool.

  By default, we operate on the node as a whole. By specifying the
  "-service" argument, this behavior can be changed to enable or disable
  only a specific service.

  If no arguments are given, the agent's maintenance status will be shown.
  This will return blank if nothing is currently under maintenance.

Options:

  -enable                    Enable maintenance mode.
  -disable                   Disable maintenance mode.
  -reason=<string>           Text string describing the maintenance reason
  -service=<serviceID>       Control maintenance mode for a specific service ID
  -token=""                  ACL token to use. Defaults to that of agent.
  -http-addr=127.0.0.1:8500  HTTP address of the Consul agent.
`
	return strings.TrimSpace(helpText)
}

func (c *MaintCommand) Run(args []string) int {
	var enable bool
	var disable bool
	var reason string
	var serviceID string
	var token string

	cmdFlags := flag.NewFlagSet("maint", flag.ContinueOnError)
	cmdFlags.Usage = func() { c.Ui.Output(c.Help()) }

	cmdFlags.BoolVar(&enable, "enable", false, "enable maintenance mode")
	cmdFlags.BoolVar(&disable, "disable", false, "disable maintenance mode")
	cmdFlags.StringVar(&reason, "reason", "", "maintenance reason")
	cmdFlags.StringVar(&serviceID, "service", "", "service maintenance")
	cmdFlags.StringVar(&token, "token", "", "")
	httpAddr := HTTPAddrFlag(cmdFlags)

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

	// Ensure we don't have conflicting args
	if enable && disable {
		c.Ui.Error("Only one of -enable or -disable may be provided")
		return 1
	}
	if !enable && reason != "" {
		c.Ui.Error("Reason may only be provided with -enable")
		return 1
	}
	if !enable && !disable && serviceID != "" {
		c.Ui.Error("Service requires either -enable or -disable")
		return 1
	}

	// Create and test the HTTP client
	conf := api.DefaultConfig()
	conf.Address = *httpAddr
	conf.Token = token
	client, err := api.NewClient(conf)
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err))
		return 1
	}
	a := client.Agent()
	nodeName, err := a.NodeName()
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error querying Consul agent: %s", err))
		return 1
	}

	if !enable && !disable {
		// List mode - list nodes/services in maintenance mode
		checks, err := a.Checks()
		if err != nil {
			c.Ui.Error(fmt.Sprintf("Error getting checks: %s", err))
			return 1
		}

		for _, check := range checks {
			if check.CheckID == "_node_maintenance" {
				c.Ui.Output("Node:")
				c.Ui.Output("  Name:   " + nodeName)
				c.Ui.Output("  Reason: " + check.Notes)
				c.Ui.Output("")
			} else if strings.HasPrefix(string(check.CheckID), "_service_maintenance:") {
				c.Ui.Output("Service:")
				c.Ui.Output("  ID:     " + check.ServiceID)
				c.Ui.Output("  Reason: " + check.Notes)
				c.Ui.Output("")
			}
		}

		return 0
	}

	if enable {
		// Enable node maintenance
		if serviceID == "" {
			if err := a.EnableNodeMaintenance(reason); err != nil {
				c.Ui.Error(fmt.Sprintf("Error enabling node maintenance: %s", err))
				return 1
			}
			c.Ui.Output("Node maintenance is now enabled")
			return 0
		}

		// Enable service maintenance
		if err := a.EnableServiceMaintenance(serviceID, reason); err != nil {
			c.Ui.Error(fmt.Sprintf("Error enabling service maintenance: %s", err))
			return 1
		}
		c.Ui.Output(fmt.Sprintf("Service maintenance is now enabled for %q", serviceID))
		return 0
	}

	if disable {
		// Disable node maintenance
		if serviceID == "" {
			if err := a.DisableNodeMaintenance(); err != nil {
				c.Ui.Error(fmt.Sprintf("Error disabling node maintenance: %s", err))
				return 1
			}
			c.Ui.Output("Node maintenance is now disabled")
			return 0
		}

		// Disable service maintenance
		if err := a.DisableServiceMaintenance(serviceID); err != nil {
			c.Ui.Error(fmt.Sprintf("Error disabling service maintenance: %s", err))
			return 1
		}
		c.Ui.Output(fmt.Sprintf("Service maintenance is now disabled for %q", serviceID))
		return 0
	}

	return 0
}

func (c *MaintCommand) Synopsis() string {
	return "Controls node or service maintenance mode"
}