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

package plans

import (
	"fmt"

	"github.com/opentofu/opentofu/internal/addrs"
	"github.com/opentofu/opentofu/internal/providers"
	"github.com/opentofu/opentofu/internal/states"
	"github.com/zclconf/go-cty/cty"
	ctyjson "github.com/zclconf/go-cty/cty/json"
)

// ResourceInstanceChangeSrc is a not-yet-decoded ResourceInstanceChange.
// Pass the associated resource type's schema type to method Decode to
// obtain a ResourceInstanceChange.
type ResourceInstanceChangeSrc struct {
	// Addr is the absolute address of the resource instance that the change
	// will apply to.
	Addr addrs.AbsResourceInstance

	// PrevRunAddr is the absolute address that this resource instance had at
	// the conclusion of a previous run.
	//
	// This will typically be the same as Addr, but can be different if the
	// previous resource instance was subject to a "moved" block that we
	// handled in the process of creating this plan.
	//
	// For the initial creation of a resource instance there isn't really any
	// meaningful "previous run address", but PrevRunAddr will still be set
	// equal to Addr in that case in order to simplify logic elsewhere which
	// aims to detect and react to the movement of instances between addresses.
	PrevRunAddr addrs.AbsResourceInstance

	// DeposedKey is the identifier for a deposed object associated with the
	// given instance, or states.NotDeposed if this change applies to the
	// current object.
	//
	// A Replace change for a resource with create_before_destroy set will
	// create a new DeposedKey temporarily during replacement. In that case,
	// DeposedKey in the plan is always states.NotDeposed, representing that
	// the current object is being replaced with the deposed.
	DeposedKey states.DeposedKey

	// Provider is the address of the provider configuration that was used
	// to plan this change, and thus the configuration that must also be
	// used to apply it.
	ProviderAddr addrs.AbsProviderConfig

	// ChangeSrc is an embedded description of the not-yet-decoded change.
	ChangeSrc

	// ActionReason is an optional extra indication of why we chose the
	// action recorded in Change.Action for this particular resource instance.
	//
	// This is an approximate mechanism only for the purpose of explaining the
	// plan to end-users in the UI and is not to be used for any
	// decision-making during the apply step; if apply behavior needs to vary
	// depending on the "action reason" then the information for that decision
	// must be recorded more precisely elsewhere for that purpose.
	//
	// See the field of the same name in ResourceInstanceChange for more
	// details.
	ActionReason ResourceInstanceChangeActionReason

	// RequiredReplace is a set of paths that caused the change action to be
	// Replace rather than Update. Always nil if the change action is not
	// Replace.
	RequiredReplace cty.PathSet

	// Private allows a provider to stash any extra data that is opaque to
	// OpenTofu that relates to this change. OpenTofu will save this
	// byte-for-byte and return it to the provider in the apply call.
	Private []byte
}

// Decode unmarshals the raw representation of the instance object being
// changed. Pass the implied type of the corresponding resource type schema
// for correct operation.
func (rcs *ResourceInstanceChangeSrc) Decode(schema *providers.Schema) (*ResourceInstanceChange, error) {
	change, err := rcs.ChangeSrc.Decode(schema)
	if err != nil {
		return nil, err
	}
	prevRunAddr := rcs.PrevRunAddr
	if prevRunAddr.Resource.Resource.Type == "" {
		// Suggests an old caller that hasn't been properly updated to
		// populate this yet.
		prevRunAddr = rcs.Addr
	}
	return &ResourceInstanceChange{
		Addr:            rcs.Addr,
		PrevRunAddr:     prevRunAddr,
		DeposedKey:      rcs.DeposedKey,
		ProviderAddr:    rcs.ProviderAddr,
		Change:          *change,
		ActionReason:    rcs.ActionReason,
		RequiredReplace: rcs.RequiredReplace,
		Private:         rcs.Private,
	}, nil
}

