package ecs

import (
	"encoding/json"
	"fmt"
	"os"
	"strings"
	"testing"

	"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
	builderT "github.com/hashicorp/packer/helper/builder/testing"
	"github.com/hashicorp/packer/packer"
)

const defaultTestRegion = "cn-beijing"

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

	if os.Getenv(builderT.TestEnvVar) == "" {
		t.Skip(fmt.Sprintf("Acceptance tests skipped unless env '%s' set", builderT.TestEnvVar))
		return
	}

	testAccPreCheck(t)

	access := &AlicloudAccessConfig{AlicloudRegion: "cn-beijing"}
	err := access.Config()
	if err != nil {
		t.Fatalf("init AlicloudAccessConfig failed: %s", err)
	}

	err = access.ValidateRegion("cn-hangzhou")
	if err != nil {
		t.Fatalf("Expect pass with valid region id but failed: %s", err)
	}

	err = access.ValidateRegion("invalidRegionId")
	if err == nil {
		t.Fatal("Expect failure due to invalid region id but passed")
	}
}

func TestBuilderAcc_basic(t *testing.T) {
	t.Parallel()
	builderT.Test(t, builderT.TestCase{
		PreCheck: func() {
			testAccPreCheck(t)
		},
		Builder:  &Builder{},
		Template: testBuilderAccBasic,
	})
}

const testBuilderAccBasic = `
{	"builders": [{
		"type": "test",
		"region": "cn-beijing",
		"instance_type": "ecs.n1.tiny",
		"source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd",
		"io_optimized":"true",
		"ssh_username":"root",
		"image_name": "packer-test-basic_{{timestamp}}"
	}]
}`

func TestBuilderAcc_withDiskSettings(t *testing.T) {
	t.Parallel()
	builderT.Test(t, builderT.TestCase{
		PreCheck: func() {
			testAccPreCheck(t)
		},
		Builder:  &Builder{},
		Template: testBuilderAccWithDiskSettings,
		Check:    checkImageDisksSettings(),
	})
}

const testBuilderAccWithDiskSettings = `
{	"builders": [{
		"type": "test",
		"region": "cn-beijing",
		"instance_type": "ecs.n1.tiny",
		"source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd",
		"io_optimized":"true",
		"ssh_username":"root",
		"image_name": "packer-test-withDiskSettings_{{timestamp}}",
		"system_disk_mapping": {
			"disk_size": 60
		},
		"image_disk_mappings": [
			{
				"disk_name": "datadisk1",
				"disk_size": 25,
				"disk_delete_with_instance": true
			},
			{
				"disk_name": "datadisk2",
				"disk_size": 25,
				"disk_delete_with_instance": true
			}
		]
	}]
}`

