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

package aws_kms

import (
	"fmt"
	"reflect"
	"testing"
	"time"

	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/feature/ec2/imds"
	"github.com/aws/aws-sdk-go-v2/service/kms/types"
	"github.com/davecgh/go-spew/spew"
	awsbase "github.com/hashicorp/aws-sdk-go-base/v2"
	"github.com/hashicorp/hcl/v2"
	"github.com/hashicorp/hcl/v2/gohcl"
	"github.com/hashicorp/hcl/v2/hclsyntax"
	"github.com/opentofu/opentofu/internal/httpclient"
	"github.com/opentofu/opentofu/version"
)

func TestConfig_asAWSBase(t *testing.T) {
	testCases := []struct {
		name     string
		input    string
		expected awsbase.Config
	}{
		{
			name: "minconfig",
			input: `
				kms_key_id = "my-kms-key-id"
				key_spec = "AES_256"
				region = "magic-mountain"`,
			expected: awsbase.Config{
				Region:                 "magic-mountain",
				CallerDocumentationURL: "https://opentofu.org/docs/language/state/encryption/#aws-kms",
				CallerName:             "KMS Key Provider",
				MaxRetries:             5,
				APNInfo: &awsbase.APNInfo{
					PartnerName: "OpenTofu-AWS-KMS",
					Products: []awsbase.UserAgentProduct{
						{Name: httpclient.DefaultApplicationName, Version: version.String()},
					},
				},
			},
		},
		{
			name: "maxconfig",
			input: `
				kms_key_id = "my-kms-key-id"
				key_spec = "AES_256"

				access_key = "my-access-key"
				endpoints {
					iam = "endpoint-iam"
					sts = "endpoint-sts"
				}
				max_retries = 42
				profile = "my-profile"
				region = "my-region"
				secret_key = "my-secret-key"
				skip_credentials_validation = true
				skip_requesting_account_id = true
				sts_region = "my-sts-region"
				token = "my-token"
				http_proxy = "my-http-proxy"
				https_proxy = "my-https-proxy"
				no_proxy = "my-noproxy"
				insecure = true
				use_dualstack_endpoint = true
				use_fips_endpoint = true
				custom_ca_bundle = "my-custom-ca-bundle"
				ec2_metadata_service_endpoint = "my-emde"
				ec2_metadata_service_endpoint_mode = "my-emde-mode"
				skip_metadata_api_check = false
				shared_credentials_files = ["my-scredf"]
				shared_config_files = ["my-sconff"]
				assume_role = {
					role_arn = "ar_arn"
					duration = "4h"
					external_id = "ar_extid"
					policy = "ar_policy"
					policy_arns = ["arn:aws:iam::123456789012:policy/AR"]
					session_name = "ar_session_name"
					tags = {
						foo = "bar"
					}
					transitive_tag_keys = ["ar_tags"]
				}
				assume_role_with_web_identity = {
					role_arn = "wi_arn"
					duration = "5h"
					policy = "wi_policy"
					policy_arns = ["arn:aws:iam::123456789012:policy/WI"]
					session_name = "wi_session_name"
					web_identity_token = "wi_token"
					//web_identity_token_file = "wi_token_file"
				}
				allowed_account_ids = ["account"]
				//forbidden_account_ids = ?
				retry_mode = "adaptive"
				`,
			expected: awsbase.Config{
				CallerDocumentationURL: "https://opentofu.org/docs/language/state/encryption/#aws-kms",
				CallerName:             "KMS Key Provider",
				APNInfo: &awsbase.APNInfo{
					PartnerName: "OpenTofu-AWS-KMS",
					Products: []awsbase.UserAgentProduct{
						{Name: httpclient.DefaultApplicationName, Version: version.String()},
					},
				},
				AccessKey:                      "my-access-key",
				IamEndpoint:                    "https://endpoint-iam",
				MaxRetries:                     42,
				Profile:                        "my-profile",
				Region:                         "my-region",
				SecretKey:                      "my-secret-key",
				SkipCredsValidation:            true,
				SkipRequestingAccountId:        true,
				StsEndpoint:                    "https://endpoint-sts",
				StsRegion:                      "my-sts-region",
				Token:                          "my-token",
				HTTPProxy:                      aws.String("my-http-proxy"),
				HTTPSProxy:                     aws.String("my-https-proxy"),
				NoProxy:                        "my-noproxy",
				Insecure:                       true,
				UseDualStackEndpoint:           true,
				UseFIPSEndpoint:                true,
				CustomCABundle:                 "my-custom-ca-bundle",
				EC2MetadataServiceEnableState:  imds.ClientDisabled,
				EC2MetadataServiceEndpoint:     "my-emde",
				EC2MetadataServiceEndpointMode: "my-emde-mode",
				SharedCredentialsFiles:         []string{"my-scredf"},
				SharedConfigFiles:              []string{"my-sconff"},
				AssumeRole: []awsbase.AssumeRole{
					{
						RoleARN:    "ar_arn",
						Duration:   time.Hour * 4,
						ExternalID: "ar_extid",
						Policy:     "ar_policy",
						PolicyARNs: []string{
							"arn:aws:iam::123456789012:policy/AR",
						},
						SessionName: "ar_session_name",
						Tags: map[string]string{
							"foo": "bar",
						},
						TransitiveTagKeys: []string{
							"ar_tags",
						},
					},
				},
				AssumeRoleWithWebIdentity: &awsbase.AssumeRoleWithWebIdentity{
					RoleARN:  "wi_arn",
					Duration: time.Hour * 5,
					Policy:   "wi_policy",
					PolicyARNs: []string{
						"arn:aws:iam::123456789012:policy/WI",
					},
					SessionName:          "wi_session_name",
					WebIdentityToken:     "wi_token",
					WebIdentityTokenFile: "",
				},
				AllowedAccountIds: []string{"account"},
				RetryMode:         aws.RetryModeAdaptive,
			},
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			input, diags := hclsyntax.ParseConfig([]byte(tc.input), "test", hcl.InitialPos)
			if diags.HasErrors() {
				t.Fatal(diags.Error())
			}

			config := new(Config)

			diags = gohcl.DecodeBody(input.Body, nil, config)
			if diags.HasErrors() {
				t.Fatal(diags.Error())
			}

			if config.KMSKeyID != "my-kms-key-id" {
				t.Fatal("missing kms_key_id")
			}
			if config.KeySpec != "AES_256" {
				t.Fatal("missing key_spec")
			}

			actual, err := config.asAWSBase()
			if err != nil {
				t.Fatal(err.Error())
			}
			if !reflect.DeepEqual(tc.expected, *actual) {
				t.Fatalf("Expected %s, got %s", spew.Sdump(tc.expected), spew.Sdump(*actual))
			}
		})
	}
}