// DeepCopy creates a copy of the receiver where any pointers to nested mutable
// values are also copied, thus ensuring that future mutations of the receiver
// will not affect the copy.
//
// Some types used within a resource change are immutable by convention even
// though the Go language allows them to be mutated, such as the types from
// the addrs package. These are _not_ copied by this method, under the
// assumption that callers will behave themselves.
func (rcs *ResourceInstanceChangeSrc) DeepCopy() *ResourceInstanceChangeSrc {
	if rcs == nil {
		return nil
	}
	ret := *rcs

	ret.RequiredReplace = cty.NewPathSet(ret.RequiredReplace.List()...)

	if len(ret.Private) != 0 {
		private := make([]byte, len(ret.Private))
		copy(private, ret.Private)
		ret.Private = private
	}

	ret.ChangeSrc.Before = ret.ChangeSrc.Before.Copy()
	ret.ChangeSrc.After = ret.ChangeSrc.After.Copy()
	ret.ChangeSrc.BeforeIdentity = ret.ChangeSrc.BeforeIdentity.Copy()
	ret.ChangeSrc.AfterIdentity = ret.ChangeSrc.AfterIdentity.Copy()

	if ret.ChangeSrc.Importing != nil {
		importing := *ret.ChangeSrc.Importing
		importing.Identity = importing.Identity.Copy()
		ret.ChangeSrc.Importing = &importing
	}

	return &ret
}

func (rcs *ResourceInstanceChangeSrc) Moved() bool {
	return !rcs.Addr.Equal(rcs.PrevRunAddr)
}

// OutputChangeSrc describes a change to an output value.
type OutputChangeSrc struct {
	// Addr is the absolute address of the output value that the change
	// will apply to.
	Addr addrs.AbsOutputValue

	// ChangeSrc is an embedded description of the not-yet-decoded change.
	//
	// For output value changes, the type constraint for the DynamicValue
	// instances is always cty.DynamicPseudoType.
	ChangeSrc

	// Sensitive, if true, indicates that either the old or new value in the
	// change is sensitive and so a rendered version of the plan in the UI
	// should elide the actual values while still indicating the action of the
	// change.
	Sensitive bool
}

// Decode unmarshals the raw representation of the output value being
// changed.
func (ocs *OutputChangeSrc) Decode() (*OutputChange, error) {
	change, err := ocs.ChangeSrc.Decode(nil)
	if err != nil {
		return nil, err
	}
	return &OutputChange{
		Addr:      ocs.Addr,
		Change:    *change,
		Sensitive: ocs.Sensitive,
	}, nil
}

// DeepCopy creates a copy of the receiver where any pointers to nested mutable
// values are also copied, thus ensuring that future mutations of the receiver
// will not affect the copy.
//
// Some types used within a resource change are immutable by convention even
// though the Go language allows them to be mutated, such as the types from
// the addrs package. These are _not_ copied by this method, under the
// assumption that callers will behave themselves.
func (ocs *OutputChangeSrc) DeepCopy() *OutputChangeSrc {
	if ocs == nil {
		return nil
	}
	ret := *ocs

	ret.ChangeSrc.Before = ret.ChangeSrc.Before.Copy()
	ret.ChangeSrc.After = ret.ChangeSrc.After.Copy()
	ret.ChangeSrc.BeforeIdentity = ret.ChangeSrc.BeforeIdentity.Copy()
	ret.ChangeSrc.AfterIdentity = ret.ChangeSrc.AfterIdentity.Copy()

	if ret.ChangeSrc.Importing != nil {
		importing := *ret.ChangeSrc.Importing
		importing.Identity = importing.Identity.Copy()
		ret.ChangeSrc.Importing = &importing
	}

	return &ret
}

// ImportingSrc is the part of a ChangeSrc that describes the embedded import
// action.
//
// The fields in here are subject to change, so downstream consumers should be
// prepared for backwards compatibility in case the contents changes.
type ImportingSrc struct {
	// ID is the original ID of the imported resource.
	ID string

	// Identity is an alternative way to import a resource, using its identity
	// attributes instead of its ID.
	//
	// This is mutually exclusive with ID; and only one of the two should be set.
	Identity DynamicValue
}

// String returns a human-readable representation of the import source.
// It returns the ID if set, or a rendered identity value if available.
func (i ImportingSrc) String() string {
	if i.ID != "" {
		return i.ID
	}
	if len(i.Identity) == 0 {
		return ""
	}
	ty, err := i.Identity.ImpliedType()
	if err != nil {
		return "<identity>"
	}
	val, err := i.Identity.Decode(ty)
	if err != nil {
		return "<identity>"
	}

	jsonBytes, err := ctyjson.Marshal(val, ty)
	if err != nil {
		return "<identity>"
	}
	return string(jsonBytes)
}