func checkImageDisksSettings() builderT.TestCheckFunc {
	return func(artifacts []packer.Artifact) error {
		if len(artifacts) > 1 {
			return fmt.Errorf("more than 1 artifact")
		}

		// Get the actual *Artifact pointer so we can access the AMIs directly
		artifactRaw := artifacts[0]
		artifact, ok := artifactRaw.(*Artifact)
		if !ok {
			return fmt.Errorf("unknown artifact: %#v", artifactRaw)
		}
		imageId := artifact.AlicloudImages[defaultTestRegion]

		// describe the image, get block devices with a snapshot
		client, _ := testAliyunClient()

		describeImagesRequest := ecs.CreateDescribeImagesRequest()
		describeImagesRequest.RegionId = defaultTestRegion
		describeImagesRequest.ImageId = imageId
		imagesResponse, err := client.DescribeImages(describeImagesRequest)
		if err != nil {
			return fmt.Errorf("describe images failed due to %s", err)
		}

		if len(imagesResponse.Images.Image) == 0 {
			return fmt.Errorf("image %s generated can not be found", imageId)
		}

		image := imagesResponse.Images.Image[0]
		if image.Size != 60 {
			return fmt.Errorf("the size of image %s should be equal to 60G but got %dG", imageId, image.Size)
		}
		if len(image.DiskDeviceMappings.DiskDeviceMapping) != 3 {
			return fmt.Errorf("image %s should contains 3 disks", imageId)
		}

		var snapshotIds []string
		for _, mapping := range image.DiskDeviceMappings.DiskDeviceMapping {
			if mapping.Type == DiskTypeSystem {
				if mapping.Size != "60" {
					return fmt.Errorf("the system snapshot size of image %s should be equal to 60G but got %sG", imageId, mapping.Size)
				}
			} else {
				if mapping.Size != "25" {
					return fmt.Errorf("the data disk size of image %s should be equal to 25G but got %sG", imageId, mapping.Size)
				}

				snapshotIds = append(snapshotIds, mapping.SnapshotId)
			}
		}

		data, _ := json.Marshal(snapshotIds)

		describeSnapshotRequest := ecs.CreateDescribeSnapshotsRequest()
		describeSnapshotRequest.RegionId = defaultTestRegion
		describeSnapshotRequest.SnapshotIds = string(data)
		describeSnapshotsResponse, err := client.DescribeSnapshots(describeSnapshotRequest)
		if err != nil {
			return fmt.Errorf("describe data snapshots failed due to %s", err)
		}
		if len(describeSnapshotsResponse.Snapshots.Snapshot) != 2 {
			return fmt.Errorf("expect %d data snapshots but got %d", len(snapshotIds), len(describeSnapshotsResponse.Snapshots.Snapshot))
		}

		var dataDiskIds []string
		for _, snapshot := range describeSnapshotsResponse.Snapshots.Snapshot {
			dataDiskIds = append(dataDiskIds, snapshot.SourceDiskId)
		}
		data, _ = json.Marshal(dataDiskIds)

		describeDisksRequest := ecs.CreateDescribeDisksRequest()
		describeDisksRequest.RegionId = defaultTestRegion
		describeDisksRequest.DiskIds = string(data)
		describeDisksResponse, err := client.DescribeDisks(describeDisksRequest)
		if err != nil {
			return fmt.Errorf("describe snapshots failed due to %s", err)
		}
		if len(describeDisksResponse.Disks.Disk) != 0 {
			return fmt.Errorf("data disks should be deleted but %d left", len(describeDisksResponse.Disks.Disk))
		}

		return nil
	}
}

func TestBuilderAcc_withIgnoreDataDisks(t *testing.T) {
	t.Parallel()
	builderT.Test(t, builderT.TestCase{
		PreCheck: func() {
			testAccPreCheck(t)
		},
		Builder:  &Builder{},
		Template: testBuilderAccIgnoreDataDisks,
		Check:    checkIgnoreDataDisks(),
	})
}

const testBuilderAccIgnoreDataDisks = `
{	"builders": [{
		"type": "test",
		"region": "cn-beijing",
		"instance_type": "ecs.gn5-c8g1.2xlarge",
		"source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd",
		"io_optimized":"true",
		"ssh_username":"root",
		"image_name": "packer-test-ignoreDataDisks_{{timestamp}}",
		"image_ignore_data_disks": true
	}]
}`

func checkIgnoreDataDisks() builderT.TestCheckFunc {
	return func(artifacts []packer.Artifact) error {
		if len(artifacts) > 1 {
			return fmt.Errorf("more than 1 artifact")
		}

		// Get the actual *Artifact pointer so we can access the AMIs directly
		artifactRaw := artifacts[0]
		artifact, ok := artifactRaw.(*Artifact)
		if !ok {
			return fmt.Errorf("unknown artifact: %#v", artifactRaw)
		}
		imageId := artifact.AlicloudImages[defaultTestRegion]

		// describe the image, get block devices with a snapshot
		client, _ := testAliyunClient()

		describeImagesRequest := ecs.CreateDescribeImagesRequest()
		describeImagesRequest.RegionId = defaultTestRegion
		describeImagesRequest.ImageId = imageId
		imagesResponse, err := client.DescribeImages(describeImagesRequest)
		if err != nil {
			return fmt.Errorf("describe images failed due to %s", err)
		}

		if len(imagesResponse.Images.Image) == 0 {
			return fmt.Errorf("image %s generated can not be found", imageId)
		}

		image := imagesResponse.Images.Image[0]
		if len(image.DiskDeviceMappings.DiskDeviceMapping) != 1 {
			return fmt.Errorf("image %s should only contain one disks", imageId)
		}

		return nil
	}
}

