// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1

package command

import (
	"fmt"

	"github.com/hashicorp/hcl/v2"
	"github.com/hashicorp/terraform/internal/tfdiags"
)

// errWrongWorkspaceForPlan is a custom error used to alert users that the plan file they are applying
// describes a workspace that doesn't match the currently selected workspace.
//
// This needs to render slightly different errors depending on whether we're using:
// > CE Workspaces (remote-state backends, local backends)
// > HCP Terraform Workspaces (cloud backend)
type errWrongWorkspaceForPlan struct {
	plannedWorkspace string
	currentWorkspace string
	isCloud          bool
}

func (e *errWrongWorkspaceForPlan) Error() string {
	msg := fmt.Sprintf(`The plan file describes changes to the %q workspace, but the %q workspace is currently in use.

Applying this plan with the incorrect workspace selected could result in state being stored in an unexpected location, or a downstream error when Terraform attempts apply a plan using the other workspace's state.`,
		e.plannedWorkspace,
		e.currentWorkspace,
	)

	// For users to understand what's happened and how to correct it we'll give some guidance,
	// but that guidance depends on whether a cloud backend is in use or not.
	if e.isCloud {
		// When using the cloud backend the solution is to focus on the cloud block and running init
		msg = msg + fmt.Sprintf(` If you'd like to continue to use the plan file, make sure the cloud block in your configuration contains the workspace name %q.
In future, make sure your cloud block is correct and unchanged since the last time you performed "terraform init" before creating a plan.`, e.plannedWorkspace)
	} else {
		// When using the backend block the solution is to not select a different workspace
		// between plan and apply operations.
		msg = msg + fmt.Sprintf(` If you'd like to continue to use the plan file, you must run "terraform workspace select %s" to select the matching workspace.
In future make sure the selected workspace is not changed between creating and applying a plan file.
`, e.plannedWorkspace)
	}

	return msg
}

// errBackendLocalRead is a custom error used to alert users that state
// files on their local filesystem were not erased successfully after
// migrating that state to a remote-state backend.
type errBackendLocalRead struct {
	innerError error
}

func (e *errBackendLocalRead) Error() string {
	return fmt.Sprintf(`Error reading local state: %s

Terraform is trying to read your local state to determine if there is
state to migrate to your newly configured backend. Terraform can't continue
without this check because that would risk losing state. Please resolve the
error above and try again.`, e.innerError)
}

// errBackendMigrateLocalDelete is a custom error used to alert users that state
// files on their local filesystem were not erased successfully after migrating
// that state to a remote-state backend.
type errBackendMigrateLocalDelete struct {
	innerError error
}

func (e *errBackendMigrateLocalDelete) Error() string {
	return fmt.Sprintf(`Error deleting local state after migration: %s

Your local state is deleted after successfully migrating it to the newly
configured backend. As part of the deletion process, a backup is made at
the standard backup path unless explicitly asked not to. To cleanly operate
with a backend, we must delete the local state file. Please resolve the
issue above and retry the command.`, e.innerError)
}

// errBackendSavedUnknown is a custom error used to alert users that their
// configuration describes a backend that's not implemented in Terraform.
type errBackendNewUnknown struct {
	backendName string
}

func (e *errBackendNewUnknown) Error() string {
	return fmt.Sprintf(`The backend %q could not be found.

This is the backend specified in your Terraform configuration file.
This error could be a simple typo in your configuration, but it can also
be caused by using a Terraform version that doesn't support the specified
backend type. Please check your configuration and your Terraform version.

If you'd like to run Terraform and store state locally, you can fix this
error by removing the backend configuration from your configuration.`, e.backendName)
}

// errBackendSavedUnknown is a custom error used to alert users that their
// plan file describes a backend that's not implemented in Terraform.
type errBackendSavedUnknown struct {
	backendName string
}

