package awsproviderpatch_test

import (
	"testing"

	awsproviderpatch "github.com/gruntwork-io/terragrunt/internal/cli/commands/aws-provider-patch"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

const terraformCodeExampleOutputOnly = `
output "hello" {
  value = "Hello, World"
}
`

const terraformCodeExampleGcpProvider = `
provider "google" {
  credentials = file("account.json")
  project     = "my-project-id"
  region      = "us-central1"
}

output "hello" {
  value = "Hello, World"
}
`

const terraformCodeExampleAwsProviderEmptyOriginal = `
provider "aws" {
}

output "hello" {
  value = "Hello, World"
}
`

const terraformCodeExampleAwsProviderRegionVersionOverridenExpected = `
provider "aws" {
  region  = "eu-west-1"
  version = "0.3.0"
}

output "hello" {
  value = "Hello, World"
}
`

const terraformCodeExampleAwsProviderRegionVersionOverridenReverseOrderExpected = `
provider "aws" {
  version = "0.3.0"
  region  = "eu-west-1"
}

output "hello" {
  value = "Hello, World"
}
`

const terraformCodeExampleAwsProviderNonEmptyOriginal = `
provider "aws" {
  region  = var.aws_region
  version = "0.2.0"
}

output "hello" {
  value = "Hello, World"
}
`

const terraformCodeExampleAwsProviderRegionOverridenVersionNotOverriddenExpected = `
provider "aws" {
  region  = "eu-west-1"
  version = "0.2.0"
}

output "hello" {
  value = "Hello, World"
}
`

const terraformCodeExampleAwsMultipleProvidersOriginal = `
provider "aws" {
  region  = var.aws_region
  version = "0.2.0"
}

provider "aws" {
  alias   = "another"
  region  = var.aws_region
  version = "0.2.0"
}

resource "aws_instance" "example" {

}

provider "google" {
  credentials = file("account.json")
  project     = "my-project-id"
  region      = "us-central1"
}

provider "aws" {
  alias  = "yet another"
  region = var.aws_region
}

output "hello" {
  value = "Hello, World"
}
`

const terraformCodeExampleAwsMultipleProvidersRegionOverridenExpected = `
provider "aws" {
  region  = "eu-west-1"
  version = "0.2.0"
}

provider "aws" {
  alias   = "another"
  region  = "eu-west-1"
  version = "0.2.0"
}

resource "aws_instance" "example" {

}

provider "google" {
  credentials = file("account.json")
  project     = "my-project-id"
  region      = "us-central1"
}

provider "aws" {
  alias  = "yet another"
  region = "eu-west-1"
}

output "hello" {
  value = "Hello, World"
}
`

const terraformCodeExampleAwsMultipleProvidersRegionVersionOverridenExpected = `
provider "aws" {
  region  = "eu-west-1"
  version = "0.3.0"
}

provider "aws" {
  alias   = "another"
  region  = "eu-west-1"
  version = "0.3.0"
}

resource "aws_instance" "example" {

}

provider "google" {
  credentials = file("account.json")
  project     = "my-project-id"
  region      = "us-central1"
}

provider "aws" {
  alias  = "yet another"
  region = "eu-west-1"
}

output "hello" {
  value = "Hello, World"
}
`

const terraformCodeExampleAwsMultipleProvidersNonEmptyWithCommentsOriginal = `
# Make sure comments are maintained
# And don't interfere with parsing
provider "aws" {
  # Make sure comments are maintained
  # And don't interfere with parsing
  region = var.aws_region

  # Make sure comments are maintained
  # And don't interfere with parsing
  version = "0.2.0"
}

# Make sure comments are maintained
# And don't interfere with parsing
provider "aws" {
  # Make sure comments are maintained
  # And don't interfere with parsing
  region = var.aws_region

  # Make sure comments are maintained
  # And don't interfere with parsing
  version = "0.2.0"

  # Make sure comments are maintained
  # And don't interfere with parsing
  alias = "secondary"
}
`

const terraformCodeExampleAwsMultipleProvidersNonEmptyWithCommentsRegionOverriddenExpected = `
# Make sure comments are maintained
# And don't interfere with parsing
provider "aws" {
  # Make sure comments are maintained
  # And don't interfere with parsing
  region = "eu-west-1"

  # Make sure comments are maintained
  # And don't interfere with parsing
  version = "0.2.0"
}

# Make sure comments are maintained
# And don't interfere with parsing
provider "aws" {
  # Make sure comments are maintained
  # And don't interfere with parsing
  region = "eu-west-1"

  # Make sure comments are maintained
  # And don't interfere with parsing
  version = "0.2.0"

  # Make sure comments are maintained
  # And don't interfere with parsing
  alias = "secondary"
}
`

const terraformCodeExampleAwsMultipleProvidersNonEmptyWithCommentsRegionVersionOverriddenExpected = `
# Make sure comments are maintained
# And don't interfere with parsing
provider "aws" {
  # Make sure comments are maintained
  # And don't interfere with parsing
  region = "eu-west-1"

  # Make sure comments are maintained
  # And don't interfere with parsing
  version = "0.3.0"
}

# Make sure comments are maintained
# And don't interfere with parsing
provider "aws" {
  # Make sure comments are maintained
  # And don't interfere with parsing
  region = "eu-west-1"

  # Make sure comments are maintained
  # And don't interfere with parsing
  version = "0.3.0"

  # Make sure comments are maintained
  # And don't interfere with parsing
  alias = "secondary"
}
`

const terraformCodeExampleAwsOneProviderNestedBlocks = `
provider "aws" {
  region = var.aws_region
  assume_role {
    role_arn = var.role_arn
  }
}
`

const terraformCodeExampleAwsOneProviderNestedBlocksRegionRoleArnExpected = `
provider "aws" {
  region = "eu-west-1"
  assume_role {
    role_arn = "nested-override"
  }
}
`

func TestPatchAwsProviderInTerraformCodeHappyPath(t *testing.T) {
	t.Parallel()

	testCases := []struct {
		attributesToOverride   map[string]string
		testName               string
		originalTerraformCode  string
		expectedTerraformCode  []string
		expectedCodeWasUpdated bool
	}{
		{
			testName:               "empty",
			originalTerraformCode:  "",
			attributesToOverride:   nil,
			expectedCodeWasUpdated: false,
			expectedTerraformCode:  []string{""},
		},
		{
			testName:               "empty with attributes",
			originalTerraformCode:  "",
			attributesToOverride:   map[string]string{"region": `"eu-west-1"`},
			expectedCodeWasUpdated: false,
			expectedTerraformCode:  []string{""},
		},
		{
			testName:               "no provider",
			originalTerraformCode:  terraformCodeExampleOutputOnly,
			attributesToOverride:   map[string]string{"region": `"eu-west-1"`},
			expectedCodeWasUpdated: false,
			expectedTerraformCode:  []string{terraformCodeExampleOutputOnly},
		},
		{
			testName:               "no aws provider",
			originalTerraformCode:  terraformCodeExampleGcpProvider,
			attributesToOverride:   map[string]string{"region": `"eu-west-1"`},
			expectedCodeWasUpdated: false,
			expectedTerraformCode:  []string{terraformCodeExampleGcpProvider},
		},
		{
			testName:               "one empty aws provider, but no overrides",
			originalTerraformCode:  terraformCodeExampleAwsProviderEmptyOriginal,
			attributesToOverride:   nil,
			expectedCodeWasUpdated: false,
			expectedTerraformCode:  []string{terraformCodeExampleAwsProviderEmptyOriginal},
		},
		{
			testName:               "one empty aws provider, with region override",
			originalTerraformCode:  terraformCodeExampleAwsProviderEmptyOriginal,
			attributesToOverride:   map[string]string{"region": `"eu-west-1"`},
			expectedCodeWasUpdated: false,
			expectedTerraformCode:  []string{terraformCodeExampleAwsProviderEmptyOriginal},
		},
		{
			testName:              "one empty aws provider, with region, version override",
			originalTerraformCode: terraformCodeExampleAwsProviderEmptyOriginal,
			attributesToOverride: map[string]string{
				"region":  `"eu-west-1"`,
				"version": `"0.3.0"`,
			},
			expectedCodeWasUpdated: false,
			expectedTerraformCode:  []string{terraformCodeExampleAwsProviderEmptyOriginal},
		},
		{
			testName:               "one non-empty aws provider, but no overrides",
			originalTerraformCode:  terraformCodeExampleAwsProviderNonEmptyOriginal,
			attributesToOverride:   nil,
			expectedCodeWasUpdated: false,
			expectedTerraformCode:  []string{terraformCodeExampleAwsProviderNonEmptyOriginal},
		},
		{
			testName:               "one non-empty aws provider, with region override",
			originalTerraformCode:  terraformCodeExampleAwsProviderNonEmptyOriginal,
			attributesToOverride:   map[string]string{"region": `"eu-west-1"`},
			expectedCodeWasUpdated: true,
			expectedTerraformCode: []string{
				terraformCodeExampleAwsProviderRegionOverridenVersionNotOverriddenExpected,
			},
		},
		{
			testName:              "one non-empty aws provider, with region, version override",
			originalTerraformCode: terraformCodeExampleAwsProviderNonEmptyOriginal,
			attributesToOverride: map[string]string{
				"region":  `"eu-west-1"`,
				"version": `"0.3.0"`,
			},
			expectedCodeWasUpdated: true,
			expectedTerraformCode: []string{
				terraformCodeExampleAwsProviderRegionVersionOverridenExpected,
				terraformCodeExampleAwsProviderRegionVersionOverridenReverseOrderExpected,
			},
		},
		{
			testName:               "multiple providers, but no overrides",
			originalTerraformCode:  terraformCodeExampleAwsMultipleProvidersOriginal,
			attributesToOverride:   nil,
			expectedCodeWasUpdated: false,
			expectedTerraformCode:  []string{terraformCodeExampleAwsMultipleProvidersOriginal},
		},
		{
			testName:               "multiple providers, with region override",
			originalTerraformCode:  terraformCodeExampleAwsMultipleProvidersOriginal,
			attributesToOverride:   map[string]string{"region": `"eu-west-1"`},
			expectedCodeWasUpdated: true,
			expectedTerraformCode: []string{
				terraformCodeExampleAwsMultipleProvidersRegionOverridenExpected,
			},
		},
		{
			testName:              "multiple providers, with region, version override",
			originalTerraformCode: terraformCodeExampleAwsMultipleProvidersOriginal,
			attributesToOverride: map[string]string{
				"region":  `"eu-west-1"`,
				"version": `"0.3.0"`,
			},
			expectedCodeWasUpdated: true,
			expectedTerraformCode: []string{
				terraformCodeExampleAwsMultipleProvidersRegionVersionOverridenExpected,
			},
		},
		{
			testName:               "multiple providers with comments, but no overrides",
			originalTerraformCode:  terraformCodeExampleAwsMultipleProvidersNonEmptyWithCommentsOriginal,
			attributesToOverride:   nil,
			expectedCodeWasUpdated: false,
			expectedTerraformCode: []string{
				terraformCodeExampleAwsMultipleProvidersNonEmptyWithCommentsOriginal,
			},
		},
		{
			testName:               "multiple providers with comments, with region override",
			originalTerraformCode:  terraformCodeExampleAwsMultipleProvidersNonEmptyWithCommentsOriginal,
			attributesToOverride:   map[string]string{"region": `"eu-west-1"`},
			expectedCodeWasUpdated: true,
			expectedTerraformCode: []string{
				terraformCodeExampleAwsMultipleProvidersNonEmptyWithCommentsRegionOverriddenExpected,
			},
		},
		{
			testName:              "multiple providers with comments, with region, version override",
			originalTerraformCode: terraformCodeExampleAwsMultipleProvidersNonEmptyWithCommentsOriginal,
			attributesToOverride: map[string]string{
				"region":  `"eu-west-1"`,
				"version": `"0.3.0"`,
			},
			expectedCodeWasUpdated: true,
			expectedTerraformCode: []string{
				terraformCodeExampleAwsMultipleProvidersNonEmptyWithCommentsRegionVersionOverriddenExpected,
			},
		},
		{
			testName:              "one provider with nested blocks, with region and role_arn override",
			originalTerraformCode: terraformCodeExampleAwsOneProviderNestedBlocks,
			attributesToOverride: map[string]string{
				"region":               `"eu-west-1"`,
				"assume_role.role_arn": `"nested-override"`,
			},
			expectedCodeWasUpdated: true,
			expectedTerraformCode: []string{
				terraformCodeExampleAwsOneProviderNestedBlocksRegionRoleArnExpected,
			},
		},
		{
			testName:              "one provider with nested blocks, with region and role_arn override, plus non-matching overrides",
			originalTerraformCode: terraformCodeExampleAwsOneProviderNestedBlocks,
			attributesToOverride: map[string]string{
				"region":                `"eu-west-1"`,
				"assume_role.role_arn":  `"nested-override"`,
				"should-be":             `"ignored"`,
				"assume_role.should-be": `"ignored"`,
			},
			expectedCodeWasUpdated: true,
			expectedTerraformCode: []string{
				terraformCodeExampleAwsOneProviderNestedBlocksRegionRoleArnExpected,
			},
		},
	}

	for _, tc := range testCases {
		t.Run(tc.testName, func(t *testing.T) {
			t.Parallel()

			actualTerraformCode, actualCodeWasUpdated, err := awsproviderpatch.PatchAwsProviderInTerraformCode(
				tc.originalTerraformCode,
				"test.tf",
				tc.attributesToOverride,
			)
			require.NoError(t, err)
			assert.Equal(t, tc.expectedCodeWasUpdated, actualCodeWasUpdated)

			// We check an array  of possible expected code here due to possible ordering differences. That is, the
			// attributes within a provider block are stored in a map, and iteration order on maps is randomized, so
			// sometimes the provider block might come back with region first, followed by version, but other times,
			// the order is reversed. For those cases, we pass in multiple possible expected results and check that
			// one of them matches.
			assert.Contains(t, tc.expectedTerraformCode, actualTerraformCode)
		})
	}
}