func TestBuilderAcc_windows(t *testing.T) {
	t.Parallel()
	builderT.Test(t, builderT.TestCase{
		PreCheck: func() {
			testAccPreCheck(t)
		},
		Builder:  &Builder{},
		Template: testBuilderAccWindows,
	})
}

const testBuilderAccWindows = `
{	"builders": [{
		"type": "test",
		"region": "cn-beijing",
		"instance_type": "ecs.n1.tiny",
		"source_image":"winsvr_64_dtcC_1809_en-us_40G_alibase_20190318.vhd",
		"io_optimized":"true",
		"communicator": "winrm",
		"winrm_port": 5985,
		"winrm_username": "Administrator",
		"winrm_password": "Test1234",
		"image_name": "packer-test-windows_{{timestamp}}",
		"user_data_file": "../../../examples/alicloud/basic/winrm_enable_userdata.ps1"
	}]
}`

func TestBuilderAcc_regionCopy(t *testing.T) {
	t.Parallel()
	builderT.Test(t, builderT.TestCase{
		PreCheck: func() {
			testAccPreCheck(t)
		},
		Builder:  &Builder{},
		Template: testBuilderAccRegionCopy,
		Check:    checkRegionCopy([]string{"cn-hangzhou", "cn-shenzhen"}),
	})
}

const testBuilderAccRegionCopy = `
{
	"builders": [{
		"type": "test",
		"region": "cn-beijing",
		"instance_type": "ecs.n1.tiny",
		"source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd",
		"io_optimized":"true",
		"ssh_username":"root",
		"image_name": "packer-test-regionCopy_{{timestamp}}",
		"image_copy_regions": ["cn-hangzhou", "cn-shenzhen"],
		"image_copy_names": ["packer-copy-test-hz_{{timestamp}}", "packer-copy-test-sz_{{timestamp}}"]
	}]
}
`

func checkRegionCopy(regions []string) builderT.TestCheckFunc {
	return func(artifacts []packer.Artifact) error {
		if len(artifacts) > 1 {
			return fmt.Errorf("more than 1 artifact")
		}

		// Get the actual *Artifact pointer so we can access the AMIs directly
		artifactRaw := artifacts[0]
		artifact, ok := artifactRaw.(*Artifact)
		if !ok {
			return fmt.Errorf("unknown artifact: %#v", artifactRaw)
		}

		// Verify that we copied to only the regions given
		regionSet := make(map[string]struct{})
		for _, r := range regions {
			regionSet[r] = struct{}{}
		}

		for r := range artifact.AlicloudImages {
			if r == "cn-beijing" {
				delete(regionSet, r)
				continue
			}

			if _, ok := regionSet[r]; !ok {
				return fmt.Errorf("region %s is not the target region but found in artifacts", r)
			}

			delete(regionSet, r)
		}

		if len(regionSet) > 0 {
			return fmt.Errorf("following region(s) should be the copying targets but corresponding artifact(s) not found: %#v", regionSet)
		}

		client, _ := testAliyunClient()
		for regionId, imageId := range artifact.AlicloudImages {
			describeImagesRequest := ecs.CreateDescribeImagesRequest()
			describeImagesRequest.RegionId = regionId
			describeImagesRequest.ImageId = imageId
			describeImagesRequest.Status = ImageStatusQueried
			describeImagesResponse, err := client.DescribeImages(describeImagesRequest)
			if err != nil {
				return fmt.Errorf("describe generated image %s failed due to %s", imageId, err)
			}
			if len(describeImagesResponse.Images.Image) == 0 {
				return fmt.Errorf("image %s in artifacts can not be found", imageId)
			}

			image := describeImagesResponse.Images.Image[0]
			if image.IsCopied && regionId == "cn-hangzhou" && !strings.HasPrefix(image.ImageName, "packer-copy-test-hz") {
				return fmt.Errorf("the name of image %s in artifacts should begin with %s but got %s", imageId, "packer-copy-test-hz", image.ImageName)
			}
			if image.IsCopied && regionId == "cn-shenzhen" && !strings.HasPrefix(image.ImageName, "packer-copy-test-sz") {
				return fmt.Errorf("the name of image %s in artifacts should begin with %s but got %s", imageId, "packer-copy-test-sz", image.ImageName)
			}
		}

		return nil
	}
}

