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

package cloud

import (
	"github.com/opentofu/opentofu/internal/command/clistate"
	"github.com/opentofu/opentofu/internal/configs"
)

// Most of the logic for migrating into and out of "cloud mode" actually lives
// in the "command" package as part of the general backend init mechanisms,
// but we have some cloud-specific helper functionality here.

// ConfigChangeMode is a rough way to think about different situations that
// our backend change and state migration codepaths need to distinguish in
// the context of Cloud integration mode.
type ConfigChangeMode rune

//go:generate go tool golang.org/x/tools/cmd/stringer -type ConfigChangeMode

const (
	// ConfigMigrationIn represents when the configuration calls for using
	// Cloud mode but the working directory state disagrees.
	ConfigMigrationIn ConfigChangeMode = '↘'

	// ConfigMigrationOut represents when the working directory state calls
	// for using Cloud mode but the working directory state disagrees.
	ConfigMigrationOut ConfigChangeMode = '↖'

	// ConfigChangeInPlace represents when both the working directory state
	// and the config call for using Cloud mode, and so there might be
	// (but won't necessarily be) cloud settings changing, but we don't
	// need to do any actual migration.
	ConfigChangeInPlace ConfigChangeMode = '↻'

	// ConfigChangeIrrelevant represents when the config and working directory
	// state disagree but neither calls for using Cloud mode, and so the
	// Cloud integration is not involved in dealing with this.
	ConfigChangeIrrelevant ConfigChangeMode = '🤷'
)

// DetectConfigChangeType encapsulates the fiddly logic for deciding what kind
// of Cloud configuration change we seem to be making, based on the existing
// working directory state (if any) and the current configuration.
//
// This is a pretty specialized sort of thing focused on finicky details of
// the way we currently model working directory settings and config, so its
// signature probably won't survive any non-trivial refactoring of how
// the CLI layer thinks about backends/state storage.
func DetectConfigChangeType(wdState *clistate.BackendState, config *configs.Backend, haveLocalStates bool) ConfigChangeMode {
	// Although externally the cloud integration isn't really a "backend",
	// internally we treat it a bit like one just to preserve all of our
	// existing interfaces that assume backends. "cloud" is the placeholder
	// name we use for it, even though that isn't a backend that's actually
	// available for selection in the usual way.
	wdIsCloud := wdState != nil && wdState.Type == "cloud"
	configIsCloud := config != nil && config.Type == "cloud"

	// "uninit" here means that the working directory is totally uninitialized,
	// even taking into account the possibility of implied local state that
	// therefore doesn't typically require explicit "tofu init".
	wdIsUninit := wdState == nil && !haveLocalStates

	switch {
	case configIsCloud:
		switch {
		case wdIsCloud || wdIsUninit:
			// If config has cloud and the working directory is completely
			// uninitialized then we assume we're doing the initial activation
			// of this working directory for an already-migrated-to-cloud
			// remote state.
			return ConfigChangeInPlace
		default:
			// Otherwise, we seem to be migrating into cloud mode from a backend.
			return ConfigMigrationIn
		}
	default:
		switch {
		case wdIsCloud:
			// If working directory is already cloud but config isn't, we're
			// migrating away from cloud to a backend.
			return ConfigMigrationOut
		default:
			// Otherwise, this situation seems to be something unrelated to
			// cloud mode and so outside of our scope here.
			return ConfigChangeIrrelevant
		}
	}

}

func (m ConfigChangeMode) InvolvesCloud() bool {
	switch m {
	case ConfigMigrationIn, ConfigMigrationOut, ConfigChangeInPlace:
		return true
	default:
		return false
	}
}

func (m ConfigChangeMode) IsCloudMigration() bool {
	switch m {
	case ConfigMigrationIn, ConfigMigrationOut:
		return true
	default:
		return false
	}
}
