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

package sanitize

import (
	"errors"

	tfjson "github.com/hashicorp/terraform-json"
)

const DefaultSensitiveValue = "REDACTED_SENSITIVE"

var NilPlanError = errors.New("nil plan supplied")

// SanitizePlan sanitizes the entirety of a Plan, replacing sensitive
// values with the default value in DefaultSensitiveValue.
//
// See SanitizePlanWithValue for full detail on the where replacement
// takes place.
func SanitizePlan(old *tfjson.Plan) (*tfjson.Plan, error) {
	return SanitizePlanWithValue(old, DefaultSensitiveValue)
}

// SanitizePlanWithValue sanitizes the entirety of a Plan to the best
// of its ability, depending on the provided metadata on sensitive
// values. These are found in:
//
// * ResourceChanges: Sanitized based on BeforeSensitive and
// AfterSensitive fields.
//
// * Variables: Based on variable config data found in the root
// module of the Config.
//
// * PlannedValues: Sanitized based on the values found in
// AfterSensitive in ResourceChanges. Outputs are sanitized
// according to the appropriate sensitivity flags provided for the
// output.
//
// * PriorState: Sanitized based on the values found in
// BeforeSensitive in ResourceChanges. Outputs are sanitized according
// to the appropriate sensitivity flags provided for the output.
//
// * OutputChanges: Sanitized based on the values found in
// BeforeSensitive and AfterSensitive. This generally means that
// any sensitive output will have OutputChange fully obfuscated as
// the BeforeSensitive and AfterSensitive in outputs are opaquely the
// same.
//
// Sensitive values are replaced with the value supplied with
// replaceWith. A copy of the Plan is returned.
func SanitizePlanWithValue(old *tfjson.Plan, replaceWith interface{}) (*tfjson.Plan, error) {
	if old == nil {
		return nil, NilPlanError
	}

	result, err := copyPlan(old)
	if err != nil {
		return nil, err
	}

	// Sanitize ResourceChanges
	for i := range result.ResourceChanges {
		result.ResourceChanges[i].Change, err = SanitizeChange(result.ResourceChanges[i].Change, replaceWith)
		if err != nil {
			return nil, err
		}
	}

	// Sanitize Variables
	result.Variables, err = SanitizePlanVariables(result.Variables, result.Config.RootModule.Variables, replaceWith)
	if err != nil {
		return nil, err
	}

	// Sanitize PlannedValues
	result.PlannedValues.RootModule, err = SanitizeStateModule(
		result.PlannedValues.RootModule,
		result.ResourceChanges,
		SanitizeStateModuleChangeModeAfter,
		replaceWith)
	if err != nil {
		return nil, err
	}

	result.PlannedValues.Outputs, err = SanitizeStateOutputs(result.PlannedValues.Outputs, replaceWith)
	if err != nil {
		return nil, err
	}

	// Sanitize PriorState
	if result.PriorState != nil {
		result.PriorState.Values.RootModule, err = SanitizeStateModule(
			result.PriorState.Values.RootModule,
			result.ResourceChanges,
			SanitizeStateModuleChangeModeBefore,
			replaceWith)
		if err != nil {
			return nil, err
		}

		result.PriorState.Values.Outputs, err = SanitizeStateOutputs(result.PriorState.Values.Outputs, replaceWith)
		if err != nil {
			return nil, err
		}
	}

	// Sanitize OutputChanges
	for k := range result.OutputChanges {
		result.OutputChanges[k], err = SanitizeChange(result.OutputChanges[k], replaceWith)
		if err != nil {
			return nil, err
		}
	}

	return result, nil
}