func TestBuilderAcc_forceDelete(t *testing.T) {
	t.Parallel()
	// Build the same alicloud image twice, with ecs_image_force_delete on the second run
	builderT.Test(t, builderT.TestCase{
		PreCheck: func() {
			testAccPreCheck(t)
		},
		Builder:              &Builder{},
		Template:             buildForceDeregisterConfig("false", "delete"),
		SkipArtifactTeardown: true,
	})

	builderT.Test(t, builderT.TestCase{
		PreCheck: func() {
			testAccPreCheck(t)
		},
		Builder:  &Builder{},
		Template: buildForceDeregisterConfig("true", "delete"),
	})
}

func buildForceDeregisterConfig(val, name string) string {
	return fmt.Sprintf(testBuilderAccForceDelete, val, name)
}

const testBuilderAccForceDelete = `
{
	"builders": [{
		"type": "test",
		"region": "cn-beijing",
		"instance_type": "ecs.n1.tiny",
		"source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd",
		"io_optimized":"true",
		"ssh_username":"root",
		"image_force_delete": "%s",
		"image_name": "packer-test-forceDelete_%s"
	}]
}
`

func TestBuilderAcc_ECSImageSharing(t *testing.T) {
	t.Parallel()
	builderT.Test(t, builderT.TestCase{
		PreCheck: func() {
			testAccPreCheck(t)
		},
		Builder:  &Builder{},
		Template: testBuilderAccSharing,
		Check:    checkECSImageSharing("1309208528360047"),
	})
}

// share with catsby
const testBuilderAccSharing = `
{
	"builders": [{
		"type": "test",
		"region": "cn-beijing",
		"instance_type": "ecs.n1.tiny",
		"source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd",
		"io_optimized":"true",
		"ssh_username":"root",
		"image_name": "packer-test-ECSImageSharing_{{timestamp}}",
		"image_share_account":["1309208528360047"]
	}]
}
`

func checkECSImageSharing(uid string) builderT.TestCheckFunc {
	return func(artifacts []packer.Artifact) error {
		if len(artifacts) > 1 {
			return fmt.Errorf("more than 1 artifact")
		}

		// Get the actual *Artifact pointer so we can access the AMIs directly
		artifactRaw := artifacts[0]
		artifact, ok := artifactRaw.(*Artifact)
		if !ok {
			return fmt.Errorf("unknown artifact: %#v", artifactRaw)
		}

		// describe the image, get block devices with a snapshot
		client, _ := testAliyunClient()

		describeImageShareRequest := ecs.CreateDescribeImageSharePermissionRequest()
		describeImageShareRequest.RegionId = "cn-beijing"
		describeImageShareRequest.ImageId = artifact.AlicloudImages["cn-beijing"]
		imageShareResponse, err := client.DescribeImageSharePermission(describeImageShareRequest)

		if err != nil {
			return fmt.Errorf("Error retrieving Image Attributes for ECS Image Artifact (%#v) "+
				"in ECS Image Sharing Test: %s", artifact, err)
		}

		if len(imageShareResponse.Accounts.Account) != 1 && imageShareResponse.Accounts.Account[0].AliyunId != uid {
			return fmt.Errorf("share account is incorrect %d", len(imageShareResponse.Accounts.Account))
		}

		return nil
	}
}

