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

package command

import (
	"fmt"
	"log"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/opentofu/opentofu/internal/command/workdir"
	"github.com/zclconf/go-cty/cty"

	"github.com/opentofu/opentofu/internal/configs/configschema"
	"github.com/opentofu/opentofu/internal/copy"
	"github.com/opentofu/opentofu/internal/providers"
	"github.com/opentofu/opentofu/internal/tfdiags"
)

func TestImport(t *testing.T) {
	t.Chdir(testFixturePath("import-provider-implicit"))

	statePath := testTempFile(t)

	p := testProvider()
	view, done := testView(t)
	c := &ImportCommand{
		Meta: Meta{
			WorkingDir:       workdir.NewDir("."),
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	p.ImportResourceStateFn = nil
	p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
		ImportedResources: []providers.ImportedResource{
			{
				TypeName: "test_instance",
				State: cty.ObjectVal(map[string]cty.Value{
					"id": cty.StringVal("yay"),
				}),
			},
		},
	}
	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
		ResourceTypes: map[string]providers.Schema{
			"test_instance": {
				Block: &configschema.Block{
					Attributes: map[string]*configschema.Attribute{
						"id": {Type: cty.String, Optional: true, Computed: true},
					},
				},
			},
		},
	}

	args := []string{
		"-state", statePath,
		"test_instance.foo",
		"bar",
	}
	code := c.Run(args)
	output := done(t)
	if code != 0 {
		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
	}

	if !p.ImportResourceStateCalled {
		t.Fatal("ImportResourceState should be called")
	}

	testStateOutput(t, statePath, testImportStr)
}

func TestImport_providerConfig(t *testing.T) {
	t.Chdir(testFixturePath("import-provider"))

	statePath := testTempFile(t)

	p := testProvider()
	view, done := testView(t)
	c := &ImportCommand{
		Meta: Meta{
			WorkingDir:       workdir.NewDir("."),
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	p.ImportResourceStateFn = nil
	p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
		ImportedResources: []providers.ImportedResource{
			{
				TypeName: "test_instance",
				State: cty.ObjectVal(map[string]cty.Value{
					"id": cty.StringVal("yay"),
				}),
			},
		},
	}
	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
		Provider: providers.Schema{
			Block: &configschema.Block{
				Attributes: map[string]*configschema.Attribute{
					"foo": {Type: cty.String, Optional: true},
				},
			},
		},
		ResourceTypes: map[string]providers.Schema{
			"test_instance": {
				Block: &configschema.Block{
					Attributes: map[string]*configschema.Attribute{
						"id": {Type: cty.String, Optional: true, Computed: true},
					},
				},
			},
		},
	}

	configured := false
	p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) providers.ConfigureProviderResponse {
		configured = true

		cfg := req.Config
		if !cfg.Type().HasAttribute("foo") {
			return providers.ConfigureProviderResponse{
				Diagnostics: tfdiags.Diagnostics{}.Append(fmt.Errorf("configuration has no foo argument")),
			}
		}
		if got, want := cfg.GetAttr("foo"), cty.StringVal("bar"); !want.RawEquals(got) {
			return providers.ConfigureProviderResponse{
				Diagnostics: tfdiags.Diagnostics{}.Append(fmt.Errorf("foo argument is %#v, but want %#v", got, want)),
			}
		}

		return providers.ConfigureProviderResponse{}
	}

	args := []string{
		"-state", statePath,
		"test_instance.foo",
		"bar",
	}
	code := c.Run(args)
	output := done(t)
	if code != 0 {
		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
	}

	// Verify that we were called
	if !configured {
		t.Fatal("Configure should be called")
	}

	if !p.ImportResourceStateCalled {
		t.Fatal("ImportResourceState should be called")
	}

	testStateOutput(t, statePath, testImportStr)
}

