package middlewares

import (
	"strings"
	"testing"

	"github.com/aws/aws-sdk-go/aws/awsutil"
	"github.com/r3labs/diff/v2"
	"github.com/snyk/driftctl/pkg/resource"
	"github.com/snyk/driftctl/pkg/terraform"
	"github.com/stretchr/testify/mock"
)

func TestAwsInstanceBlockDeviceResourceMapper_Execute(t *testing.T) {
	type args struct {
		expectedResource   *[]*resource.Resource
		resourcesFromState *[]*resource.Resource
	}
	tests := []struct {
		name    string
		args    args
		mocks   func(factory *terraform.MockResourceFactory)
		wantErr bool
	}{
		{
			"Test with root block device and ebs block device",
			struct {
				expectedResource   *[]*resource.Resource
				resourcesFromState *[]*resource.Resource
			}{
				expectedResource: &[]*resource.Resource{
					{
						Id:   "dummy-instance",
						Type: "aws_instance",
						Attrs: &resource.Attributes{
							"availability_zone": "eu-west-3",
						},
					},
					{
						Id:   "vol-02862d9b39045a3a4",
						Type: "aws_ebs_volume",
						Attrs: &resource.Attributes{
							"id":                   "vol-02862d9b39045a3a4",
							"encrypted":            true,
							"multi_attach_enabled": false,
							"availability_zone":    "eu-west-3",
							"iops":                 1234,
							"kms_key_id":           "kms",
							"size":                 8,
							"type":                 "gp2",
							"throughput":           125,
							"tags": map[string]interface{}{
								"Name": "rootVol",
							},
						},
					},
					{
						Id:   "vol-018c5ae89895aca4c",
						Type: "aws_ebs_volume",
						Attrs: &resource.Attributes{
							"id":                   "vol-018c5ae89895aca4c",
							"encrypted":            true,
							"multi_attach_enabled": false,
							"availability_zone":    "eu-west-3",
							"size":                 23,
							"type":                 "gp2",
							"throughput":           125,
							"tags": map[string]interface{}{
								"Name": "rootVol",
							},
						},
					},
					{
						Id:    "vol-foobar",
						Type:  "aws_ebs_volume",
						Attrs: &resource.Attributes{},
					},
				},
				resourcesFromState: &[]*resource.Resource{
					{
						Id:    "vol-foobar",
						Type:  "aws_ebs_volume",
						Attrs: &resource.Attributes{},
					},
					{
						Id:   "dummy-instance",
						Type: "aws_instance",
						Attrs: &resource.Attributes{
							"availability_zone": "eu-west-3",
							"volume_tags": map[string]string{
								"Name": "rootVol",
							},
							"root_block_device": []interface{}{
								map[string]interface{}{
									"volume_id":   "vol-02862d9b39045a3a4",
									"volume_type": "gp2",
									"device_name": "/dev/sda1",
									"encrypted":   true,
									"kms_key_id":  "kms",
									"volume_size": 8,
									"throughput":  125,
									"iops":        1234,
								},
							},
							"ebs_block_device": []interface{}{
								map[string]interface{}{
									"volume_id":             "vol-018c5ae89895aca4c",
									"volume_type":           "gp2",
									"device_name":           "/dev/sdb",
									"encrypted":             true,
									"delete_on_termination": true,
									"volume_size":           23,
									"throughput":            125,
								},
							},
						},
					},
				},
			},
			func(factory *terraform.MockResourceFactory) {
				foo := resource.Resource{
					Id:   "vol-02862d9b39045a3a4",
					Type: "aws_ebs_volume",
					Attrs: &resource.Attributes{
						"id":                   "vol-02862d9b39045a3a4",
						"encrypted":            true,
						"multi_attach_enabled": false,
						"availability_zone":    "eu-west-3",
						"iops":                 1234,
						"kms_key_id":           "kms",
						"size":                 8,
						"type":                 "gp2",
						"throughput":           125,
						"tags": map[string]interface{}{
							"Name": "rootVol",
						},
					},
				}
				factory.On("CreateAbstractResource", "aws_ebs_volume", mock.Anything, mock.MatchedBy(func(input map[string]interface{}) bool {
					return input["id"] == "vol-02862d9b39045a3a4"
				})).Times(1).Return(&foo, nil)

				bar := resource.Resource{
					Id:   "vol-018c5ae89895aca4c",
					Type: "aws_ebs_volume",
					Attrs: &resource.Attributes{
						"id":                   "vol-018c5ae89895aca4c",
						"encrypted":            true,
						"multi_attach_enabled": false,
						"availability_zone":    "eu-west-3",
						"size":                 23,
						"type":                 "gp2",
						"throughput":           125,
						"tags": map[string]interface{}{
							"Name": "rootVol",
						},
					},
				}
				factory.On("CreateAbstractResource", "aws_ebs_volume", mock.Anything, mock.MatchedBy(func(input map[string]interface{}) bool {
					return input["id"] == "vol-018c5ae89895aca4c"
				})).Times(1).Return(&bar, nil)
			},
			false,
		},
		{
			"Test with tags inside root/ebs block device",
			struct {
				expectedResource   *[]*resource.Resource
				resourcesFromState *[]*resource.Resource
			}{
				expectedResource: &[]*resource.Resource{
					&resource.Resource{
						Id:   "dummy-instance",
						Type: "aws_instance",
						Attrs: &resource.Attributes{
							"availability_zone": "eu-west-3",
						},
					},
					&resource.Resource{
						Id:   "vol-02862d9b39045a3a4",
						Type: "aws_ebs_volume",
						Attrs: &resource.Attributes{
							"id":                   "vol-02862d9b39045a3a4",
							"encrypted":            true,
							"multi_attach_enabled": false,
							"availability_zone":    "eu-west-3",
							"iops":                 1234,
							"kms_key_id":           "kms",
							"size":                 8,
							"type":                 "gp2",
							"throughput":           125,
							"tags": map[string]interface{}{
								"Name": "rootVol",
							},
						},
					},
					&resource.Resource{
						Id:   "vol-018c5ae89895aca4c",
						Type: "aws_ebs_volume",
						Attrs: &resource.Attributes{
							"id":                   "vol-018c5ae89895aca4c",
							"encrypted":            true,
							"multi_attach_enabled": false,
							"availability_zone":    "eu-west-3",
							"size":                 23,
							"type":                 "gp2",
							"throughput":           125,
							"tags": map[string]interface{}{
								"Name": "ebsVol",
							},
						},
					},
				},
				resourcesFromState: &[]*resource.Resource{
					&resource.Resource{
						Id:   "dummy-instance",
						Type: "aws_instance",
						Attrs: &resource.Attributes{
							"availability_zone": "eu-west-3",
							"root_block_device": []interface{}{
								map[string]interface{}{
									"volume_id":   "vol-02862d9b39045a3a4",
									"volume_type": "gp2",
									"device_name": "/dev/sda1",
									"encrypted":   true,
									"kms_key_id":  "kms",
									"volume_size": 8,
									"throughput":  125,
									"iops":        1234,
									"tags": map[string]interface{}{
										"Name": "rootVol",
									},
								},
							},
							"ebs_block_device": []interface{}{
								map[string]interface{}{
									"volume_id":             "vol-018c5ae89895aca4c",
									"volume_type":           "gp2",
									"device_name":           "/dev/sdb",
									"encrypted":             true,
									"delete_on_termination": true,
									"volume_size":           23,
									"throughput":            125,
									"tags": map[string]interface{}{
										"Name": "ebsVol",
									},
								},
							},
						},
					},
				},
			},
			func(factory *terraform.MockResourceFactory) {
				foo := resource.Resource{
					Id:   "vol-02862d9b39045a3a4",
					Type: "aws_ebs_volume",
					Attrs: &resource.Attributes{
						"id":                   "vol-02862d9b39045a3a4",
						"encrypted":            true,
						"multi_attach_enabled": false,
						"availability_zone":    "eu-west-3",
						"iops":                 1234,
						"kms_key_id":           "kms",
						"size":                 8,
						"type":                 "gp2",
						"throughput":           125,
						"tags": map[string]interface{}{
							"Name": "rootVol",
						},
					},
				}
				factory.On("CreateAbstractResource", "aws_ebs_volume", mock.Anything, mock.MatchedBy(func(input map[string]interface{}) bool {
					return input["id"] == "vol-02862d9b39045a3a4" && len(input["tags"].(map[string]interface{})) == 1
				})).Times(1).Return(&foo, nil)

				bar := resource.Resource{
					Id:   "vol-018c5ae89895aca4c",
					Type: "aws_ebs_volume",
					Attrs: &resource.Attributes{
						"id":                   "vol-018c5ae89895aca4c",
						"encrypted":            true,
						"multi_attach_enabled": false,
						"availability_zone":    "eu-west-3",
						"size":                 23,
						"type":                 "gp2",
						"throughput":           125,
						"tags": map[string]interface{}{
							"Name": "ebsVol",
						},
					},
				}
				factory.On("CreateAbstractResource", "aws_ebs_volume", mock.Anything, mock.MatchedBy(func(input map[string]interface{}) bool {
					return input["id"] == "vol-018c5ae89895aca4c" && len(input["tags"].(map[string]interface{})) == 1
				})).Times(1).Return(&bar, nil)
			},
			false,
		},
		{
			"Should not create ebs volume if there is already one (e.g. inline ebs_block_device)",
			struct {
				expectedResource   *[]*resource.Resource
				resourcesFromState *[]*resource.Resource
			}{
				expectedResource: &[]*resource.Resource{
					&resource.Resource{
						Id:   "dummy-instance",
						Type: "aws_instance",
						Attrs: &resource.Attributes{
							"availability_zone": "eu-west-3",
						},
					},
					&resource.Resource{
						Id:   "vol-02862d9b39045a3a4",
						Type: "aws_ebs_volume",
						Attrs: &resource.Attributes{
							"id": "vol-02862d9b39045a3a4",
						},
					},
				},
				resourcesFromState: &[]*resource.Resource{
					&resource.Resource{
						Id:   "vol-02862d9b39045a3a4",
						Type: "aws_ebs_volume",
						Attrs: &resource.Attributes{
							"id": "vol-02862d9b39045a3a4",
						},
					},
					&resource.Resource{
						Id:   "dummy-instance",
						Type: "aws_instance",
						Attrs: &resource.Attributes{
							"availability_zone": "eu-west-3",
							"ebs_block_device": []interface{}{
								map[string]interface{}{
									"volume_id": "vol-02862d9b39045a3a4",
								},
							},
						},
					},
				},
			},
			func(factory *terraform.MockResourceFactory) {},
			false,
		},
	}
	for _, c := range tests {
		t.Run(c.name, func(tt *testing.T) {

			factory := &terraform.MockResourceFactory{}
			if c.mocks != nil {
				c.mocks(factory)
			}

			a := NewAwsInstanceBlockDeviceResourceMapper(factory)
			if err := a.Execute(&[]*resource.Resource{}, c.args.resourcesFromState); (err != nil) != c.wantErr {
				t.Errorf("Execute() error = %v, wantErr %v", err, c.wantErr)
			}
			changelog, err := diff.Diff(c.args.resourcesFromState, c.args.expectedResource)
			if err != nil {
				tt.Error(err)
			}
			if len(changelog) > 0 {
				for _, change := range changelog {
					t.Errorf("%s got = %v, want %v", strings.Join(change.Path, "."), awsutil.Prettify(change.From), awsutil.Prettify(change.To))
				}
			}
		})
	}
}