func TestBuilderAcc_forceDeleteSnapshot(t *testing.T) {
	t.Parallel()
	destImageName := "delete"

	// Build the same alicloud image name twice, with force_delete_snapshot on the second run
	builderT.Test(t, builderT.TestCase{
		PreCheck: func() {
			testAccPreCheck(t)
		},
		Builder:              &Builder{},
		Template:             buildForceDeleteSnapshotConfig("false", destImageName),
		SkipArtifactTeardown: true,
	})

	// Get image data by image image name
	client, _ := testAliyunClient()

	describeImagesRequest := ecs.CreateDescribeImagesRequest()
	describeImagesRequest.RegionId = "cn-beijing"
	describeImagesRequest.ImageName = "packer-test-" + destImageName
	images, _ := client.DescribeImages(describeImagesRequest)

	image := images.Images.Image[0]

	// Get snapshot ids for image
	snapshotIds := []string{}
	for _, device := range image.DiskDeviceMappings.DiskDeviceMapping {
		if device.Device != "" && device.SnapshotId != "" {
			snapshotIds = append(snapshotIds, device.SnapshotId)
		}
	}

	builderT.Test(t, builderT.TestCase{
		PreCheck: func() {
			testAccPreCheck(t)
		},
		Builder:  &Builder{},
		Template: buildForceDeleteSnapshotConfig("true", destImageName),
		Check:    checkSnapshotsDeleted(snapshotIds),
	})
}

func buildForceDeleteSnapshotConfig(val, name string) string {
	return fmt.Sprintf(testBuilderAccForceDeleteSnapshot, val, val, name)
}

const testBuilderAccForceDeleteSnapshot = `
{
	"builders": [{
		"type": "test",
		"region": "cn-beijing",
		"instance_type": "ecs.n1.tiny",
		"source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd",
		"io_optimized":"true",
		"ssh_username":"root",
		"image_force_delete_snapshots": "%s",
		"image_force_delete": "%s",
		"image_name": "packer-test-%s"
	}]
}
`

func checkSnapshotsDeleted(snapshotIds []string) builderT.TestCheckFunc {
	return func(artifacts []packer.Artifact) error {
		// Verify the snapshots are gone
		client, _ := testAliyunClient()
		data, err := json.Marshal(snapshotIds)
		if err != nil {
			return fmt.Errorf("Marshal snapshotIds array failed %v", err)
		}

		describeSnapshotsRequest := ecs.CreateDescribeSnapshotsRequest()
		describeSnapshotsRequest.RegionId = "cn-beijing"
		describeSnapshotsRequest.SnapshotIds = string(data)
		snapshotResp, err := client.DescribeSnapshots(describeSnapshotsRequest)
		if err != nil {
			return fmt.Errorf("Query snapshot failed %v", err)
		}
		snapshots := snapshotResp.Snapshots.Snapshot
		if len(snapshots) > 0 {
			return fmt.Errorf("Snapshots weren't successfully deleted by " +
				"`ecs_image_force_delete_snapshots`")
		}
		return nil
	}
}

func TestBuilderAcc_imageTags(t *testing.T) {
	t.Parallel()
	builderT.Test(t, builderT.TestCase{
		PreCheck: func() {
			testAccPreCheck(t)
		},
		Builder:  &Builder{},
		Template: testBuilderAccImageTags,
		Check:    checkImageTags(),
	})
}

const testBuilderAccImageTags = `
{	"builders": [{
		"type": "test",
		"region": "cn-beijing",
		"instance_type": "ecs.n1.tiny",
		"source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd",
		"ssh_username": "root",
		"io_optimized":"true",
		"image_name": "packer-test-imageTags_{{timestamp}}",
		"tags": {
			"TagKey1": "TagValue1",
			"TagKey2": "TagValue2"
       }
	}]
}`

