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

package typeexpr

import (
	"testing"

	"github.com/google/go-cmp/cmp"
	"github.com/zclconf/go-cty/cty"
)

var (
	valueComparer = cmp.Comparer(cty.Value.RawEquals)
)

func TestDefaults_Apply(t *testing.T) {
	simpleObject := cty.ObjectWithOptionalAttrs(map[string]cty.Type{
		"a": cty.String,
		"b": cty.Bool,
	}, []string{"b"})
	nestedObject := cty.ObjectWithOptionalAttrs(map[string]cty.Type{
		"c": simpleObject,
		"d": cty.Number,
	}, []string{"c"})

	testCases := map[string]struct {
		defaults *Defaults
		value    cty.Value
		want     cty.Value
	}{
		// Nothing happens when there are no default values and no children.
		"no defaults": {
			defaults: &Defaults{
				Type: cty.Map(cty.String),
			},
			value: cty.MapVal(map[string]cty.Value{
				"a": cty.StringVal("foo"),
				"b": cty.StringVal("bar"),
			}),
			want: cty.MapVal(map[string]cty.Value{
				"a": cty.StringVal("foo"),
				"b": cty.StringVal("bar"),
			}),
		},
		// Passing a map which does not include one of the attributes with a
		// default results in the default being applied to the output. Output
		// is always an object.
		"simple object with defaults applied": {
			defaults: &Defaults{
				Type: simpleObject,
				DefaultValues: map[string]cty.Value{
					"b": cty.True,
				},
			},
			value: cty.MapVal(map[string]cty.Value{
				"a": cty.StringVal("foo"),
			}),
			want: cty.MapVal(map[string]cty.Value{
				"a": cty.StringVal("foo"),
				"b": cty.StringVal("true"),
			}),
		},
		// Unknown values may be assigned to root modules during validation,
		// and we cannot apply defaults at that time.
		"simple object with defaults but unknown value": {
			defaults: &Defaults{
				Type: simpleObject,
				DefaultValues: map[string]cty.Value{
					"b": cty.True,
				},
			},
			value: cty.UnknownVal(cty.Map(cty.String)),
			want:  cty.UnknownVal(cty.Map(cty.String)),
		},
		// Defaults do not override attributes which are present in the given
		// value.
		"simple object with optional attributes specified": {
			defaults: &Defaults{
				Type: simpleObject,
				DefaultValues: map[string]cty.Value{
					"b": cty.True,
				},
			},
			value: cty.MapVal(map[string]cty.Value{
				"a": cty.StringVal("foo"),
				"b": cty.StringVal("false"),
			}),
			want: cty.MapVal(map[string]cty.Value{
				"a": cty.StringVal("foo"),
				"b": cty.StringVal("false"),
			}),
		},
		// Defaults will replace explicit nulls.
		"object with explicit null for attribute with default": {
			defaults: &Defaults{
				Type: simpleObject,
				DefaultValues: map[string]cty.Value{
					"b": cty.True,
				},
			},
			value: cty.MapVal(map[string]cty.Value{
				"a": cty.StringVal("foo"),
				"b": cty.NullVal(cty.String),
			}),
			want: cty.MapVal(map[string]cty.Value{
				"a": cty.StringVal("foo"),
				"b": cty.StringVal("true"),
			}),
		},
		// Defaults can be specified at any level of depth and will be applied
		// so long as there is a parent value to populate.
		"nested object with defaults applied": {
			defaults: &Defaults{
				Type: nestedObject,
				Children: map[string]*Defaults{
					"c": {
						Type: simpleObject,
						DefaultValues: map[string]cty.Value{
							"b": cty.False,
						},
					},
				},
			},
			value: cty.ObjectVal(map[string]cty.Value{
				"c": cty.ObjectVal(map[string]cty.Value{
					"a": cty.StringVal("foo"),
				}),
				"d": cty.NumberIntVal(5),
			}),
			want: cty.ObjectVal(map[string]cty.Value{
				"c": cty.ObjectVal(map[string]cty.Value{
					"a": cty.StringVal("foo"),
					"b": cty.False,
				}),
				"d": cty.NumberIntVal(5),
			}),
		},
		// Testing traversal of collections.
		"map of objects with defaults applied": {
			defaults: &Defaults{
				Type: cty.Map(simpleObject),
				Children: map[string]*Defaults{
					"": {
						Type: simpleObject,
						DefaultValues: map[string]cty.Value{
							"b": cty.True,
						},
					},
				},
			},
			value: cty.MapVal(map[string]cty.Value{
				"f": cty.ObjectVal(map[string]cty.Value{
					"a": cty.StringVal("foo"),
				}),
				"b": cty.ObjectVal(map[string]cty.Value{
					"a": cty.StringVal("bar"),
				}),
			}),
			want: cty.MapVal(map[string]cty.Value{
				"f": cty.ObjectVal(map[string]cty.Value{
					"a": cty.StringVal("foo"),
					"b": cty.True,
				}),
				"b": cty.ObjectVal(map[string]cty.Value{
					"a": cty.StringVal("bar"),
					"b": cty.True,
				}),
			}),
		},
		// A map variable value specified in a tfvars file will be an object,
		// in which case we must still traverse the defaults structure
		// correctly.
		"map of objects with defaults applied, given object instead of map": {
			defaults: &Defaults{
				Type: cty.Map(simpleObject),
				Children: map[string]*Defaults{
					"": {
						Type: simpleObject,
						DefaultValues: map[string]cty.Value{
							"b": cty.True,
						},
					},
				},
			},
			value: cty.ObjectVal(map[string]cty.Value{
				"f": cty.ObjectVal(map[string]cty.Value{
					"a": cty.StringVal("foo"),
				}),
				"b": cty.ObjectVal(map[string]cty.Value{
					"a": cty.StringVal("bar"),
				}),
			}),
			want: cty.ObjectVal(map[string]cty.Value{
				"f": cty.ObjectVal(map[string]cty.Value{
					"a": cty.StringVal("foo"),
					"b": cty.True,
				}),
				"b": cty.ObjectVal(map[string]cty.Value{
					"a": cty.StringVal("bar"),
					"b": cty.True,
				}),
			}),
		},
		// Another example of a collection type, this time exercising the code
		// processing a tuple input.
		"list of objects with defaults applied": {
			defaults: &Defaults{
				Type: cty.List(simpleObject),
				Children: map[string]*Defaults{
					"": {
						Type: simpleObject,
						DefaultValues: map[string]cty.Value{
							"b": cty.True,
						},
					},
				},
			},
			value: cty.TupleVal([]cty.Value{
				cty.ObjectVal(map[string]cty.Value{
					"a": cty.StringVal("foo"),
				}),
				cty.ObjectVal(map[string]cty.Value{
					"a": cty.StringVal("bar"),
				}),
			}),
			want: cty.TupleVal([]cty.Value{
				cty.ObjectVal(map[string]cty.Value{
					"a": cty.StringVal("foo"),
					"b": cty.True,
				}),
				cty.ObjectVal(map[string]cty.Value{
					"a": cty.StringVal("bar"),
					"b": cty.True,
				}),
			}),
		},
		// Unlike collections, tuple variable types can have defaults for
		// multiple element types.
		"tuple of objects with defaults applied": {
			defaults: &Defaults{
				Type: cty.Tuple([]cty.Type{simpleObject, nestedObject}),
				Children: map[string]*Defaults{
					"0": {
						Type: simpleObject,
						DefaultValues: map[string]cty.Value{
							"b": cty.False,
						},
					},
					"1": {
						Type: nestedObject,
						DefaultValues: map[string]cty.Value{
							"c": cty.ObjectVal(map[string]cty.Value{
								"a": cty.StringVal("default"),
								"b": cty.True,
							}),
						},
					},
				},
			},
			value: cty.TupleVal([]cty.Value{
				cty.ObjectVal(map[string]cty.Value{
					"a": cty.StringVal("foo"),
				}),
				cty.ObjectVal(map[string]cty.Value{
					"d": cty.NumberIntVal(5),
				}),
			}),
			want: cty.TupleVal([]cty.Value{
				cty.ObjectVal(map[string]cty.Value{
					"a": cty.StringVal("foo"),
					"b": cty.False,
				}),
				cty.ObjectVal(map[string]cty.Value{
					"c": cty.ObjectVal(map[string]cty.Value{
						"a": cty.StringVal("default"),
						"b": cty.True,
					}),
					"d": cty.NumberIntVal(5),
				}),
			}),
		},
		// More complex cases with deeply nested defaults, testing the "default
		// within a default" edges.
		"set of nested objects, no default sub-object": {
			defaults: &Defaults{
				Type: cty.Set(nestedObject),
				Children: map[string]*Defaults{
					"": {
						Type: nestedObject,
						Children: map[string]*Defaults{
							"c": {
								Type: simpleObject,
								DefaultValues: map[string]cty.Value{
									"b": cty.True,
								},
							},
						},
					},
				},
			},
			value: cty.TupleVal([]cty.Value{
				cty.ObjectVal(map[string]cty.Value{
					"c": cty.ObjectVal(map[string]cty.Value{
						"a": cty.StringVal("foo"),
					}),
					"d": cty.NumberIntVal(5),
				}),
				cty.ObjectVal(map[string]cty.Value{
					"d": cty.NumberIntVal(7),
				}),
			}),
			want: cty.TupleVal([]cty.Value{
				cty.ObjectVal(map[string]cty.Value{
					"c": cty.ObjectVal(map[string]cty.Value{
						"a": cty.StringVal("foo"),
						"b": cty.True,
					}),
					"d": cty.NumberIntVal(5),
				}),
				cty.ObjectVal(map[string]cty.Value{
					// No default value for "c" specified, so none applied. The
					// convert stage will fill in a null.
					"d": cty.NumberIntVal(7),
				}),
			}),
		},
		"set of nested objects, empty default sub-object": {
			defaults: &Defaults{
				Type: cty.Set(nestedObject),
				Children: map[string]*Defaults{
					"": {
						Type: nestedObject,
						DefaultValues: map[string]cty.Value{
							// This is a convenient shorthand which causes a
							// missing sub-object to be filled with an object
							// with all of the default values specified in the
							// sub-object's type.
							"c": cty.EmptyObjectVal,
						},
						Children: map[string]*Defaults{
							"c": {
								Type: simpleObject,
								DefaultValues: map[string]cty.Value{
									"b": cty.True,
								},
							},
						},
					},
				},
			},
			value: cty.TupleVal([]cty.Value{
				cty.ObjectVal(map[string]cty.Value{
					"c": cty.ObjectVal(map[string]cty.Value{
						"a": cty.StringVal("foo"),
					}),
					"d": cty.NumberIntVal(5),
				}),
				cty.ObjectVal(map[string]cty.Value{
					"d": cty.NumberIntVal(7),
				}),
			}),
			want: cty.TupleVal([]cty.Value{
				cty.ObjectVal(map[string]cty.Value{
					"c": cty.ObjectVal(map[string]cty.Value{
						"a": cty.StringVal("foo"),
						"b": cty.True,
					}),
					"d": cty.NumberIntVal(5),
				}),
				cty.ObjectVal(map[string]cty.Value{
					"c": cty.ObjectVal(map[string]cty.Value{
						// Default value for "b" is applied to the empty object
						// specified as the default for "c"
						"b": cty.True,
					}),
					"d": cty.NumberIntVal(7),
				}),
			}),
		},
		"set of nested objects, overriding default sub-object": {
			defaults: &Defaults{
				Type: cty.Set(nestedObject),
				Children: map[string]*Defaults{
					"": {
						Type: nestedObject,
						DefaultValues: map[string]cty.Value{
							// If no value is given for "c", we use this object
							// of non-default values instead. These take
							// precedence over the default values specified in
							// the child type.
							"c": cty.ObjectVal(map[string]cty.Value{
								"a": cty.StringVal("fallback"),
								"b": cty.False,
							}),
						},
						Children: map[string]*Defaults{
							"c": {
								Type: simpleObject,
								DefaultValues: map[string]cty.Value{
									"b": cty.True,
								},
							},
						},
					},
				},
			},
			value: cty.TupleVal([]cty.Value{
				cty.ObjectVal(map[string]cty.Value{
					"c": cty.ObjectVal(map[string]cty.Value{
						"a": cty.StringVal("foo"),
					}),
					"d": cty.NumberIntVal(5),
				}),
				cty.ObjectVal(map[string]cty.Value{
					"d": cty.NumberIntVal(7),
				}),
			}),
			want: cty.TupleVal([]cty.Value{
				cty.ObjectVal(map[string]cty.Value{
					"c": cty.ObjectVal(map[string]cty.Value{
						"a": cty.StringVal("foo"),
						"b": cty.True,
					}),
					"d": cty.NumberIntVal(5),
				}),
				cty.ObjectVal(map[string]cty.Value{
					"c": cty.ObjectVal(map[string]cty.Value{
						// The default value for "b" is not applied, as the
						// default value for "c" includes a non-default value
						// already.
						"a": cty.StringVal("fallback"),
						"b": cty.False,
					}),
					"d": cty.NumberIntVal(7),
				}),
			}),
		},
		"set of nested objects, nulls in default sub-object overridden": {
			defaults: &Defaults{
				Type: cty.Set(nestedObject),
				Children: map[string]*Defaults{
					"": {
						Type: nestedObject,
						DefaultValues: map[string]cty.Value{
							// The default value for "c" is used to prepopulate
							// the nested object's value if not specified, but
							// the null default for its "b" attribute will be
							// overridden by the default specified in the child
							// type.
							"c": cty.ObjectVal(map[string]cty.Value{
								"a": cty.StringVal("fallback"),
								"b": cty.NullVal(cty.Bool),
							}),
						},
						Children: map[string]*Defaults{
							"c": {
								Type: simpleObject,
								DefaultValues: map[string]cty.Value{
									"b": cty.True,
								},
							},
						},
					},
				},
			},
			value: cty.TupleVal([]cty.Value{
				cty.ObjectVal(map[string]cty.Value{
					"c": cty.ObjectVal(map[string]cty.Value{
						"a": cty.StringVal("foo"),
					}),
					"d": cty.NumberIntVal(5),
				}),
				cty.ObjectVal(map[string]cty.Value{
					"d": cty.NumberIntVal(7),
				}),
			}),
			want: cty.TupleVal([]cty.Value{
				cty.ObjectVal(map[string]cty.Value{
					"c": cty.ObjectVal(map[string]cty.Value{
						"a": cty.StringVal("foo"),
						"b": cty.True,
					}),
					"d": cty.NumberIntVal(5),
				}),
				cty.ObjectVal(map[string]cty.Value{
					"c": cty.ObjectVal(map[string]cty.Value{
						// The default value for "b" overrides the explicit
						// null in the default value for "c".
						"a": cty.StringVal("fallback"),
						"b": cty.True,
					}),
					"d": cty.NumberIntVal(7),
				}),
			}),
		},
		"null objects do not get default values inserted": {
			defaults: &Defaults{
				Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{
					"required": cty.String,
					"optional": cty.String,
				}, []string{"optional"}),
				DefaultValues: map[string]cty.Value{
					"optional": cty.StringVal("optional"),
				},
			},
			value: cty.NullVal(cty.Object(map[string]cty.Type{
				"required": cty.String,
				"optional": cty.String,
			})),
			want: cty.NullVal(cty.Object(map[string]cty.Type{
				"required": cty.String,
				"optional": cty.String,
			})),
		},
		"defaults with unset defaults are still applied (null)": {
			defaults: &Defaults{
				Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{
					"required": cty.String,
					"optional_object": cty.ObjectWithOptionalAttrs(map[string]cty.Type{
						"nested_required": cty.String,
						"nested_optional": cty.String,
					}, []string{"nested_optional"}),
				}, []string{"optional_object"}),
				DefaultValues: map[string]cty.Value{
					"optional_object": cty.ObjectVal(map[string]cty.Value{
						"nested_required": cty.StringVal("required"),
						"nested_optional": cty.NullVal(cty.String),
					}),
				},
				Children: map[string]*Defaults{
					"optional_object": {
						Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{
							"nested_required": cty.String,
							"nested_optional": cty.String,
						}, []string{"nested_optional"}),
						DefaultValues: map[string]cty.Value{
							"nested_optional": cty.StringVal("optional"),
						},
					},
				},
			},
			value: cty.ObjectVal(map[string]cty.Value{
				"required": cty.StringVal("required"),
				// optional_object is explicitly set to null for this test case.
				"optional_object": cty.NullVal(cty.Object(map[string]cty.Type{
					"nested_required": cty.String,
					"nested_optional": cty.String,
				})),
			}),
			want: cty.ObjectVal(map[string]cty.Value{
				"required": cty.StringVal("required"),
				"optional_object": cty.ObjectVal(map[string]cty.Value{
					"nested_required": cty.StringVal("required"),
					"nested_optional": cty.StringVal("optional"),
				}),
			}),
		},
		"defaults with unset defaults are still applied (missing)": {
			defaults: &Defaults{
				Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{
					"required": cty.String,
					"optional_object": cty.ObjectWithOptionalAttrs(map[string]cty.Type{
						"nested_required": cty.String,
						"nested_optional": cty.String,
					}, []string{"nested_optional"}),
				}, []string{"optional_object"}),
				DefaultValues: map[string]cty.Value{
					"optional_object": cty.ObjectVal(map[string]cty.Value{
						"nested_required": cty.StringVal("required"),
						"nested_optional": cty.NullVal(cty.String),
					}),
				},
				Children: map[string]*Defaults{
					"optional_object": {
						Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{
							"nested_required": cty.String,
							"nested_optional": cty.String,
						}, []string{"nested_optional"}),
						DefaultValues: map[string]cty.Value{
							"nested_optional": cty.StringVal("optional"),
						},
					},
				},
			},
			value: cty.ObjectVal(map[string]cty.Value{
				"required": cty.StringVal("required"),
				// optional_object is missing but not null for this test case.
			}),
			want: cty.ObjectVal(map[string]cty.Value{
				"required": cty.StringVal("required"),
				"optional_object": cty.ObjectVal(map[string]cty.Value{
					"nested_required": cty.StringVal("required"),
					"nested_optional": cty.StringVal("optional"),
				}),
			}),
		},
		// https://discuss.hashicorp.com/t/request-for-feedback-optional-object-type-attributes-with-defaults-in-v1-3-alpha/40550/6?u=alisdair
		"all child and nested values are optional with defaults": {
			defaults: &Defaults{
				Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{
					"settings": cty.ObjectWithOptionalAttrs(map[string]cty.Type{
						"setting_one": cty.String,
						"setting_two": cty.Number,
					}, []string{"setting_one", "setting_two"}),
				}, []string{"settings"}),
				DefaultValues: map[string]cty.Value{
					"settings": cty.EmptyObjectVal,
				},
				Children: map[string]*Defaults{
					"settings": {
						Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{
							"setting_one": cty.String,
							"setting_two": cty.String,
						}, []string{"setting_one", "setting_two"}),
						DefaultValues: map[string]cty.Value{
							"setting_one": cty.StringVal(""),
							"setting_two": cty.NumberIntVal(0),
						},
					},
				},
			},
			value: cty.EmptyObjectVal,
			want: cty.ObjectVal(map[string]cty.Value{
				"settings": cty.ObjectVal(map[string]cty.Value{
					"setting_one": cty.StringVal(""),
					"setting_two": cty.NumberIntVal(0),
				}),
			}),
		},
		"all nested values are optional with defaults, but direct child has no default": {
			defaults: &Defaults{
				Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{
					"settings": cty.ObjectWithOptionalAttrs(map[string]cty.Type{
						"setting_one": cty.String,
						"setting_two": cty.Number,
					}, []string{"setting_one", "setting_two"}),
				}, []string{"settings"}),
				Children: map[string]*Defaults{
					"settings": {
						Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{
							"setting_one": cty.String,
							"setting_two": cty.String,
						}, []string{"setting_one", "setting_two"}),
						DefaultValues: map[string]cty.Value{
							"setting_one": cty.StringVal(""),
							"setting_two": cty.NumberIntVal(0),
						},
					},
				},
			},
			value: cty.EmptyObjectVal,
			want:  cty.EmptyObjectVal,
		},
		"tuples retain custom values and dynamic types": {
			defaults: &Defaults{
				Type: cty.List(cty.ObjectWithOptionalAttrs(map[string]cty.Type{
					"name":   cty.String,
					"taints": cty.List(cty.Map(cty.DynamicPseudoType)),
				}, []string{"name", "taints"})),
				Children: map[string]*Defaults{
					"": {
						Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{
							"name":   cty.String,
							"taints": cty.List(cty.Map(cty.DynamicPseudoType)),
						}, []string{"name", "taints"}),
						DefaultValues: map[string]cty.Value{
							"name":   cty.StringVal("default"),
							"taints": cty.ListValEmpty(cty.Map(cty.DynamicPseudoType)),
						},
					},
				},
			},
			value: cty.TupleVal([]cty.Value{
				cty.ObjectVal(map[string]cty.Value{
					"name": cty.StringVal("node-pool-32"),
				}),
				cty.ObjectVal(map[string]cty.Value{
					"name": cty.StringVal("node-envoy-32"),
					"taints": cty.ListVal([]cty.Value{
						cty.MapVal(map[string]cty.Value{
							"key":   cty.StringVal("etsy.com/nodepool"),
							"value": cty.StringVal("envoy"),
						}),
					}),
				}),
			}),
			want: cty.TupleVal([]cty.Value{
				cty.ObjectVal(map[string]cty.Value{
					"name":   cty.StringVal("node-pool-32"),
					"taints": cty.ListValEmpty(cty.Map(cty.DynamicPseudoType)),
				}),
				cty.ObjectVal(map[string]cty.Value{
					"name": cty.StringVal("node-envoy-32"),
					"taints": cty.ListVal([]cty.Value{
						cty.MapVal(map[string]cty.Value{
							"key":   cty.StringVal("etsy.com/nodepool"),
							"value": cty.StringVal("envoy"),
						}),
					}),
				}),
			}),
		},
		"lists merge dynamic types with concrete types": {
			defaults: &Defaults{
				Type: cty.List(cty.ObjectWithOptionalAttrs(map[string]cty.Type{
					"name":   cty.String,
					"taints": cty.List(cty.Map(cty.DynamicPseudoType)),
				}, []string{"name", "taints"})),
				Children: map[string]*Defaults{
					"": {
						Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{
							"name":   cty.String,
							"taints": cty.List(cty.Map(cty.DynamicPseudoType)),
						}, []string{"name", "taints"}),
						DefaultValues: map[string]cty.Value{
							"name":   cty.StringVal("default"),
							"taints": cty.ListValEmpty(cty.Map(cty.DynamicPseudoType)),
						},
					},
				},
			},
			value: cty.ListVal([]cty.Value{
				cty.ObjectVal(map[string]cty.Value{
					"name":   cty.StringVal("node-pool-32"),
					"taints": cty.NullVal(cty.List(cty.Map(cty.String))),
				}),
				cty.ObjectVal(map[string]cty.Value{
					"name": cty.StringVal("node-envoy-32"),
					"taints": cty.ListVal([]cty.Value{
						cty.MapVal(map[string]cty.Value{
							"key":   cty.StringVal("etsy.com/nodepool"),
							"value": cty.StringVal("envoy"),
						}),
					}),
				}),
			}),
			want: cty.ListVal([]cty.Value{
				cty.ObjectVal(map[string]cty.Value{
					"name":   cty.StringVal("node-pool-32"),
					"taints": cty.ListValEmpty(cty.Map(cty.String)),
				}),
				cty.ObjectVal(map[string]cty.Value{
					"name": cty.StringVal("node-envoy-32"),
					"taints": cty.ListVal([]cty.Value{
						cty.MapVal(map[string]cty.Value{
							"key":   cty.StringVal("etsy.com/nodepool"),
							"value": cty.StringVal("envoy"),
						}),
					}),
				}),
			}),
		},
		"applies default safely where possible when types mismatch": {
			defaults: &Defaults{
				Type: cty.Map(cty.ObjectWithOptionalAttrs(map[string]cty.Type{
					"description": cty.String,
					"rules": cty.Map(cty.ObjectWithOptionalAttrs(map[string]cty.Type{
						"description":           cty.String,
						"destination_ports":     cty.List(cty.String),
						"destination_addresses": cty.List(cty.String),
						"translated_address":    cty.String,
						"translated_port":       cty.String,
					}, []string{"destination_addresses"})),
				}, []string{"description"})),
				Children: map[string]*Defaults{
					"": {
						Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{
							"description": cty.String,
							"rules": cty.Map(cty.ObjectWithOptionalAttrs(map[string]cty.Type{
								"description":           cty.String,
								"destination_ports":     cty.List(cty.String),
								"destination_addresses": cty.List(cty.String),
								"translated_address":    cty.String,
								"translated_port":       cty.String,
							}, []string{"destination_addresses"})),
						}, []string{"description"}),
						DefaultValues: map[string]cty.Value{
							"description": cty.StringVal("unknown"),
						},
						Children: map[string]*Defaults{
							"rules": {
								Type: cty.Map(cty.ObjectWithOptionalAttrs(map[string]cty.Type{
									"description":           cty.String,
									"destination_ports":     cty.List(cty.String),
									"destination_addresses": cty.List(cty.String),
									"translated_address":    cty.String,
									"translated_port":       cty.String,
								}, []string{"destination_addresses"})),
								Children: map[string]*Defaults{
									"": {
										Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{
											"description":           cty.String,
											"destination_ports":     cty.List(cty.String),
											"destination_addresses": cty.List(cty.String),
											"translated_address":    cty.String,
											"translated_port":       cty.String,
										}, []string{"destination_addresses"}),
										DefaultValues: map[string]cty.Value{
											"destination_addresses": cty.ListValEmpty(cty.String),
										},
									},
								},
							},
						},
					},
				},
			},
			value: cty.MapVal(map[string]cty.Value{
				"mysql": cty.ObjectVal(map[string]cty.Value{
					"rules": cty.ObjectVal(map[string]cty.Value{
						"description":           cty.StringVal("Port forward"),
						"destination_ports":     cty.ListVal([]cty.Value{cty.StringVal("3306")}),
						"destination_addresses": cty.ListVal([]cty.Value{cty.StringVal("192.168.0.1")}),
						"translated_address":    cty.StringVal("192.168.0.1"),
						"translated_port":       cty.StringVal("3306"),
					}),
				}),
			}),
			want: cty.MapVal(map[string]cty.Value{
				"mysql": cty.ObjectVal(map[string]cty.Value{
					"description": cty.StringVal("unknown"),
					"rules": cty.ObjectVal(map[string]cty.Value{
						"description":           cty.StringVal("Port forward"),
						"destination_ports":     cty.ListVal([]cty.Value{cty.StringVal("3306")}),
						"destination_addresses": cty.ListVal([]cty.Value{cty.StringVal("192.168.0.1")}),
						"translated_address":    cty.StringVal("192.168.0.1"),
						"translated_port":       cty.StringVal("3306"),
					}),
				}),
			}),
		},
		"optional attribute with a default can never be null": {
			defaults: &Defaults{
				Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{
					"foo": cty.String,
				}, []string{"foo"}),
				DefaultValues: map[string]cty.Value{
					"foo": cty.StringVal("bar"), // Important: default is non-null
				},
			},
			value: cty.ObjectVal(map[string]cty.Value{
				"foo": cty.UnknownVal(cty.String), // could potentially be null once known
			}),
			want: cty.ObjectVal(map[string]cty.Value{
				// Because the default isn't null we can guarantee that the
				// result cannot be null even if the given value turns out to be.
				"foo": cty.UnknownVal(cty.String).RefineNotNull(),
			}),
		},
		"optional attribute with a null default could be null": {
			defaults: &Defaults{
				Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{
					"foo": cty.String,
				}, []string{"foo"}),
				DefaultValues: map[string]cty.Value{
					"foo": cty.NullVal(cty.String), // Important: default is null
				},
			},
			value: cty.ObjectVal(map[string]cty.Value{
				"foo": cty.UnknownVal(cty.String), // could potentially be null once known
			}),
			want: cty.ObjectVal(map[string]cty.Value{
				// The default value is itself null, so this result is nullable.
				"foo": cty.UnknownVal(cty.String),
			}),
		},
		"optional attribute with no default could be null": {
			defaults: &Defaults{
				Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{
					"foo": cty.String,
				}, []string{"foo"}),
			},
			value: cty.ObjectVal(map[string]cty.Value{
				"foo": cty.UnknownVal(cty.String), // could potentially be null once known
			}),
			want: cty.ObjectVal(map[string]cty.Value{
				// The default value is itself null, so this result is nullable.
				"foo": cty.UnknownVal(cty.String),
			}),
		},
		"optional attribute with non-null unknown value cannot be null": {
			defaults: &Defaults{
				Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{
					"foo": cty.String,
				}, []string{"foo"}),
				DefaultValues: map[string]cty.Value{
					"foo": cty.NullVal(cty.String), // Important: default is null
				},
			},
			value: cty.ObjectVal(map[string]cty.Value{
				"foo": cty.UnknownVal(cty.String).RefineNotNull(),
			}),
			want: cty.ObjectVal(map[string]cty.Value{
				// If the input is guaranteed not null then the default
				// value can't possibly be selected, and so the result can
				// also not be null.
				"foo": cty.UnknownVal(cty.String).RefineNotNull(),
			}),
		},
		"optional attribute with dynamic value can be null": {
			defaults: &Defaults{
				Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{
					"foo": cty.String,
				}, []string{"foo"}),
				DefaultValues: map[string]cty.Value{
					"foo": cty.StringVal("bar"), // Important: default is non-null
				},
			},
			value: cty.ObjectVal(map[string]cty.Value{
				"foo": cty.DynamicVal,
			}),
			want: cty.ObjectVal(map[string]cty.Value{
				// The default value is itself non-null, but dynamic value cannot be refined.
				"foo": cty.DynamicVal,
			}),
		},
	}

	for name, tc := range testCases {
		t.Run(name, func(t *testing.T) {
			got := tc.defaults.Apply(tc.value)
			if !cmp.Equal(tc.want, got, valueComparer) {
				t.Errorf("wrong result\n%s", cmp.Diff(tc.want, got, valueComparer))
			}
		})
	}
}