// "remote" state provided by the "local" backend
func TestImport_remoteState(t *testing.T) {
	td := t.TempDir()
	testCopyDir(t, testFixturePath("import-provider-remote-state"), td)
	t.Chdir(td)

	statePath := "imported.tfstate"

	providerSource, close := newMockProviderSource(t, map[string][]string{
		"test": []string{"1.2.3"},
	})
	defer close()

	// init our backend
	initView, initDone := testView(t)
	m := Meta{
		WorkingDir:       workdir.NewDir("."),
		testingOverrides: metaOverridesForProvider(testProvider()),
		View:             initView,
		ProviderSource:   providerSource,
	}

	ic := &InitCommand{
		Meta: m,
	}

	// (Using log here rather than t.Log so that these messages interleave with other trace logs)
	log.Print("[TRACE] TestImport_remoteState running: tofu init")
	code := ic.Run([]string{})
	initOutput := initDone(t)
	if code != 0 {
		t.Fatalf("init failed\n%s", initOutput.Stderr())
	}

	p := testProvider()
	importView, importDone := testView(t)
	c := &ImportCommand{
		Meta: Meta{
			WorkingDir:       workdir.NewDir("."),
			testingOverrides: metaOverridesForProvider(p),
			View:             importView,
		},
	}

	p.ImportResourceStateFn = nil
	p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
		ImportedResources: []providers.ImportedResource{
			{
				TypeName: "test_instance",
				State: cty.ObjectVal(map[string]cty.Value{
					"id": cty.StringVal("yay"),
				}),
			},
		},
	}
	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
		Provider: providers.Schema{
			Block: &configschema.Block{
				Attributes: map[string]*configschema.Attribute{
					"foo": {Type: cty.String, Optional: true},
				},
			},
		},
		ResourceTypes: map[string]providers.Schema{
			"test_instance": {
				Block: &configschema.Block{
					Attributes: map[string]*configschema.Attribute{
						"id": {Type: cty.String, Optional: true, Computed: true},
					},
				},
			},
		},
	}

	configured := false
	p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) providers.ConfigureProviderResponse {
		var diags tfdiags.Diagnostics
		configured = true
		if got, want := req.Config.GetAttr("foo"), cty.StringVal("bar"); !want.RawEquals(got) {
			diags = diags.Append(fmt.Errorf("wrong \"foo\" value %#v; want %#v", got, want))
		}
		return providers.ConfigureProviderResponse{
			Diagnostics: diags,
		}
	}

	args := []string{
		"test_instance.foo",
		"bar",
	}
	log.Printf("[TRACE] TestImport_remoteState running: tofu import %s %s", args[0], args[1])
	code = c.Run(args)
	importOutput := importDone(t)
	if code != 0 {
		fmt.Println(importOutput.Stdout())
		t.Fatalf("bad: %d\n\n%s", code, importOutput.Stderr())
	}

	// verify that the local state was unlocked after import
	if _, err := os.Stat(filepath.Join(td, fmt.Sprintf(".%s.lock.info", statePath))); !os.IsNotExist(err) {
		t.Fatal("state left locked after import")
	}

	// Verify that we were called
	if !configured {
		t.Fatal("Configure should be called")
	}

	if !p.ImportResourceStateCalled {
		t.Fatal("ImportResourceState should be called")
	}

	testStateOutput(t, statePath, testImportStr)
}