func checkImageTags() builderT.TestCheckFunc {
	return func(artifacts []packer.Artifact) error {
		if len(artifacts) > 1 {
			return fmt.Errorf("more than 1 artifact")
		}
		// Get the actual *Artifact pointer so we can access the AMIs directly
		artifactRaw := artifacts[0]
		artifact, ok := artifactRaw.(*Artifact)
		if !ok {
			return fmt.Errorf("unknown artifact: %#v", artifactRaw)
		}
		imageId := artifact.AlicloudImages[defaultTestRegion]

		// describe the image, get block devices with a snapshot
		client, _ := testAliyunClient()

		describeImageTagsRequest := ecs.CreateDescribeTagsRequest()
		describeImageTagsRequest.RegionId = defaultTestRegion
		describeImageTagsRequest.ResourceType = TagResourceImage
		describeImageTagsRequest.ResourceId = imageId
		imageTagsResponse, err := client.DescribeTags(describeImageTagsRequest)
		if err != nil {
			return fmt.Errorf("Error retrieving Image Attributes for ECS Image Artifact (%#v) "+
				"in ECS Image Tags Test: %s", artifact, err)
		}

		if len(imageTagsResponse.Tags.Tag) != 2 {
			return fmt.Errorf("expect 2 tags set on image %s but got %d", imageId, len(imageTagsResponse.Tags.Tag))
		}

		for _, tag := range imageTagsResponse.Tags.Tag {
			if tag.TagKey != "TagKey1" && tag.TagKey != "TagKey2" {
				return fmt.Errorf("tags on image %s should be within the list of TagKey1 and TagKey2 but got %s", imageId, tag.TagKey)
			}

			if tag.TagKey == "TagKey1" && tag.TagValue != "TagValue1" {
				return fmt.Errorf("the value for tag %s on image %s should be TagValue1 but got %s", tag.TagKey, imageId, tag.TagValue)
			} else if tag.TagKey == "TagKey2" && tag.TagValue != "TagValue2" {
				return fmt.Errorf("the value for tag %s on image %s should be TagValue2 but got %s", tag.TagKey, imageId, tag.TagValue)
			}
		}

		describeImagesRequest := ecs.CreateDescribeImagesRequest()
		describeImagesRequest.RegionId = defaultTestRegion
		describeImagesRequest.ImageId = imageId
		imagesResponse, err := client.DescribeImages(describeImagesRequest)
		if err != nil {
			return fmt.Errorf("describe images failed due to %s", err)
		}

		if len(imagesResponse.Images.Image) == 0 {
			return fmt.Errorf("image %s generated can not be found", imageId)
		}

		image := imagesResponse.Images.Image[0]
		for _, mapping := range image.DiskDeviceMappings.DiskDeviceMapping {
			describeSnapshotTagsRequest := ecs.CreateDescribeTagsRequest()
			describeSnapshotTagsRequest.RegionId = defaultTestRegion
			describeSnapshotTagsRequest.ResourceType = TagResourceSnapshot
			describeSnapshotTagsRequest.ResourceId = mapping.SnapshotId
			snapshotTagsResponse, err := client.DescribeTags(describeSnapshotTagsRequest)
			if err != nil {
				return fmt.Errorf("failed to get snapshot tags due to %s", err)
			}

			if len(snapshotTagsResponse.Tags.Tag) != 2 {
				return fmt.Errorf("expect 2 tags set on snapshot %s but got %d", mapping.SnapshotId, len(snapshotTagsResponse.Tags.Tag))
			}

			for _, tag := range snapshotTagsResponse.Tags.Tag {
				if tag.TagKey != "TagKey1" && tag.TagKey != "TagKey2" {
					return fmt.Errorf("tags on snapshot %s should be within the list of TagKey1 and TagKey2 but got %s", mapping.SnapshotId, tag.TagKey)
				}

				if tag.TagKey == "TagKey1" && tag.TagValue != "TagValue1" {
					return fmt.Errorf("the value for tag %s on snapshot %s should be TagValue1 but got %s", tag.TagKey, mapping.SnapshotId, tag.TagValue)
				} else if tag.TagKey == "TagKey2" && tag.TagValue != "TagValue2" {
					return fmt.Errorf("the value for tag %s on snapshot %s should be TagValue2 but got %s", tag.TagKey, mapping.SnapshotId, tag.TagValue)
				}
			}
		}

		return nil
	}
}

