// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package template

import (
	"testing"

	"github.com/spf13/afero"

	"github.com/stretchr/testify/require"
)

func TestCFNType_ImportName(t *testing.T) {
	require.Equal(t, "aws_autoscaling", CFNType("AWS::AutoScaling::AutoScalingGroup").ImportName())
}

func TestCFNType_ImportShortRename(t *testing.T) {
	testCases := []struct {
		in     string
		wanted string
	}{
		{
			in:     "AWS::AutoScaling::AutoScalingGroup",
			wanted: "asg",
		},
		{
			in:     "AWS::Logs::LogGroup",
			wanted: "logs",
		},
		{
			in:     "AWS::ECS::Service",
			wanted: "ecs",
		},
		{
			in:     "AWS::DynamoDB::Table",
			wanted: "ddb",
		},
		{
			in:     "AWS::ApiGatewayV2::Api",
			wanted: "apigwv2",
		},
		{
			in:     "AWS::EC2::CapacityReservation",
			wanted: "ec2",
		},
		{
			in:     "AWS::ElasticLoadBalancingV2::Listener",
			wanted: "elbv2",
		},
	}

	for _, tc := range testCases {
		require.Equal(t, tc.wanted, CFNType(tc.in).ImportShortRename(), "unexpected short name for %q", tc.in)
	}
}

func TestCFNType_L1ConstructName(t *testing.T) {
	require.Equal(t, "CfnAutoScalingGroup", CFNType("AWS::AutoScaling::AutoScalingGroup").L1ConstructName())
}

type cdkImportTestDouble struct {
	importNameFn      func() string
	importShortRename func() string
}

// Assert that cdkImportTestDouble implements the CDKImport interface.
var _ CDKImport = (*cdkImportTestDouble)(nil)

func (td *cdkImportTestDouble) ImportName() string {
	return td.importNameFn()
}

func (td *cdkImportTestDouble) ImportShortRename() string {
	return td.importShortRename()
}

func TestCfnResources_Imports(t *testing.T) {
	// GIVEN
	resources := cfnResources([]CFNResource{
		{
			Type: "AWS::IAM::Role",
		},
		{
			Type: "AWS::ECS::Cluster",
		},
		{
			Type: "AWS::IAM::Role",
		},
		{
			Type: "AWS::ECR::Repository",
		},
		{
			Type: "AWS::ECR::Repository",
		},
		{
			Type: "AWS::ECS::Service",
		},
	})
	wanted := []CDKImport{
		&cdkImportTestDouble{
			importNameFn: func() string {
				return "aws_ecr"
			},
			importShortRename: func() string {
				return "ecr"
			},
		},
		&cdkImportTestDouble{
			importNameFn: func() string {
				return "aws_ecs"
			},
			importShortRename: func() string {
				return "ecs"
			},
		},
		&cdkImportTestDouble{
			importNameFn: func() string {
				return "aws_iam"
			},
			importShortRename: func() string {
				return "iam"
			},
		},
	}

	// WHEN
	imports := resources.Imports()

	// THEN
	require.Equal(t, len(imports), len(wanted), "expected number of imports to be equal")
	for i, lib := range imports {
		require.Equal(t, wanted[i].ImportName(), lib.ImportName(), "expected import names to match")
		require.Equal(t, wanted[i].ImportShortRename(), lib.ImportShortRename(), "expected import short renames to match")
	}
}

func TestTemplate_WalkOverridesCDKDir(t *testing.T) {
	// GIVEN
	fs := afero.NewMemMapFs()
	_ = fs.MkdirAll("templates/overrides/cdk/bin", 0755)
	_ = afero.WriteFile(fs, "templates/overrides/cdk/bin/app.js", []byte(`const app = new cdk.App();`), 0644)
	_ = afero.WriteFile(fs, "templates/overrides/cdk/package.json", []byte(`{
 "devDependencies": {
   "aws-cdk": "{{.Version}}",
   "ts-node": "^10.9.1",
   "typescript": "~4.9.4"
 },
 "dependencies": {
   "aws-cdk-lib": "{{.Version}}",
   "constructs": "^{{.ConstructsVersion}}",
   "source-map-support": "^0.5.21"
 }
}`), 0644)
	_ = afero.WriteFile(fs, "templates/overrides/cdk/stack.ts", []byte(`{{- range $import := .Resources.Imports }}
import { {{$import.ImportName}} as {{$import.ImportShortRename}} } from 'aws-cdk-lib';
{{- end }}
{{range $resource := .Resources}}
transform{{$resource.LogicalID}}() {
	const {{lowerInitialLetters $resource.LogicalID}} = this.template.getResource("{{$resource.LogicalID}}") as {{$resource.Type.ImportShortRename}}.{{$resource.Type.L1ConstructName}};
}
{{end}}
`), 0644)

	tpl := &Template{
		fs: &mockFS{Fs: fs},
	}

	input := []CFNResource{
		{
			Type:      "AWS::ECS::Service",
			LogicalID: "Service",
		},
		{
			Type:      "AWS::ElasticLoadBalancingV2::ListenerRule",
			LogicalID: "HTTPListenerRuleWithDomain",
		},
		{
			Type:      "AWS::ElasticLoadBalancingV2::ListenerRule",
			LogicalID: "HTTPListenerRule",
		},
	}

	requiresEnv := true

	// WHEN
	walked := map[string]bool{
		"package.json": false,
		"stack.ts":     false,
		"bin/app.js":   false,
	}
	err := tpl.WalkOverridesCDKDir(input, func(name string, content *Content) error {
		switch name {
		case "package.json":
			walked["package.json"] = true
			require.Equal(t, `{
 "devDependencies": {
   "aws-cdk": "2.137.0",
   "ts-node": "^10.9.1",
   "typescript": "~4.9.4"
 },
 "dependencies": {
   "aws-cdk-lib": "2.137.0",
   "constructs": "^10.0.0",
   "source-map-support": "^0.5.21"
 }
}`, content.String())
		case "stack.ts":
			walked["stack.ts"] = true
			require.Equal(t, `
import { aws_ecs as ecs } from 'aws-cdk-lib';
import { aws_elasticloadbalancingv2 as elbv2 } from 'aws-cdk-lib';

transformService() {
	const service = this.template.getResource("Service") as ecs.CfnService;
}

transformHTTPListenerRuleWithDomain() {
	const httplistenerRuleWithDomain = this.template.getResource("HTTPListenerRuleWithDomain") as elbv2.CfnListenerRule;
}

transformHTTPListenerRule() {
	const httplistenerRule = this.template.getResource("HTTPListenerRule") as elbv2.CfnListenerRule;
}

`, content.String())
		case "bin/app.js":
			walked["bin/app.js"] = true
			require.Equal(t, "const app = new cdk.App();", content.String())
		}
		return nil
	}, requiresEnv)

	// THEN
	require.NoError(t, err)
	for name, ok := range walked {
		if !ok {
			require.FailNowf(t, "missing walk file", "file %q was not walked", name)
		}
	}
}