// ChangeSrc is a not-yet-decoded Change.
type ChangeSrc struct {
	// Action defines what kind of change is being made.
	Action Action

	// Before and After correspond to the fields of the same name in Change,
	// but have not yet been decoded from the serialized value used for
	// storage.
	Before, After DynamicValue

	// BeforeValMarks and AfterValMarks are stored path+mark combinations
	// that might be discovered when encoding a change. Marks are removed
	// to enable encoding (marked values cannot be marshalled), and so storing
	// the path+mark combinations allow us to re-mark the value later
	// when, for example, displaying the diff to the UI.
	BeforeValMarks, AfterValMarks []cty.PathValueMarks

	// Importing is present if the resource is being imported as part of this
	// change.
	//
	// Use the simple presence of this field to detect if a ChangeSrc is to be
	// imported, the contents of this structure may be modified going forward.
	Importing *ImportingSrc

	// GeneratedConfig contains any HCL config generated for this resource
	// during planning, as a string. If GeneratedConfig is populated, Importing
	// should be true. However, not all Importing changes contain generated
	// config.
	GeneratedConfig string

	// BeforeIdentity is the identity value from the known state of the resource instance
	// before the plan is executed.
	BeforeIdentity DynamicValue

	// AfterIdentity is the serialized identity value returned by the provider
	// during planning. Only relevant for managed resources, not outputs.
	AfterIdentity DynamicValue
}

// Decode unmarshals the raw representations of the before and after values
// to produce a Change object.
//
// Where a ChangeSrc is embedded in some other struct, it's generally better
// to call the corresponding Decode method of that struct rather than working
// directly with its embedded Change.
func (cs *ChangeSrc) Decode(schema *providers.Schema) (*Change, error) {
	var ty cty.Type

	if schema == nil {
		// we've been passed a nil set of schema, we should use a dynamic type here
		ty = cty.DynamicPseudoType
	} else {
		ty = schema.Block.ImpliedType()
	}

	var err error
	before := cty.NullVal(ty)
	after := cty.NullVal(ty)

	if len(cs.Before) > 0 {
		before, err = cs.Before.Decode(ty)
		if err != nil {
			return nil, fmt.Errorf("error decoding 'before' value: %w", err)
		}
	}
	if len(cs.After) > 0 {
		after, err = cs.After.Decode(ty)
		if err != nil {
			return nil, fmt.Errorf("error decoding 'after' value: %w", err)
		}
	}

	var importing *Importing
	if cs.Importing != nil {
		if len(cs.Importing.Identity) > 0 {
			var identityTy cty.Type
			if schema != nil && schema.IdentitySchema != nil {
				identityTy = schema.IdentitySchema.ImpliedType()
			} else {
				identityTy, err = cs.Importing.Identity.ImpliedType()
				if err != nil {
					return nil, fmt.Errorf("error determining importing identity type: %w", err)
				}
			}
			decodedIdentity, err := cs.Importing.Identity.Decode(identityTy)
			if err != nil {
				return nil, fmt.Errorf("error decoding importing identity value: %w", err)
			}
			importing = &Importing{
				Identity: decodedIdentity,
			}
		} else {
			importing = &Importing{
				ID: cs.Importing.ID,
			}
		}
	}

	afterIdentity := cty.NullVal(cty.DynamicPseudoType)
	if len(cs.AfterIdentity) > 0 {
		var identityTy cty.Type
		if schema != nil && schema.IdentitySchema != nil {
			identityTy = schema.IdentitySchema.ImpliedType()
		} else {
			identityTy, err = cs.AfterIdentity.ImpliedType()
			if err != nil {
				return nil, fmt.Errorf("error determining after identity type: %w", err)
			}
		}
		afterIdentity, err = cs.AfterIdentity.Decode(identityTy)
		if err != nil {
			return nil, fmt.Errorf("error decoding after identity value: %w", err)
		}
	}

	beforeIdentity := cty.NullVal(cty.DynamicPseudoType)
	if len(cs.BeforeIdentity) > 0 {
		var identityTy cty.Type
		if schema != nil && schema.IdentitySchema != nil {
			identityTy = schema.IdentitySchema.ImpliedType()
		} else {
			identityTy, err = cs.BeforeIdentity.ImpliedType()
			if err != nil {
				return nil, fmt.Errorf("error determining before identity type: %w", err)
			}
		}
		beforeIdentity, err = cs.BeforeIdentity.Decode(identityTy)
		if err != nil {
			return nil, fmt.Errorf("error decoding before identity value: %w", err)
		}
	}

	return &Change{
		Action:          cs.Action,
		Before:          before.MarkWithPaths(cs.BeforeValMarks),
		After:           after.MarkWithPaths(cs.AfterValMarks),
		Importing:       importing,
		GeneratedConfig: cs.GeneratedConfig,
		BeforeIdentity:  beforeIdentity,
		AfterIdentity:   afterIdentity,
	}, nil
}