// early failure on import should not leave stale lock
func TestImport_initializationErrorShouldUnlock(t *testing.T) {
	td := t.TempDir()
	testCopyDir(t, testFixturePath("import-provider-remote-state"), td)
	t.Chdir(td)

	statePath := "imported.tfstate"

	providerSource, close := newMockProviderSource(t, map[string][]string{
		"test": []string{"1.2.3"},
	})
	defer close()

	// init our backend
	initView, initDone := testView(t)
	m := Meta{
		WorkingDir:       workdir.NewDir("."),
		testingOverrides: metaOverridesForProvider(testProvider()),
		View:             initView,
		ProviderSource:   providerSource,
	}

	ic := &InitCommand{
		Meta: m,
	}

	// (Using log here rather than t.Log so that these messages interleave with other trace logs)
	log.Print("[TRACE] TestImport_initializationErrorShouldUnlock running: tofu init")
	code := ic.Run([]string{})
	initOutput := initDone(t)
	if code != 0 {
		t.Fatalf("init failed\n%s", initOutput.Stderr())
	}

	// overwrite the config with one including a resource from an invalid provider
	if err := copy.CopyFile(filepath.Join(testFixturePath("import-provider-invalid"), "main.tf"), filepath.Join(td, "main.tf")); err != nil {
		t.Fatal(err)
	}

	p := testProvider()
	importView, importDone := testView(t)
	c := &ImportCommand{
		Meta: Meta{
			WorkingDir:       workdir.NewDir("."),
			testingOverrides: metaOverridesForProvider(p),
			View:             importView,
		},
	}

	args := []string{
		"unknown_instance.baz",
		"bar",
	}
	log.Printf("[TRACE] TestImport_initializationErrorShouldUnlock running: tofu import %s %s", args[0], args[1])

	// this should fail
	code = c.Run(args)
	importOutput := importDone(t)
	if code != 1 {
		fmt.Println(importOutput.Stdout())
		t.Fatalf("bad: %d\n\n%s", code, importOutput.Stderr())
	}

	// specifically, it should fail due to a missing provider
	msg := strings.ReplaceAll(importOutput.Stderr(), "\n", " ")
	if want := `provider registry.opentofu.org/hashicorp/unknown: required by this configuration but no version is selected`; !strings.Contains(msg, want) {
		t.Errorf("incorrect message\nwant substring: %s\ngot:\n%s", want, msg)
	}

	// verify that the local state was unlocked after initialization error
	if _, err := os.Stat(filepath.Join(td, fmt.Sprintf(".%s.lock.info", statePath))); !os.IsNotExist(err) {
		t.Fatal("state left locked after import")
	}
}

func TestImport_providerConfigWithVar(t *testing.T) {
	t.Chdir(testFixturePath("import-provider-var"))

	statePath := testTempFile(t)

	p := testProvider()
	view, done := testView(t)
	c := &ImportCommand{
		Meta: Meta{
			WorkingDir:       workdir.NewDir("."),
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	p.ImportResourceStateFn = nil
	p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
		ImportedResources: []providers.ImportedResource{
			{
				TypeName: "test_instance",
				State: cty.ObjectVal(map[string]cty.Value{
					"id": cty.StringVal("yay"),
				}),
			},
		},
	}
	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
		Provider: providers.Schema{
			Block: &configschema.Block{
				Attributes: map[string]*configschema.Attribute{
					"foo": {Type: cty.String, Optional: true},
				},
			},
		},
		ResourceTypes: map[string]providers.Schema{
			"test_instance": {
				Block: &configschema.Block{
					Attributes: map[string]*configschema.Attribute{
						"id": {Type: cty.String, Optional: true, Computed: true},
					},
				},
			},
		},
	}

	configured := false
	p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) providers.ConfigureProviderResponse {
		var diags tfdiags.Diagnostics
		configured = true
		if got, want := req.Config.GetAttr("foo"), cty.StringVal("bar"); !want.RawEquals(got) {
			diags = diags.Append(fmt.Errorf("wrong \"foo\" value %#v; want %#v", got, want))
		}
		return providers.ConfigureProviderResponse{
			Diagnostics: diags,
		}
	}

	args := []string{
		"-state", statePath,
		"-var", "foo=bar",
		"test_instance.foo",
		"bar",
	}
	code := c.Run(args)
	output := done(t)
	if code != 0 {
		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
	}

	// Verify that we were called
	if !configured {
		t.Fatal("Configure should be called")
	}

	if !p.ImportResourceStateCalled {
		t.Fatal("ImportResourceState should be called")
	}

	testStateOutput(t, statePath, testImportStr)
}

