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

package sanitize

import (
	"fmt"

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

type SanitizeStateModuleChangeMode string

const (
	SanitizeStateModuleChangeModeBefore SanitizeStateModuleChangeMode = "before_sensitive"
	SanitizeStateModuleChangeModeAfter  SanitizeStateModuleChangeMode = "after_sensitive"
)

// SanitizeStateModule traverses a StateModule, consulting the
// supplied ResourceChange set for resources to determine whether or
// not particular values should be obfuscated.
//
// Use mode to supply the SanitizeStateModuleChangeMode that
// represents what sensitive field should be consulted to determine
// whether or not the value should be obfuscated:
//
// * SanitizeStateModuleChangeModeBefore for before_sensitive
// * SanitizeStateModuleChangeModeAfter for after_sensitive
//
// Sensitive values are replaced with the supplied replaceWith value.
// A new state module tree is issued.
func SanitizeStateModule(
	old *tfjson.StateModule,
	resourceChanges []*tfjson.ResourceChange,
	mode SanitizeStateModuleChangeMode,
	replaceWith interface{},
) (*tfjson.StateModule, error) {
	result := &tfjson.StateModule{
		Resources:    make([]*tfjson.StateResource, len(old.Resources)),
		Address:      old.Address,
		ChildModules: make([]*tfjson.StateModule, len(old.ChildModules)),
	}

	for i := range old.Resources {
		var err error
		result.Resources[i], err = sanitizeStateResource(
			old.Resources[i],
			findResourceChange(resourceChanges, old.Resources[i].Address),
			mode,
			replaceWith,
		)
		if err != nil {
			return nil, err
		}
	}

	for i := range old.ChildModules {
		var err error
		result.ChildModules[i], err = SanitizeStateModule(
			old.ChildModules[i],
			resourceChanges,
			mode,
			replaceWith,
		)
		if err != nil {
			return nil, err
		}
	}

	return result, nil
}

func sanitizeStateResource(
	old *tfjson.StateResource,
	rc *tfjson.ResourceChange,
	mode SanitizeStateModuleChangeMode,
	replaceWith interface{},
) (*tfjson.StateResource, error) {
	result, err := copyStateResource(old)
	if err != nil {
		return nil, err
	}

	if rc == nil {
		return result, nil
	}

	var sensitive interface{}
	switch mode {
	case SanitizeStateModuleChangeModeBefore:
		sensitive = rc.Change.BeforeSensitive

	case SanitizeStateModuleChangeModeAfter:
		sensitive = rc.Change.AfterSensitive

	default:
		panic(fmt.Sprintf("invalid change mode %q", mode))
	}

	// We can re-use sanitizeChangeValue here to do the sanitization.
	result.AttributeValues = sanitizeChangeValue(result.AttributeValues, sensitive, replaceWith).(map[string]interface{})
	return result, nil
}

func findResourceChange(resourceChanges []*tfjson.ResourceChange, addr string) *tfjson.ResourceChange {
	// Linear search here, unfortunately :P
	for _, rc := range resourceChanges {
		if rc.Address == addr {
			return rc
		}
	}

	return nil
}

// SanitizeStateOutputs scans the supplied map of StateOutputs and
// replaces any values of outputs marked as Sensitive with the value
// supplied in replaceWith.
//
// A new copy of StateOutputs is returned.
func SanitizeStateOutputs(old map[string]*tfjson.StateOutput, replaceWith interface{}) (map[string]*tfjson.StateOutput, error) {
	result, err := copyStateOutputs(old)
	if err != nil {
		return nil, err
	}

	for k := range result {
		if result[k].Sensitive {
			result[k].Value = replaceWith
		}
	}

	return result, nil
}