func TestBuilderAcc_dataDiskEncrypted(t *testing.T) {
	t.Parallel()
	builderT.Test(t, builderT.TestCase{
		PreCheck: func() {
			testAccPreCheck(t)
		},
		Builder:  &Builder{},
		Template: testBuilderAccDataDiskEncrypted,
		Check:    checkDataDiskEncrypted(),
	})
}

const testBuilderAccDataDiskEncrypted = `
{	"builders": [{
		"type": "test",
		"region": "cn-beijing",
		"instance_type": "ecs.n1.tiny",
		"source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd",
		"io_optimized":"true",
		"ssh_username":"root",
		"image_name": "packer-test-dataDiskEncrypted_{{timestamp}}",
		"image_disk_mappings": [
			{
				"disk_name": "data_disk1",
				"disk_size": 25,
				"disk_encrypted": true,
				"disk_delete_with_instance": true
			},
			{
				"disk_name": "data_disk2",
				"disk_size": 35,
				"disk_encrypted": false,
				"disk_delete_with_instance": true
			},
			{
				"disk_name": "data_disk3",
				"disk_size": 45,
				"disk_delete_with_instance": true
			}
		]
	}]
}`

func checkDataDiskEncrypted() builderT.TestCheckFunc {
	return func(artifacts []packer.Artifact) error {
		if len(artifacts) > 1 {
			return fmt.Errorf("more than 1 artifact")
		}

		// Get the actual *Artifact pointer so we can access the AMIs directly
		artifactRaw := artifacts[0]
		artifact, ok := artifactRaw.(*Artifact)
		if !ok {
			return fmt.Errorf("unknown artifact: %#v", artifactRaw)
		}
		imageId := artifact.AlicloudImages[defaultTestRegion]

		// describe the image, get block devices with a snapshot
		client, _ := testAliyunClient()

		describeImagesRequest := ecs.CreateDescribeImagesRequest()
		describeImagesRequest.RegionId = defaultTestRegion
		describeImagesRequest.ImageId = imageId
		imagesResponse, err := client.DescribeImages(describeImagesRequest)
		if err != nil {
			return fmt.Errorf("describe images failed due to %s", err)
		}

		if len(imagesResponse.Images.Image) == 0 {
			return fmt.Errorf("image %s generated can not be found", imageId)
		}
		image := imagesResponse.Images.Image[0]

		var snapshotIds []string
		for _, mapping := range image.DiskDeviceMappings.DiskDeviceMapping {
			snapshotIds = append(snapshotIds, mapping.SnapshotId)
		}

		data, _ := json.Marshal(snapshotIds)

		describeSnapshotRequest := ecs.CreateDescribeSnapshotsRequest()
		describeSnapshotRequest.RegionId = defaultTestRegion
		describeSnapshotRequest.SnapshotIds = string(data)
		describeSnapshotsResponse, err := client.DescribeSnapshots(describeSnapshotRequest)
		if err != nil {
			return fmt.Errorf("describe data snapshots failed due to %s", err)
		}
		if len(describeSnapshotsResponse.Snapshots.Snapshot) != 4 {
			return fmt.Errorf("expect %d data snapshots but got %d", len(snapshotIds), len(describeSnapshotsResponse.Snapshots.Snapshot))
		}
		snapshots := describeSnapshotsResponse.Snapshots.Snapshot
		for _, snapshot := range snapshots {
			if snapshot.SourceDiskType == DiskTypeSystem {
				if snapshot.Encrypted != false {
					return fmt.Errorf("the system snapshot expected to be non-encrypted but got true")
				}

				continue
			}

			if snapshot.SourceDiskSize == "25" && snapshot.Encrypted != true {
				return fmt.Errorf("the first snapshot expected to be encrypted but got false")
			}

			if snapshot.SourceDiskSize == "35" && snapshot.Encrypted != false {
				return fmt.Errorf("the second snapshot expected to be non-encrypted but got true")
			}

			if snapshot.SourceDiskSize == "45" && snapshot.Encrypted != false {
				return fmt.Errorf("the third snapshot expected to be non-encrypted but got true")
			}
		}
		return nil
	}
}

