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

package schema

import (
	"reflect"
	"strconv"
	"testing"

	"github.com/opentofu/opentofu/internal/legacy/tofu"
)

func TestMultiLevelFieldReaderReadFieldExact(t *testing.T) {
	cases := map[string]struct {
		Addr    []string
		Readers []FieldReader
		Level   string
		Result  FieldReadResult
	}{
		"specific": {
			Addr: []string{"foo"},

			Readers: []FieldReader{
				&MapFieldReader{
					Schema: map[string]*Schema{
						"foo": &Schema{Type: TypeString},
					},
					Map: BasicMapReader(map[string]string{
						"foo": "bar",
					}),
				},
				&MapFieldReader{
					Schema: map[string]*Schema{
						"foo": &Schema{Type: TypeString},
					},
					Map: BasicMapReader(map[string]string{
						"foo": "baz",
					}),
				},
				&MapFieldReader{
					Schema: map[string]*Schema{
						"foo": &Schema{Type: TypeString},
					},
					Map: BasicMapReader(map[string]string{}),
				},
			},

			Level: "1",
			Result: FieldReadResult{
				Value:  "baz",
				Exists: true,
			},
		},
	}

	for name, tc := range cases {
		readers := make(map[string]FieldReader)
		levels := make([]string, len(tc.Readers))
		for i, r := range tc.Readers {
			is := strconv.FormatInt(int64(i), 10)
			readers[is] = r
			levels[i] = is
		}

		r := &MultiLevelFieldReader{
			Readers: readers,
			Levels:  levels,
		}

		out, err := r.ReadFieldExact(tc.Addr, tc.Level)
		if err != nil {
			t.Fatalf("%s: err: %s", name, err)
		}

		if !reflect.DeepEqual(tc.Result, out) {
			t.Fatalf("%s: bad: %#v", name, out)
		}
	}
}

func TestMultiLevelFieldReaderReadFieldMerge(t *testing.T) {
	cases := map[string]struct {
		Addr    []string
		Readers []FieldReader
		Result  FieldReadResult
	}{
		"stringInDiff": {
			Addr: []string{"availability_zone"},

			Readers: []FieldReader{
				&DiffFieldReader{
					Schema: map[string]*Schema{
						"availability_zone": &Schema{Type: TypeString},
					},

					Source: &MapFieldReader{
						Schema: map[string]*Schema{
							"availability_zone": &Schema{Type: TypeString},
						},
						Map: BasicMapReader(map[string]string{
							"availability_zone": "foo",
						}),
					},

					Diff: &tofu.InstanceDiff{
						Attributes: map[string]*tofu.ResourceAttrDiff{
							"availability_zone": &tofu.ResourceAttrDiff{
								Old:         "foo",
								New:         "bar",
								RequiresNew: true,
							},
						},
					},
				},
			},

			Result: FieldReadResult{
				Value:  "bar",
				Exists: true,
			},
		},

		"lastLevelComputed": {
			Addr: []string{"availability_zone"},

			Readers: []FieldReader{
				&MapFieldReader{
					Schema: map[string]*Schema{
						"availability_zone": &Schema{Type: TypeString},
					},

					Map: BasicMapReader(map[string]string{
						"availability_zone": "foo",
					}),
				},

				&DiffFieldReader{
					Schema: map[string]*Schema{
						"availability_zone": &Schema{Type: TypeString},
					},

					Source: &MapFieldReader{
						Schema: map[string]*Schema{
							"availability_zone": &Schema{Type: TypeString},
						},

						Map: BasicMapReader(map[string]string{
							"availability_zone": "foo",
						}),
					},

					Diff: &tofu.InstanceDiff{
						Attributes: map[string]*tofu.ResourceAttrDiff{
							"availability_zone": &tofu.ResourceAttrDiff{
								Old:         "foo",
								New:         "bar",
								NewComputed: true,
							},
						},
					},
				},
			},

			Result: FieldReadResult{
				Value:    "",
				Exists:   true,
				Computed: true,
			},
		},

		"list of maps with removal in diff": {
			Addr: []string{"config_vars"},

			Readers: []FieldReader{
				&DiffFieldReader{
					Schema: map[string]*Schema{
						"config_vars": &Schema{
							Type: TypeList,
							Elem: &Schema{Type: TypeMap},
						},
					},

					Source: &MapFieldReader{
						Schema: map[string]*Schema{
							"config_vars": &Schema{
								Type: TypeList,
								Elem: &Schema{Type: TypeMap},
							},
						},

						Map: BasicMapReader(map[string]string{
							"config_vars.#":     "2",
							"config_vars.0.foo": "bar",
							"config_vars.0.bar": "bar",
							"config_vars.1.bar": "baz",
						}),
					},

					Diff: &tofu.InstanceDiff{
						Attributes: map[string]*tofu.ResourceAttrDiff{
							"config_vars.0.bar": &tofu.ResourceAttrDiff{
								NewRemoved: true,
							},
						},
					},
				},
			},

			Result: FieldReadResult{
				Value: []interface{}{
					map[string]interface{}{
						"foo": "bar",
					},
					map[string]interface{}{
						"bar": "baz",
					},
				},
				Exists: true,
			},
		},

		"first level only": {
			Addr: []string{"foo"},

			Readers: []FieldReader{
				&MapFieldReader{
					Schema: map[string]*Schema{
						"foo": &Schema{Type: TypeString},
					},
					Map: BasicMapReader(map[string]string{
						"foo": "bar",
					}),
				},
				&MapFieldReader{
					Schema: map[string]*Schema{
						"foo": &Schema{Type: TypeString},
					},
					Map: BasicMapReader(map[string]string{}),
				},
			},

			Result: FieldReadResult{
				Value:  "bar",
				Exists: true,
			},
		},
	}

	for name, tc := range cases {
		readers := make(map[string]FieldReader)
		levels := make([]string, len(tc.Readers))
		for i, r := range tc.Readers {
			is := strconv.FormatInt(int64(i), 10)
			readers[is] = r
			levels[i] = is
		}

		r := &MultiLevelFieldReader{
			Readers: readers,
			Levels:  levels,
		}

		out, err := r.ReadFieldMerge(tc.Addr, levels[len(levels)-1])
		if err != nil {
			t.Fatalf("%s: err: %s", name, err)
		}

		if !reflect.DeepEqual(tc.Result, out) {
			t.Fatalf("%s: bad: %#v", name, out)
		}
	}
}