func TestImport_providerConfigWithDataSource(t *testing.T) {
	t.Chdir(testFixturePath("import-provider-datasource"))

	statePath := testTempFile(t)

	p := testProvider()
	view, done := testView(t)
	c := &ImportCommand{
		Meta: Meta{
			WorkingDir:       workdir.NewDir("."),
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	p.ImportResourceStateFn = nil
	p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
		ImportedResources: []providers.ImportedResource{
			{
				TypeName: "test_instance",
				State: cty.ObjectVal(map[string]cty.Value{
					"id": cty.StringVal("yay"),
				}),
			},
		},
	}
	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
		Provider: providers.Schema{
			Block: &configschema.Block{
				Attributes: map[string]*configschema.Attribute{
					"foo": {Type: cty.String, Optional: true},
				},
			},
		},
		ResourceTypes: map[string]providers.Schema{
			"test_instance": {
				Block: &configschema.Block{
					Attributes: map[string]*configschema.Attribute{
						"id": {Type: cty.String, Optional: true, Computed: true},
					},
				},
			},
		},
		DataSources: map[string]providers.Schema{
			"test_data": {
				Block: &configschema.Block{
					Attributes: map[string]*configschema.Attribute{
						"foo": {Type: cty.String, Optional: true},
					},
				},
			},
		},
	}

	args := []string{
		"-state", statePath,
		"test_instance.foo",
		"bar",
	}
	code := c.Run(args)
	output := done(t)
	if code != 1 {
		t.Fatalf("bad, wanted error: %d\n\n%s", code, output.Stderr())
	}
}

func TestImport_providerConfigWithVarDefault(t *testing.T) {
	t.Chdir(testFixturePath("import-provider-var-default"))

	statePath := testTempFile(t)

	p := testProvider()
	view, done := testView(t)
	c := &ImportCommand{
		Meta: Meta{
			WorkingDir:       workdir.NewDir("."),
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	p.ImportResourceStateFn = nil
	p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
		ImportedResources: []providers.ImportedResource{
			{
				TypeName: "test_instance",
				State: cty.ObjectVal(map[string]cty.Value{
					"id": cty.StringVal("yay"),
				}),
			},
		},
	}
	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
		Provider: providers.Schema{
			Block: &configschema.Block{
				Attributes: map[string]*configschema.Attribute{
					"foo": {Type: cty.String, Optional: true},
				},
			},
		},
		ResourceTypes: map[string]providers.Schema{
			"test_instance": {
				Block: &configschema.Block{
					Attributes: map[string]*configschema.Attribute{
						"id": {Type: cty.String, Optional: true, Computed: true},
					},
				},
			},
		},
	}

	configured := false
	p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) providers.ConfigureProviderResponse {
		var diags tfdiags.Diagnostics
		configured = true
		if got, want := req.Config.GetAttr("foo"), cty.StringVal("bar"); !want.RawEquals(got) {
			diags = diags.Append(fmt.Errorf("wrong \"foo\" value %#v; want %#v", got, want))
		}
		return providers.ConfigureProviderResponse{
			Diagnostics: diags,
		}
	}

	args := []string{
		"-state", statePath,
		"test_instance.foo",
		"bar",
	}
	code := c.Run(args)
	output := done(t)
	if code != 0 {
		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
	}

	// Verify that we were called
	if !configured {
		t.Fatal("Configure should be called")
	}

	if !p.ImportResourceStateCalled {
		t.Fatal("ImportResourceState should be called")
	}

	testStateOutput(t, statePath, testImportStr)
}

func TestImport_providerConfigWithVarFile(t *testing.T) {
	t.Chdir(testFixturePath("import-provider-var-file"))

	statePath := testTempFile(t)

	p := testProvider()
	view, done := testView(t)
	c := &ImportCommand{
		Meta: Meta{
			WorkingDir:       workdir.NewDir("."),
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	p.ImportResourceStateFn = nil
	p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
		ImportedResources: []providers.ImportedResource{
			{
				TypeName: "test_instance",
				State: cty.ObjectVal(map[string]cty.Value{
					"id": cty.StringVal("yay"),
				}),
			},
		},
	}
	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
		Provider: providers.Schema{
			Block: &configschema.Block{
				Attributes: map[string]*configschema.Attribute{
					"foo": {Type: cty.String, Optional: true},
				},
			},
		},
		ResourceTypes: map[string]providers.Schema{
			"test_instance": {
				Block: &configschema.Block{
					Attributes: map[string]*configschema.Attribute{
						"id": {Type: cty.String, Optional: true, Computed: true},
					},
				},
			},
		},
	}

	configured := false
	p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) providers.ConfigureProviderResponse {
		var diags tfdiags.Diagnostics
		configured = true
		if got, want := req.Config.GetAttr("foo"), cty.StringVal("bar"); !want.RawEquals(got) {
			diags = diags.Append(fmt.Errorf("wrong \"foo\" value %#v; want %#v", got, want))
		}
		return providers.ConfigureProviderResponse{
			Diagnostics: diags,
		}
	}

	args := []string{
		"-state", statePath,
		"-var-file", "blah.tfvars",
		"test_instance.foo",
		"bar",
	}
	code := c.Run(args)
	output := done(t)
	if code != 0 {
		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
	}

	// Verify that we were called
	if !configured {
		t.Fatal("Configure should be called")
	}

	if !p.ImportResourceStateCalled {
		t.Fatal("ImportResourceState should be called")
	}

	testStateOutput(t, statePath, testImportStr)
}

