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

package tofu

import (
	"bytes"
	"strings"
	"testing"
)

// TestReadUpgradeStateV2toV3 tests the state upgrade process from the V2 state
// to the current version, and needs editing each time. This means it tests the
// entire pipeline of upgrades (which migrate version to version).
func TestReadUpgradeStateV2toV3(t *testing.T) {
	// ReadState should transparently detect the old version but will upgrade
	// it on Write.
	upgraded, err := ReadState(strings.NewReader(testV2State))
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	buf := new(bytes.Buffer)
	if err := WriteState(upgraded, buf); err != nil {
		t.Fatalf("err: %s", err)
	}

	if upgraded.Version != 3 {
		t.Fatalf("bad: State version not incremented; is %d", upgraded.Version)
	}

	// For this test we cannot assert that we match the round trip because an
	// empty map has been removed from state. Instead we make assertions against
	// some of the key fields in the _upgraded_ state.
	instanceState, ok := upgraded.RootModule().Resources["test_resource.main"]
	if !ok {
		t.Fatalf("Instance state for test_resource.main was removed from state during upgrade")
	}

	primary := instanceState.Primary
	if primary == nil {
		t.Fatalf("Primary instance was removed from state for test_resource.main")
	}

	// Non-empty computed map is moved from .# to .%
	if _, ok := primary.Attributes["computed_map.#"]; ok {
		t.Fatalf("Count was not upgraded from .# to .%% for computed_map")
	}
	if count, ok := primary.Attributes["computed_map.%"]; !ok || count != "1" {
		t.Fatalf("Count was not in .%% or was not 2 for computed_map")
	}

	// list_of_map top level retains .#
	if count, ok := primary.Attributes["list_of_map.#"]; !ok || count != "2" {
		t.Fatal("Count for list_of_map was migrated incorrectly")
	}

	// list_of_map.0 is moved from .# to .%
	if _, ok := primary.Attributes["list_of_map.0.#"]; ok {
		t.Fatalf("Count was not upgraded from .# to .%% for list_of_map.0")
	}
	if count, ok := primary.Attributes["list_of_map.0.%"]; !ok || count != "2" {
		t.Fatalf("Count was not in .%% or was not 2 for list_of_map.0")
	}

	// list_of_map.1 is moved from .# to .%
	if _, ok := primary.Attributes["list_of_map.1.#"]; ok {
		t.Fatalf("Count was not upgraded from .# to .%% for list_of_map.1")
	}
	if count, ok := primary.Attributes["list_of_map.1.%"]; !ok || count != "2" {
		t.Fatalf("Count was not in .%% or was not 2 for list_of_map.1")
	}

	// map is moved from .# to .%
	if _, ok := primary.Attributes["map.#"]; ok {
		t.Fatalf("Count was not upgraded from .# to .%% for map")
	}
	if count, ok := primary.Attributes["map.%"]; !ok || count != "2" {
		t.Fatalf("Count was not in .%% or was not 2 for map")
	}

	// optional_computed_map should be removed from state
	if _, ok := primary.Attributes["optional_computed_map"]; ok {
		t.Fatal("optional_computed_map was not removed from state")
	}

	// required_map is moved from .# to .%
	if _, ok := primary.Attributes["required_map.#"]; ok {
		t.Fatalf("Count was not upgraded from .# to .%% for required_map")
	}
	if count, ok := primary.Attributes["required_map.%"]; !ok || count != "3" {
		t.Fatalf("Count was not in .%% or was not 3 for map")
	}

	// computed_list keeps .#
	if count, ok := primary.Attributes["computed_list.#"]; !ok || count != "2" {
		t.Fatal("Count was migrated incorrectly for computed_list")
	}

	// computed_set keeps .#
	if count, ok := primary.Attributes["computed_set.#"]; !ok || count != "2" {
		t.Fatal("Count was migrated incorrectly for computed_set")
	}
	if val, ok := primary.Attributes["computed_set.2337322984"]; !ok || val != "setval1" {
		t.Fatal("Set item for computed_set.2337322984 changed or moved")
	}
	if val, ok := primary.Attributes["computed_set.307881554"]; !ok || val != "setval2" {
		t.Fatal("Set item for computed_set.307881554 changed or moved")
	}

	// string properties are unaffected
	if val, ok := primary.Attributes["id"]; !ok || val != "testId" {
		t.Fatal("id was not set correctly after migration")
	}
}

const testV2State = `{
    "version": 2,
    "terraform_version": "0.7.0",
    "serial": 2,
    "modules": [
        {
            "path": [
                "root"
            ],
            "outputs": {
                "computed_map": {
                    "sensitive": false,
                    "type": "map",
                    "value": {
                        "key1": "value1"
                    }
                },
                "computed_set": {
                    "sensitive": false,
                    "type": "list",
                    "value": [
                        "setval1",
                        "setval2"
                    ]
                },
                "map": {
                    "sensitive": false,
                    "type": "map",
                    "value": {
                        "key": "test",
                        "test": "test"
                    }
                },
                "set": {
                    "sensitive": false,
                    "type": "list",
                    "value": [
                        "test1",
                        "test2"
                    ]
                }
            },
            "resources": {
                "test_resource.main": {
                    "type": "test_resource",
                    "primary": {
                        "id": "testId",
                        "attributes": {
                            "computed_list.#": "2",
                            "computed_list.0": "listval1",
                            "computed_list.1": "listval2",
                            "computed_map.#": "1",
                            "computed_map.key1": "value1",
                            "computed_read_only": "value_from_api",
                            "computed_read_only_force_new": "value_from_api",
                            "computed_set.#": "2",
                            "computed_set.2337322984": "setval1",
                            "computed_set.307881554": "setval2",
                            "id": "testId",
                            "list_of_map.#": "2",
                            "list_of_map.0.#": "2",
                            "list_of_map.0.key1": "value1",
                            "list_of_map.0.key2": "value2",
                            "list_of_map.1.#": "2",
                            "list_of_map.1.key3": "value3",
                            "list_of_map.1.key4": "value4",
                            "map.#": "2",
                            "map.key": "test",
                            "map.test": "test",
                            "map_that_look_like_set.#": "2",
                            "map_that_look_like_set.12352223": "hello",
                            "map_that_look_like_set.36234341": "world",
                            "optional_computed_map.#": "0",
                            "required": "Hello World",
                            "required_map.#": "3",
                            "required_map.key1": "value1",
                            "required_map.key2": "value2",
                            "required_map.key3": "value3",
                            "set.#": "2",
                            "set.2326977762": "test1",
                            "set.331058520": "test2"
                        }
                    }
                }
            }
        }
    ]
}
`