func TestBuilderAcc_systemDiskEncrypted(t *testing.T) {
	t.Parallel()
	builderT.Test(t, builderT.TestCase{
		PreCheck: func() {
			testAccPreCheck(t)
		},
		Builder:  &Builder{},
		Template: testBuilderAccSystemDiskEncrypted,
		Check:    checkSystemDiskEncrypted(),
	})
}

const testBuilderAccSystemDiskEncrypted = `
{
	"builders": [{
		"type": "test",
		"region": "cn-beijing",
		"instance_type": "ecs.n1.tiny",
		"source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd",
		"io_optimized":"true",
		"ssh_username":"root",
		"image_name": "packer-test_{{timestamp}}",
		"image_encrypted": "true"
	}]
}`

func checkSystemDiskEncrypted() builderT.TestCheckFunc {
	return func(artifacts []packer.Artifact) error {
		if len(artifacts) > 1 {
			return fmt.Errorf("more than 1 artifact")
		}

		// Get the actual *Artifact pointer so we can access the AMIs directly
		artifactRaw := artifacts[0]
		artifact, ok := artifactRaw.(*Artifact)
		if !ok {
			return fmt.Errorf("unknown artifact: %#v", artifactRaw)
		}

		// describe the image, get block devices with a snapshot
		client, _ := testAliyunClient()
		imageId := artifact.AlicloudImages[defaultTestRegion]

		describeImagesRequest := ecs.CreateDescribeImagesRequest()
		describeImagesRequest.RegionId = defaultTestRegion
		describeImagesRequest.ImageId = imageId
		describeImagesRequest.Status = ImageStatusQueried
		imagesResponse, err := client.DescribeImages(describeImagesRequest)
		if err != nil {
			return fmt.Errorf("describe images failed due to %s", err)
		}

		if len(imagesResponse.Images.Image) == 0 {
			return fmt.Errorf("image %s generated can not be found", imageId)
		}

		image := imagesResponse.Images.Image[0]
		if image.IsCopied == false {
			return fmt.Errorf("image %s generated expexted to be copied but false", image.ImageId)
		}

		describeSnapshotRequest := ecs.CreateDescribeSnapshotsRequest()
		describeSnapshotRequest.RegionId = defaultTestRegion
		describeSnapshotRequest.SnapshotIds = fmt.Sprintf("[\"%s\"]", image.DiskDeviceMappings.DiskDeviceMapping[0].SnapshotId)
		describeSnapshotsResponse, err := client.DescribeSnapshots(describeSnapshotRequest)
		if err != nil {
			return fmt.Errorf("describe system snapshots failed due to %s", err)
		}
		snapshots := describeSnapshotsResponse.Snapshots.Snapshot[0]

		if snapshots.Encrypted != true {
			return fmt.Errorf("system snapshot of image %s expected to be encrypted but got false", imageId)
		}

		return nil
	}
}

func testAccPreCheck(t *testing.T) {
	if v := os.Getenv("ALICLOUD_ACCESS_KEY"); v == "" {
		t.Fatal("ALICLOUD_ACCESS_KEY must be set for acceptance tests")
	}

	if v := os.Getenv("ALICLOUD_SECRET_KEY"); v == "" {
		t.Fatal("ALICLOUD_SECRET_KEY must be set for acceptance tests")
	}
}

func testAliyunClient() (*ClientWrapper, error) {
	access := &AlicloudAccessConfig{AlicloudRegion: "cn-beijing"}
	err := access.Config()
	if err != nil {
		return nil, err
	}
	client, err := access.Client()
	if err != nil {
		return nil, err
	}

	return client, nil
}