func (e *errBackendSavedUnknown) Error() string {
	return fmt.Sprintf(`The backend %q could not be found.

This is the backend that this Terraform environment is configured to use
both in your configuration and saved locally as your last-used backend.
If it isn't found, it could mean an alternate version of Terraform was
used with this configuration. Please use the proper version of Terraform that
contains support for this backend.

If you'd like to force remove this backend, you must update your configuration
to not use the backend and run "terraform init" (or any other command) again.`, e.backendName)
}

// errBackendClearSaved is a custom error used to alert users that
// Terraform failed to empty the backend state file's contents.
type errBackendClearSaved struct {
	innerError error
}

func (e *errBackendClearSaved) Error() string {
	return fmt.Sprintf(`Error clearing the backend configuration: %s

Terraform removes the saved backend configuration when you're removing a
configured backend. This must be done so future Terraform runs know to not
use the backend configuration. Please look at the error above, resolve it,
and try again.`, e.innerError)
}

// errBackendInitDiag creates a diagnostic to present to users when
// users attempt to run a non-init command after making a change to their
// backend configuration.
//
// An init reason should be provided as an argument.
func errBackendInitDiag(initReason string) tfdiags.Diagnostic {
	msg := fmt.Sprintf(`Reason: %s

The "backend" is the interface that Terraform uses to store state,
perform operations, etc. If this message is showing up, it means that the
Terraform configuration you're using is using a custom configuration for
the Terraform backend.

Changes to backend configurations require reinitialization. This allows
Terraform to set up the new configuration, copy existing state, etc. Please run
"terraform init" with either the "-reconfigure" or "-migrate-state" flags to
use the current configuration.

If the change reason above is incorrect, please verify your configuration
hasn't changed and try again. At this point, no changes to your existing
configuration or state have been made.`, initReason)

	return tfdiags.Sourceless(
		tfdiags.Error,
		"Backend initialization required, please run \"terraform init\"",
		msg,
	)
}

// errBackendToStateStoreInitDiag is a variation of errBackendInitDiag specific to when
// migrating from a backend to a state store. Text needs to resemble the old backend-specific diagnostic,
// but the recommended actions are different (using new terraform state migrate command).
func errBackendToStateStoreInitDiag(initReason string) tfdiags.Diagnostic {
	msg := fmt.Sprintf(`Reason: %s

The "backend" is the interface that Terraform uses to store state,
perform operations, etc. If this message is showing up, it means that the
Terraform configuration you're using is using a custom configuration for
the Terraform backend.

Changes to backend configurations require reinitialization. This allows
Terraform to set up the new configuration, copy existing state, etc. Please run
"terraform state migrate" to migrate existing state to the new state store,
or run "terraform init -reconfigure" to use the current configuration without
migrating existing state.

If the change reason above is incorrect, please verify your configuration
hasn't changed and try again. At this point, no changes to your existing
configuration or state have been made.`, initReason)

	return tfdiags.Sourceless(
		tfdiags.Error,
		"Backend initialization required, please run \"terraform state migrate\" or \"terraform init -reconfigure\"",
		msg,
	)
}

type ssInitReason struct {
	MigrationNeeded bool
	Reason          string
	Subject         *hcl.Range
}