func TestValidate(t *testing.T) {
	testCases := []struct {
		name     string
		input    Config
		expected error
	}{
		{
			name: "valid",
			input: Config{
				KMSKeyID: "my-kms-key-id",
				KeySpec:  "AES_256",
			},
			expected: nil,
		},
		{
			name: "missing kms_key_id",
			input: Config{
				KMSKeyID: "",
				KeySpec:  "AES_256",
			},
			expected: fmt.Errorf("no kms_key_id provided"),
		},
		{
			name: "missing key_spec",
			input: Config{
				KMSKeyID: "my-kms-key-id",
				KeySpec:  "",
			},
			expected: fmt.Errorf("no key_spec provided"),
		},
		{
			name: "invalid key_spec",
			input: Config{
				KMSKeyID: "my-kms-key-id",
				KeySpec:  "invalid??",
			},
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			err := tc.input.validate()
			// check if the error message is the same
			if tc.expected != nil {
				if err.Error() != tc.expected.Error() {
					t.Fatalf("Expected %q, got %q", tc.expected.Error(), err.Error())
				}
			}
		})
	}
}

func TestGetKeySpecAsAWSType(t *testing.T) {

	aes256 := types.DataKeySpecAes256
	aes128 := types.DataKeySpecAes128

	cases := []struct {
		key      string
		expected *types.DataKeySpec
	}{
		{
			key:      "AES_256",
			expected: &aes256,
		},
		{
			key:      "AES_128",
			expected: &aes128,
		},
		{
			key:      "",
			expected: nil,
		},
		{
			key:      "invalidKey",
			expected: nil,
		},
	}

	for _, c := range cases {
		t.Run(c.key, func(t *testing.T) {
			config := Config{
				KeySpec: c.key,
			}
			actual := config.getKeySpecAsAWSType()
			if !reflect.DeepEqual(c.expected, actual) {
				t.Fatalf("Expected %s, got %s", spew.Sdump(c.expected), spew.Sdump(actual))
			}
		})
	}
}
