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

package configs

import (
	"os"
	"testing"

	"github.com/google/go-cmp/cmp"
	"github.com/hashicorp/hcl/v2"
	"github.com/hashicorp/hcl/v2/hclsyntax"

	"github.com/opentofu/opentofu/internal/addrs"
)

func TestProviderReservedNames(t *testing.T) {
	src, err := os.ReadFile("testdata/invalid-files/provider-reserved.tf")
	if err != nil {
		t.Fatal(err)
	}
	parser := testParser(map[string]string{
		"config.tf": string(src),
	})
	_, diags := parser.LoadConfigFile("config.tf")

	assertExactDiagnostics(t, diags, []string{
		//TODO: This deprecation warning will be removed in OpenTofu v0.15.
		`config.tf:4,13-20: Version constraints inside provider configuration blocks are deprecated; OpenTofu 0.13 and earlier allowed provider version constraints inside the provider configuration block, but that is now deprecated and will be removed in a future version of OpenTofu. To silence this warning, move the provider version constraint into the required_providers block.`,
		`config.tf:10,3-8: Reserved argument name in provider block; The provider argument name "count" is reserved for use by OpenTofu in a future version.`,
		`config.tf:11,3-13: Reserved argument name in provider block; The provider argument name "depends_on" is reserved for use by OpenTofu in a future version.`,
		`config.tf:12,3-9: Reserved argument name in provider block; The provider argument name "source" is reserved for use by OpenTofu in a future version.`,
		`config.tf:13,3-12: Reserved block type name in provider block; The block type name "lifecycle" is reserved for use by OpenTofu in a future version.`,
		`config.tf:14,3-9: Reserved block type name in provider block; The block type name "locals" is reserved for use by OpenTofu in a future version.`,
	})
}

func TestParseProviderConfigCompact(t *testing.T) {
	tests := []struct {
		Input    string
		Want     addrs.LocalProviderConfig
		WantDiag string
	}{
		{
			`aws`,
			addrs.LocalProviderConfig{
				LocalName: "aws",
			},
			``,
		},
		{
			`aws.foo`,
			addrs.LocalProviderConfig{
				LocalName: "aws",
				Alias:     "foo",
			},
			``,
		},
		{
			`aws["foo"]`,
			addrs.LocalProviderConfig{},
			`The provider type name must either stand alone or be followed by an alias name separated with a dot.`,
		},
	}

	for _, test := range tests {
		t.Run(test.Input, func(t *testing.T) {
			traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(test.Input), "", hcl.Pos{})
			if len(parseDiags) != 0 {
				t.Errorf("unexpected diagnostics during parse")
				for _, diag := range parseDiags {
					t.Logf("- %s", diag)
				}
				return
			}

			got, diags := ParseProviderConfigCompact(traversal)

			if test.WantDiag != "" {
				if len(diags) != 1 {
					t.Fatalf("got %d diagnostics; want 1", len(diags))
				}
				gotDetail := diags[0].Description().Detail
				if gotDetail != test.WantDiag {
					t.Fatalf("wrong diagnostic detail\ngot:  %s\nwant: %s", gotDetail, test.WantDiag)
				}
				return
			} else {
				if len(diags) != 0 {
					t.Fatalf("got %d diagnostics; want 0", len(diags))
				}
			}

			if diff := cmp.Diff(test.Want, got); diff != "" {
				t.Error("wrong result:\n" + diff)
			}
		})
	}
}

func TestParseProviderConfigCompactStr(t *testing.T) {
	tests := []struct {
		Input    string
		Want     addrs.LocalProviderConfig
		WantDiag string
	}{
		{
			`aws`,
			addrs.LocalProviderConfig{
				LocalName: "aws",
			},
			``,
		},
		{
			`aws.foo`,
			addrs.LocalProviderConfig{
				LocalName: "aws",
				Alias:     "foo",
			},
			``,
		},
		{
			`aws["foo"]`,
			addrs.LocalProviderConfig{},
			`The provider type name must either stand alone or be followed by an alias name separated with a dot.`,
		},
	}

	for _, test := range tests {
		t.Run(test.Input, func(t *testing.T) {
			got, diags := ParseProviderConfigCompactStr(test.Input)

			if test.WantDiag != "" {
				if len(diags) != 1 {
					t.Fatalf("got %d diagnostics; want 1", len(diags))
				}
				gotDetail := diags[0].Description().Detail
				if gotDetail != test.WantDiag {
					t.Fatalf("wrong diagnostic detail\ngot:  %s\nwant: %s", gotDetail, test.WantDiag)
				}
				return
			} else {
				if len(diags) != 0 {
					t.Fatalf("got %d diagnostics; want 0", len(diags))
				}
			}

			if diff := cmp.Diff(test.Want, got); diff != "" {
				t.Error("wrong result:\n" + diff)
			}
		})
	}
}
