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

package command

import (
	"fmt"
	"path/filepath"
	"strings"

	"github.com/opentofu/svchost"

	"github.com/opentofu/opentofu/internal/command/cliconfig"
	"github.com/opentofu/opentofu/internal/tfdiags"
)

// LogoutCommand is a Command implementation which removes stored credentials
// for a remote service host.
type LogoutCommand struct {
	Meta
}

// Run implements cli.Command.
func (c *LogoutCommand) Run(args []string) int {
	ctx := c.CommandContext()

	args = c.Meta.process(args)
	cmdFlags := c.Meta.defaultFlagSet("logout")
	cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
	if err := cmdFlags.Parse(args); err != nil {
		return 1
	}

	args = cmdFlags.Args()
	if len(args) != 1 {
		c.Ui.Error(
			"The logout command expects exactly one argument: the host to log out of.")
		cmdFlags.Usage()
		return 1
	}

	var diags tfdiags.Diagnostics

	givenHostname := args[0]

	hostname, err := svchost.ForComparison(givenHostname)
	if err != nil {
		diags = diags.Append(tfdiags.Sourceless(
			tfdiags.Error,
			"Invalid hostname",
			fmt.Sprintf("The given hostname %q is not valid: %s.", givenHostname, err.Error()),
		))
		c.showDiagnostics(diags)
		return 1
	}

	// From now on, since we've validated the given hostname, we should use
	// dispHostname in the UI to ensure we're presenting it in the canonical
	// form, in case that helps users with debugging when things aren't
	// working as expected. (Perhaps the normalization is part of the cause.)
	dispHostname := hostname.ForDisplay()

	creds := c.Services.CredentialsSource().(*cliconfig.CredentialsSource)
	filename, _ := creds.CredentialsFilePath()
	credsCtx := &loginCredentialsContext{
		Location:      creds.HostCredentialsLocation(hostname),
		LocalFilename: filename, // empty in the very unlikely event that we can't select a config directory for this user
		HelperType:    creds.CredentialsHelperType(),
	}

	if credsCtx.Location == cliconfig.CredentialsInOtherFile {
		diags = diags.Append(tfdiags.Sourceless(
			tfdiags.Error,
			fmt.Sprintf("Credentials for %s are manually configured", dispHostname),
			"The \"tofu logout\" command cannot log out because credentials for this host are manually configured in a CLI configuration file.\n\nTo log out, revoke the existing credentials and remove that block from the CLI configuration.",
		))
	}

	if diags.HasErrors() {
		c.showDiagnostics(diags)
		return 1
	}

	switch credsCtx.Location {
	case cliconfig.CredentialsNotAvailable:
		c.Ui.Output(fmt.Sprintf("No credentials for %s are stored.\n", dispHostname))
		return 0
	case cliconfig.CredentialsViaHelper:
		c.Ui.Output(fmt.Sprintf("Removing the stored credentials for %s from the configured\n%q credentials helper.\n", dispHostname, credsCtx.HelperType))
	case cliconfig.CredentialsInPrimaryFile:
		c.Ui.Output(fmt.Sprintf("Removing the stored credentials for %s from the following file:\n    %s\n", dispHostname, credsCtx.LocalFilename))
	}

	err = creds.ForgetForHost(ctx, hostname)
	if err != nil {
		diags = diags.Append(tfdiags.Sourceless(
			tfdiags.Error,
			"Failed to remove API token",
			fmt.Sprintf("Unable to remove stored API token: %s", err),
		))
	}

	c.showDiagnostics(diags)
	if diags.HasErrors() {
		return 1
	}

	c.Ui.Output(
		fmt.Sprintf(
			c.Colorize().Color(strings.TrimSpace(`
[green][bold]Success![reset] [bold]OpenTofu has removed the stored API token for %s.[reset]
`)),
			dispHostname,
		) + "\n",
	)

	return 0
}

// Help implements cli.Command.
func (c *LogoutCommand) Help() string {
	defaultFile := c.defaultOutputFile()
	if defaultFile == "" {
		// Because this is just for the help message and it's very unlikely
		// that a user wouldn't have a functioning home directory anyway,
		// we'll just use a placeholder here. The real command has some
		// more complex behavior for this case. This result is not correct
		// on all platforms, but given how unlikely we are to hit this case
		// that seems okay.
		defaultFile = "~/.terraform/credentials.tfrc.json"
	}

	helpText := fmt.Sprintf(`
Usage: tofu [global options] logout [hostname]

  Removes locally-stored credentials for specified hostname.

  Note: the API token is only removed from local storage, not destroyed on the
  remote server, so it will remain valid until manually revoked.
      %s
`, defaultFile)
	return strings.TrimSpace(helpText)
}

// Synopsis implements cli.Command.
func (c *LogoutCommand) Synopsis() string {
	return "Remove locally-stored credentials for a remote host"
}

func (c *LogoutCommand) defaultOutputFile() string {
	if c.CLIConfigDir == "" {
		return "" // no default available
	}
	return filepath.Join(c.CLIConfigDir, "credentials.tfrc.json")
}