// errStateStoreInitDiag creates a diagnostic to present to users when
// users attempt to run a non-init command after making a change to their
// state_store configuration.
func errStateStoreInitDiag(ir *ssInitReason) tfdiags.Diagnostics {
	if ir == nil {
		panic("errStateStoreInitDiag requires a non-nil reason argument")
	}

	var msg string
	msg += fmt.Sprintf("Reason: %s\n\n", ir.Reason)

	msg += `A "state store" is an interface that Terraform uses to store state.
Changes to state store configurations require reinitialization, and a
decision whether to migrate any existing state or not.

If you want to migrate existing state using the current configuration,
please run "terraform state migrate".

If you don't want to migrate existing state and just reconfigure how state is
stored using the current configuration, please run "terraform init -reconfigure".

If the change reason above is incorrect, please verify your configuration
hasn't changed and try again. At this point, no changes to your existing
configuration or state have been made.`

	var diags tfdiags.Diagnostics

	var summary string
	if ir.MigrationNeeded {
		summary = "State store initialization required, please run \"terraform state migrate\" or \"terraform init -reconfigure\""
	} else {
		// When no migration is needed we're just initializing a state store for the first time.
		summary = "State store initialization required, please run \"terraform init\""
	}

	if ir.Subject != nil {
		diags = diags.Append(&hcl.Diagnostic{
			Subject:  ir.Subject,
			Severity: hcl.DiagError,
			Summary:  summary,
			Detail:   msg,
		})
		return diags
	}

	diags = diags.Append(tfdiags.Sourceless(
		tfdiags.Error,
		summary,
		msg,
	))

	return diags
}

// errBackendInitCloudDiag creates a diagnostic to present to users when
// an init command encounters config changes in a `cloud` block.
//
// An init reason should be provided as an argument.
func errBackendInitCloudDiag(initReason string) tfdiags.Diagnostic {
	msg := fmt.Sprintf(`Reason: %s.

Changes to the HCP Terraform configuration block require reinitialization, to discover any changes to the available workspaces.

To re-initialize, run:
  terraform init

Terraform has not yet made changes to your existing configuration or state.`, initReason)

	return tfdiags.Sourceless(
		tfdiags.Error,
		"HCP Terraform or Terraform Enterprise initialization required: please run \"terraform init\"",
		msg,
	)
}

// errBackendWriteSavedDiag creates a diagnostic to present to users when
// an init command experiences an error while writing to the backend state file.
func errBackendWriteSavedDiag(innerError error) tfdiags.Diagnostic {
	msg := fmt.Sprintf(`Error saving the backend configuration: %s

Terraform saves the complete backend configuration in a local file for
configuring the backend on future operations. This cannot be disabled. Errors
are usually due to simple file permission errors. Please look at the error
above, resolve it, and try again.`, innerError)

	return tfdiags.Sourceless(
		tfdiags.Error,
		"Backend initialization failed",
		msg,
	)
}

// errStateStoreWriteSavedDiag creates a diagnostic to present to users when
// an init command experiences an error while writing to the backend state file.
func errStateStoreWriteSavedDiag(innerError error) tfdiags.Diagnostic {
	msg := fmt.Sprintf(`Error saving the state store configuration: %s

Terraform saves the complete state store configuration in a local file for
configuring the state store on future operations. This cannot be disabled. Errors
are usually due to simple file permission errors. Please look at the error
above, resolve it, and try again.`, innerError)

	return tfdiags.Sourceless(
		tfdiags.Error,
		"State store initialization failed",
		msg,
	)
}

// errBackendNoExistingWorkspaces is returned by calling code when it expects a backend.Backend
// to report one or more workspaces exist.
//
// The returned error may be used as a sentinel error and acted upon or just wrapped in a
// diagnostic and returned.
type errBackendNoExistingWorkspaces struct{}

func (e *errBackendNoExistingWorkspaces) Error() string {
	return `No existing workspaces.

Use the "terraform workspace" command to create and select a new workspace.
If the backend already contains existing workspaces, you may need to update
the backend configuration.`
}

// migrateOrReconfigDiag creates a diagnostic to present to users when
// an init command encounters a mismatch in backend state and the current config
// and Terraform needs users to provide additional instructions about how Terraform
// should proceed.
var migrateOrReconfigDiag = tfdiags.Sourceless(
	tfdiags.Error,
	"Backend configuration changed",
	"A change in the backend configuration has been detected, which may require migrating existing state.\n\n"+
		"If you wish to attempt automatic migration of the state, run \"terraform init -migrate-state\".\n"+
		`If you wish to store the current configuration with no changes to the state, run "terraform init -reconfigure".`)