func TestImport_emptyConfig(t *testing.T) {
	t.Chdir(testFixturePath("empty"))

	statePath := testTempFile(t)

	p := testProvider()
	view, done := testView(t)
	c := &ImportCommand{
		Meta: Meta{
			WorkingDir:       workdir.NewDir("."),
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	args := []string{
		"-state", statePath,
		"test_instance.foo",
		"bar",
	}
	code := c.Run(args)
	output := done(t)
	if code != 1 {
		t.Fatalf("import succeeded; expected failure")
	}

	msg := output.Stderr()
	if want := `No OpenTofu configuration files`; !strings.Contains(msg, want) {
		t.Errorf("incorrect message\nwant substring: %s\ngot:\n%s", want, msg)
	}
}

func TestImport_missingResourceConfig(t *testing.T) {
	t.Chdir(testFixturePath("import-missing-resource-config"))

	statePath := testTempFile(t)

	p := testProvider()
	view, done := testView(t)
	c := &ImportCommand{
		Meta: Meta{
			WorkingDir:       workdir.NewDir("."),
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	args := []string{
		"-state", statePath,
		"test_instance.foo",
		"bar",
	}
	code := c.Run(args)
	output := done(t)
	if code != 1 {
		t.Fatalf("import succeeded; expected failure")
	}

	msg := output.Stderr()
	if want := `resource address "test_instance.foo" does not exist`; !strings.Contains(msg, want) {
		t.Errorf("incorrect message\nwant substring: %s\ngot:\n%s", want, msg)
	}
}

func TestImport_missingModuleConfig(t *testing.T) {
	t.Chdir(testFixturePath("import-missing-resource-config"))

	statePath := testTempFile(t)

	p := testProvider()
	view, done := testView(t)
	c := &ImportCommand{
		Meta: Meta{
			WorkingDir:       workdir.NewDir("."),
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	args := []string{
		"-state", statePath,
		"module.baz.test_instance.foo",
		"bar",
	}
	code := c.Run(args)
	output := done(t)
	if code != 1 {
		t.Fatalf("import succeeded; expected failure")
	}

	msg := output.Stderr()
	if want := `module.baz is not defined in the configuration`; !strings.Contains(msg, want) {
		t.Errorf("incorrect message\nwant substring: %s\ngot:\n%s", want, msg)
	}
}

func TestImportModuleVarFile(t *testing.T) {
	td := t.TempDir()
	testCopyDir(t, testFixturePath("import-module-var-file"), td)
	t.Chdir(td)

	statePath := testTempFile(t)

	p := testProvider()
	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
		ResourceTypes: map[string]providers.Schema{
			"test_instance": {
				Block: &configschema.Block{
					Attributes: map[string]*configschema.Attribute{
						"foo": {Type: cty.String, Optional: true},
					},
				},
			},
		},
	}

	providerSource, close := newMockProviderSource(t, map[string][]string{
		"test": []string{"1.2.3"},
	})
	defer close()

	// init to install the module
	initView, initDone := testView(t)
	m := Meta{
		WorkingDir:       workdir.NewDir("."),
		testingOverrides: metaOverridesForProvider(testProvider()),
		View:             initView,
		ProviderSource:   providerSource,
	}

	ic := &InitCommand{
		Meta: m,
	}
	code := ic.Run([]string{})
	initOutput := initDone(t)
	if code != 0 {
		t.Fatalf("init failed\n%s", initOutput.Stderr())
	}

	// import
	importView, importDone := testView(t)
	c := &ImportCommand{
		Meta: Meta{
			WorkingDir:       workdir.NewDir("."),
			testingOverrides: metaOverridesForProvider(p),
			View:             importView,
		},
	}
	args := []string{
		"-state", statePath,
		"module.child.test_instance.foo",
		"bar",
	}
	code = c.Run(args)
	importOutput := importDone(t)
	if code != 0 {
		t.Fatalf("import failed; expected success. %s", importOutput.All())
	}
}

// This test covers an edge case where a module with a complex input variable
// of nested objects has an invalid default which is overridden by the calling
// context, and is used in locals. If we don't evaluate module call variables
// for the import walk, this results in an error.
//
// The specific example has a variable "foo" which is a nested object:
//
//	foo = { bar = { baz = true } }
//
// This is used as foo = var.foo in the call to the child module, which then
// uses the traversal foo.bar.baz in a local. A default value in the child
// module of {} causes this local evaluation to error, breaking import.
func TestImportModuleInputVariableEvaluation(t *testing.T) {
	td := t.TempDir()
	testCopyDir(t, testFixturePath("import-module-input-variable"), td)
	t.Chdir(td)

	statePath := testTempFile(t)

	p := testProvider()
	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
		ResourceTypes: map[string]providers.Schema{
			"test_instance": {
				Block: &configschema.Block{
					Attributes: map[string]*configschema.Attribute{
						"foo": {Type: cty.String, Optional: true},
					},
				},
			},
		},
	}

	providerSource, close := newMockProviderSource(t, map[string][]string{
		"test": {"1.2.3"},
	})
	defer close()

	// init to install the module
	initView, initDone := testView(t)
	m := Meta{
		WorkingDir:       workdir.NewDir("."),
		testingOverrides: metaOverridesForProvider(testProvider()),
		View:             initView,
		ProviderSource:   providerSource,
	}

	ic := &InitCommand{
		Meta: m,
	}
	code := ic.Run([]string{})
	initOutput := initDone(t)
	if code != 0 {
		t.Fatalf("init failed\n%s", initOutput.Stderr())
	}

	// import
	importView, importDone := testView(t)
	c := &ImportCommand{
		Meta: Meta{
			WorkingDir:       workdir.NewDir("."),
			testingOverrides: metaOverridesForProvider(p),
			View:             importView,
		},
	}
	args := []string{
		"-state", statePath,
		"module.child.test_instance.foo",
		"bar",
	}
	code = c.Run(args)
	importOutput := importDone(t)
	if code != 0 {
		t.Fatalf("import failed; expected success: %s", importOutput.All())
	}
}

func TestImport_nonManagedResource(t *testing.T) {
	t.Chdir(testFixturePath("import-missing-resource-config"))

	statePath := testTempFile(t)

	p := testProvider()

	cases := []struct {
		resAddr        string
		expectedErrMsg string
	}{
		{
			resAddr:        "data.test_data_source.foo",
			expectedErrMsg: "A managed resource address is required. Importing into a data resource",
		},
		{
			resAddr:        "ephemeral.test_data_source.foo",
			expectedErrMsg: "A managed resource address is required. Importing into an ephemeral resource",
		},
	}
	for _, tt := range cases {
		t.Run(tt.resAddr, func(t *testing.T) {
			view, done := testView(t)
			c := &ImportCommand{
				Meta: Meta{
					WorkingDir:       workdir.NewDir("."),
					testingOverrides: metaOverridesForProvider(p),
					View:             view,
				},
			}

			args := []string{
				"-no-color",
				"-state", statePath,
				tt.resAddr,
				"bar",
			}
			code := c.Run(args)
			output := done(t)
			if code != 1 {
				t.Fatalf("import succeeded; expected failure: %s", output.All())
			}

			msg := output.Stderr()
			if want := tt.expectedErrMsg; !strings.Contains(msg, want) {
				t.Errorf("incorrect message\nwant substring: %s\ngot:\n%s", want, msg)
			}
		})
	}
}

func TestImport_invalidResourceAddr(t *testing.T) {
	t.Chdir(testFixturePath("import-missing-resource-config"))

	statePath := testTempFile(t)

	p := testProvider()
	view, done := testView(t)
	c := &ImportCommand{
		Meta: Meta{
			WorkingDir:       workdir.NewDir("."),
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	args := []string{
		"-no-color",
		"-state", statePath,
		"bananas",
		"bar",
	}
	code := c.Run(args)
	output := done(t)
	if code != 1 {
		t.Fatalf("import succeeded; expected failure")
	}

	msg := output.Stderr()
	if want := `Error: Invalid address`; !strings.Contains(msg, want) {
		t.Errorf("incorrect message\nwant substring: %s\ngot:\n%s", want, msg)
	}
}

func TestImport_targetIsModule(t *testing.T) {
	t.Chdir(testFixturePath("import-missing-resource-config"))

	statePath := testTempFile(t)

	p := testProvider()
	view, done := testView(t)
	c := &ImportCommand{
		Meta: Meta{
			WorkingDir:       workdir.NewDir("."),
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	args := []string{
		"-no-color",
		"-state", statePath,
		"module.foo",
		"bar",
	}
	code := c.Run(args)
	output := done(t)
	if code != 1 {
		t.Fatalf("import succeeded; expected failure")
	}

	msg := output.Stderr()
	if want := `Error: Invalid address`; !strings.Contains(msg, want) {
		t.Errorf("incorrect message\nwant substring: %s\ngot:\n%s", want, msg)
	}
}

func TestImport_ForEachKeyInTargetResourceAddr(t *testing.T) {
	t.Chdir(testFixturePath("import-non-existent-key"))

	statePath := testTempFile(t)
	p := testProvider()
	view, done := testView(t)
	c := &ImportCommand{
		Meta: Meta{
			WorkingDir:       workdir.NewDir("."),
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}
	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
		ResourceTypes: map[string]providers.Schema{
			"test_instance": {
				Block: &configschema.Block{
					Attributes: map[string]*configschema.Attribute{
						"id": {Type: cty.String, Optional: true, Computed: true},
					},
				},
			},
		},
	}
	// Existing key should success
	args := []string{
		"-state", statePath,
		"test_instance.this[\"a\"]",
		"aa",
	}
	code := c.Run(args)
	output := done(t)
	if code != 0 {
		t.Fatalf("import failed; expected success for existing resource: %s", output.Stderr())
	}
	// Non-existent key should fail
	args = []string{
		"-state", statePath,
		"test_instance.this[\"f\"]",
		"ff",
	}
	view, done = testView(t)
	c.Meta.View = view
	code = c.Run(args)
	output = done(t)
	if code == 0 {
		t.Fatalf("import succeeded; expected failure when the resource instance doesn't exist with the given key: %s", output.All())
	}

}

func TestImport_ForEachKeyInModuleAddr(t *testing.T) {
	td := t.TempDir()
	testCopyDir(t, testFixturePath("import-keyed-module"), td)
	t.Chdir(td)

	statePath := testTempFile(t)

	p := testProvider()
	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
		ResourceTypes: map[string]providers.Schema{
			"test_instance": {
				Block: &configschema.Block{
					Attributes: map[string]*configschema.Attribute{
						"foo": {Type: cty.String, Optional: true},
					},
				},
			},
		},
	}

	providerSource, closeCallback := newMockProviderSource(t, map[string][]string{
		"test": {"1.2.3"},
	})
	defer closeCallback()

	// init to install the module
	initView, initDone := testView(t)
	m := Meta{
		WorkingDir:       workdir.NewDir("."),
		testingOverrides: metaOverridesForProvider(testProvider()),
		View:             initView,
		ProviderSource:   providerSource,
	}

	ic := &InitCommand{
		Meta: m,
	}
	code := ic.Run([]string{})
	initOutput := initDone(t)
	if code != 0 {
		t.Fatalf("init failed\n%s", initOutput.Stderr())
	}

	// Import
	importView, importDone := testView(t)
	c := &ImportCommand{
		Meta: Meta{
			WorkingDir:       workdir.NewDir("."),
			testingOverrides: metaOverridesForProvider(p),
			View:             importView,
		},
	}
	// Importing into an existing module should succeed
	args := []string{
		"-state", statePath,
		"module.child[\"a\"].test_instance.this",
		"aa",
	}
	code = c.Run(args)
	importOutput := importDone(t)
	if code != 0 {
		t.Fatalf("import failed for a valid scenario\n%s", importOutput.Stderr())
	}

	// Importing into a non-existent module should fail
	args = []string{
		"-state", statePath,
		"module.child[\"f\"].test_instance.this",
		"ff",
	}
	importView, importDone = testView(t)
	c.Meta.View = importView
	code = c.Run(args)
	importOutput = importDone(t)
	if code == 0 {
		t.Fatalf("import succeeded; expected failure for non-existant module\n%s", importOutput.Stdout())
	}
}

func TestImport_ForEachKeyInModuleAndResourceAddr(t *testing.T) {
	td := t.TempDir()
	// We have the "child" module with keys "a" and "b"
	// Resource "test_instance.this" under the child module with keys "a" and "b"
	testCopyDir(t, testFixturePath("import-keyed-module-keyed-resource"), td)
	t.Chdir(td)

	statePath := testTempFile(t)

	p := testProvider()
	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
		ResourceTypes: map[string]providers.Schema{
			"test_instance": {
				Block: &configschema.Block{
					Attributes: map[string]*configschema.Attribute{
						"foo": {Type: cty.String, Optional: true},
					},
				},
			},
		},
	}

	providerSource, closeCallback := newMockProviderSource(t, map[string][]string{
		"test": {"1.2.3"},
	})
	defer closeCallback()

	// init to install the module
	initView, initDone := testView(t)
	m := Meta{
		WorkingDir:       workdir.NewDir("."),
		testingOverrides: metaOverridesForProvider(testProvider()),
		View:             initView,
		ProviderSource:   providerSource,
	}

	ic := &InitCommand{
		Meta: m,
	}
	code := ic.Run([]string{})
	initOutput := initDone(t)
	if code != 0 {
		t.Fatalf("init failed\n%s", initOutput.Stderr())
	}

	// Import
	importView, importDone := testView(t)
	c := &ImportCommand{
		Meta: Meta{
			WorkingDir:       workdir.NewDir("."),
			testingOverrides: metaOverridesForProvider(p),
			View:             importView,
		},
	}

	// Valid Module + Valid Resource success
	args := []string{
		"-state", statePath,
		"module.child[\"a\"].test_instance.this[\"a\"]",
		"aa",
	}
	code = c.Run(args)
	importOutput := importDone(t)
	if code != 0 {
		t.Fatalf("import failed for a valid scenario\n%s", importOutput.Stderr())
	}

	// All following combinations should fail
	// Valid Module + Invalid Resource
	args = []string{
		"-state", statePath,
		"module.child[\"a\"].test_instance.this[\"f\"]",
		"af",
	}
	importView, importDone = testView(t)
	c.Meta.View = importView
	code = c.Run(args)
	importOutput = importDone(t)
	if code == 0 {
		t.Fatalf("import succeeded; expected failure for non-existent resource instance\n%s", importOutput.Stdout())
	}

	// Invalid Module + Valid Resource
	args = []string{
		"-state", statePath,
		"module.child[\"f\"].test_instance.this[\"a\"]",
		"fa",
	}
	importView, importDone = testView(t)
	c.Meta.View = importView
	code = c.Run(args)
	importOutput = importDone(t)
	if code == 0 {
		t.Fatalf("import succeeded; expected failure for non-existent module\n%s", importOutput.Stdout())
	}

	// Invalid Module + Invalid Resource
	args = []string{
		"-state", statePath,
		"module.child[\"f\"].test_instance.this[\"f\"]",
		"ff",
	}
	importView, importDone = testView(t)
	c.Meta.View = importView
	code = c.Run(args)
	importOutput = importDone(t)
	if code == 0 {
		t.Fatalf("import succeeded; expected failure for non-existent module and resource instance\n%s", importOutput.Stdout())
	}
}

const testImportStr = `
test_instance.foo:
  ID = yay
  provider = provider["registry.opentofu.org/hashicorp/test"]
`
