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

package command

import (
	"fmt"
	"strings"

	"github.com/mitchellh/cli"
	"github.com/posener/complete"
)

var (
	_ cli.Command             = (*TokenRevokeCommand)(nil)
	_ cli.CommandAutocomplete = (*TokenRevokeCommand)(nil)
)

type TokenRevokeCommand struct {
	*BaseCommand

	flagAccessor bool
	flagSelf     bool
	flagMode     string
}

func (c *TokenRevokeCommand) Synopsis() string {
	return "Revoke a token and its children"
}

func (c *TokenRevokeCommand) Help() string {
	helpText := `
Usage: vault token revoke [options] [TOKEN | ACCESSOR]

  Revokes authentication tokens and their children. If a TOKEN is not provided,
  the locally authenticated token is used. The "-mode" flag can be used to
  control the behavior of the revocation. See the "-mode" flag documentation
  for more information.

  Revoke a token and all the token's children:

      $ vault token revoke 96ddf4bc-d217-f3ba-f9bd-017055595017

  Revoke a token leaving the token's children:

      $ vault token revoke -mode=orphan 96ddf4bc-d217-f3ba-f9bd-017055595017

  Revoke a token by accessor:

      $ vault token revoke -accessor 9793c9b3-e04a-46f3-e7b8-748d7da248da

  For a full list of examples, please see the documentation.

` + c.Flags().Help()

	return strings.TrimSpace(helpText)
}

func (c *TokenRevokeCommand) Flags() *FlagSets {
	set := c.flagSet(FlagSetHTTP)

	f := set.NewFlagSet("Command Options")

	f.BoolVar(&BoolVar{
		Name:       "accessor",
		Target:     &c.flagAccessor,
		Default:    false,
		EnvVar:     "",
		Completion: complete.PredictNothing,
		Usage:      "Treat the argument as an accessor instead of a token.",
	})

	f.BoolVar(&BoolVar{
		Name:       "self",
		Target:     &c.flagSelf,
		Default:    false,
		EnvVar:     "",
		Completion: complete.PredictNothing,
		Usage:      "Perform the revocation on the currently authenticated token.",
	})

	f.StringVar(&StringVar{
		Name:       "mode",
		Target:     &c.flagMode,
		Default:    "",
		EnvVar:     "",
		Completion: complete.PredictSet("orphan", "path"),
		Usage: "Type of revocation to perform. If unspecified, Vault will revoke " +
			"the token and all of the token's children. If \"orphan\", Vault will " +
			"revoke only the token, leaving the children as orphans. If \"path\", " +
			"tokens created from the given authentication path prefix are deleted " +
			"along with their children.",
	})

	return set
}

func (c *TokenRevokeCommand) AutocompleteArgs() complete.Predictor {
	return nil
}

func (c *TokenRevokeCommand) AutocompleteFlags() complete.Flags {
	return c.Flags().Completions()
}

func (c *TokenRevokeCommand) Run(args []string) int {
	f := c.Flags()

	if err := f.Parse(args); err != nil {
		c.UI.Error(err.Error())
		return 1
	}

	args = f.Args()
	token := ""
	if len(args) > 0 {
		token = strings.TrimSpace(args[0])
	}

	switch c.flagMode {
	case "", "orphan", "path":
	default:
		c.UI.Error(fmt.Sprintf("Invalid mode: %s", c.flagMode))
		return 1
	}

	switch {
	case c.flagSelf && len(args) > 0:
		c.UI.Error(fmt.Sprintf("Too many arguments with -self (expected 0, got %d)", len(args)))
		return 1
	case !c.flagSelf && len(args) > 1:
		c.UI.Error(fmt.Sprintf("Too many arguments (expected 1 or -self, got %d)", len(args)))
		return 1
	case !c.flagSelf && len(args) < 1:
		c.UI.Error(fmt.Sprintf("Not enough arguments (expected 1 or -self, got %d)", len(args)))
		return 1
	case c.flagSelf && c.flagAccessor:
		c.UI.Error("Cannot use -self with -accessor!")
		return 1
	case c.flagSelf && c.flagMode != "":
		c.UI.Error("Cannot use -self with -mode!")
		return 1
	case c.flagAccessor && c.flagMode == "orphan":
		c.UI.Error("Cannot use -accessor with -mode=orphan!")
		return 1
	case c.flagAccessor && c.flagMode == "path":
		c.UI.Error("Cannot use -accessor with -mode=path!")
		return 1
	}

	client, err := c.Client()
	if err != nil {
		c.UI.Error(err.Error())
		return 2
	}

	var revokeFn func(string) error
	// Handle all 6 possible combinations
	switch {
	case !c.flagAccessor && c.flagSelf && c.flagMode == "":
		revokeFn = client.Auth().Token().RevokeSelf
	case !c.flagAccessor && !c.flagSelf && c.flagMode == "":
		revokeFn = client.Auth().Token().RevokeTree
	case !c.flagAccessor && !c.flagSelf && c.flagMode == "orphan":
		revokeFn = client.Auth().Token().RevokeOrphan
	case !c.flagAccessor && !c.flagSelf && c.flagMode == "path":
		revokeFn = client.Sys().RevokePrefix
	case c.flagAccessor && !c.flagSelf && c.flagMode == "":
		revokeFn = client.Auth().Token().RevokeAccessor
	}

	if err := revokeFn(token); err != nil {
		c.UI.Error(fmt.Sprintf("Error revoking token: %s", err))
		return 2
	}

	c.UI.Output("Success! Revoked token (if it existed)")
	return 0
}
