/*
Deregister the test image with
aws ec2 deregister-image --image-id $(aws ec2 describe-images --output text --filters "Name=name,Values=packer-test-packer-test-dereg" --query 'Images[*].{ID:ImageId}')
*/
package ebs

import (
	"fmt"
	"os"
	"testing"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/ec2"
	"github.com/hashicorp/packer/builder/amazon/common"
	builderT "github.com/hashicorp/packer/packer-plugin-sdk/acctest"
	packersdk "github.com/hashicorp/packer/packer-plugin-sdk/packer"
)

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

func TestBuilderAcc_regionCopy(t *testing.T) {
	builderT.Test(t, builderT.TestCase{
		PreCheck: func() { testAccPreCheck(t) },
		Builder:  &Builder{},
		Template: testBuilderAccRegionCopy,
		Check:    checkRegionCopy([]string{"us-east-1", "us-west-2"}),
	})
}

func TestBuilderAcc_forceDeregister(t *testing.T) {
	// Build the same AMI name twice, with force_deregister on the second run
	builderT.Test(t, builderT.TestCase{
		PreCheck:             func() { testAccPreCheck(t) },
		Builder:              &Builder{},
		Template:             buildForceDeregisterConfig("false", "dereg"),
		SkipArtifactTeardown: true,
	})

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

func TestBuilderAcc_forceDeleteSnapshot(t *testing.T) {
	amiName := "packer-test-dereg"

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

	// Get image data by AMI name
	ec2conn, _ := testEC2Conn()
	describeInput := &ec2.DescribeImagesInput{Filters: []*ec2.Filter{
		{
			Name:   aws.String("name"),
			Values: []*string{aws.String(amiName)},
		},
	}}
	ec2conn.WaitUntilImageExists(describeInput)
	imageResp, _ := ec2conn.DescribeImages(describeInput)
	image := imageResp.Images[0]

	// Get snapshot ids for image
	snapshotIds := []*string{}
	for _, device := range image.BlockDeviceMappings {
		if device.Ebs != nil && device.Ebs.SnapshotId != nil {
			snapshotIds = append(snapshotIds, device.Ebs.SnapshotId)
		}
	}

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

func checkSnapshotsDeleted(snapshotIds []*string) builderT.TestCheckFunc {
	return func(artifacts []packersdk.Artifact) error {
		// Verify the snapshots are gone
		ec2conn, _ := testEC2Conn()
		snapshotResp, _ := ec2conn.DescribeSnapshots(
			&ec2.DescribeSnapshotsInput{SnapshotIds: snapshotIds},
		)

		if len(snapshotResp.Snapshots) > 0 {
			return fmt.Errorf("Snapshots weren't successfully deleted by `force_delete_snapshot`")
		}
		return nil
	}
}

func TestBuilderAcc_amiSharing(t *testing.T) {
	builderT.Test(t, builderT.TestCase{
		PreCheck: func() { testAccSharingPreCheck(t) },
		Builder:  &Builder{},
		Template: buildSharingConfig(os.Getenv("TESTACC_AWS_ACCOUNT_ID")),
		Check:    checkAMISharing(2, os.Getenv("TESTACC_AWS_ACCOUNT_ID"), "all"),
	})
}

func TestBuilderAcc_encryptedBoot(t *testing.T) {
	builderT.Test(t, builderT.TestCase{
		PreCheck: func() { testAccPreCheck(t) },
		Builder:  &Builder{},
		Template: testBuilderAccEncrypted,
		Check:    checkBootEncrypted(),
	})
}

func checkAMISharing(count int, uid, group string) builderT.TestCheckFunc {
	return func(artifacts []packersdk.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.(*common.Artifact)
		if !ok {
			return fmt.Errorf("unknown artifact: %#v", artifactRaw)
		}

		// describe the image, get block devices with a snapshot
		ec2conn, _ := testEC2Conn()
		imageResp, err := ec2conn.DescribeImageAttribute(&ec2.DescribeImageAttributeInput{
			Attribute: aws.String("launchPermission"),
			ImageId:   aws.String(artifact.Amis["us-east-1"]),
		})

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

		// Launch Permissions are in addition to the userid that created it, so if
		// you add 3 additional ami_users, you expect 2 Launch Permissions here
		if len(imageResp.LaunchPermissions) != count {
			return fmt.Errorf("Error in Image Attributes, expected (%d) Launch Permissions, got (%d)", count, len(imageResp.LaunchPermissions))
		}

		userFound := false
		for _, lp := range imageResp.LaunchPermissions {
			if lp.UserId != nil && uid == *lp.UserId {
				userFound = true
			}
		}

		if !userFound {
			return fmt.Errorf("Error in Image Attributes, expected User ID (%s) to have Launch Permissions, but was not found", uid)
		}

		groupFound := false
		for _, lp := range imageResp.LaunchPermissions {
			if lp.Group != nil && group == *lp.Group {
				groupFound = true
			}
		}

		if !groupFound {
			return fmt.Errorf("Error in Image Attributes, expected Group ID (%s) to have Launch Permissions, but was not found", group)
		}

		return nil
	}
}

func checkRegionCopy(regions []string) builderT.TestCheckFunc {
	return func(artifacts []packersdk.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.(*common.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.Amis {
			if _, ok := regionSet[r]; !ok {
				return fmt.Errorf("unknown region: %s", r)
			}

			delete(regionSet, r)
		}
		if len(regionSet) > 0 {
			return fmt.Errorf("didn't copy to: %#v", regionSet)
		}

		return nil
	}
}

func checkBootEncrypted() builderT.TestCheckFunc {
	return func(artifacts []packersdk.Artifact) error {

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

		// describe the image, get block devices with a snapshot
		ec2conn, _ := testEC2Conn()
		imageResp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{
			ImageIds: []*string{aws.String(artifact.Amis["us-east-1"])},
		})

		if err != nil {
			return fmt.Errorf("Error retrieving Image Attributes for AMI (%s) in AMI Encrypted Boot Test: %s", artifact, err)
		}

		image := imageResp.Images[0] // Only requested a single AMI ID

		rootDeviceName := image.RootDeviceName

		for _, bd := range image.BlockDeviceMappings {
			if *bd.DeviceName == *rootDeviceName {
				if *bd.Ebs.Encrypted != true {
					return fmt.Errorf("volume not encrypted: %s", *bd.Ebs.SnapshotId)
				}
			}
		}

		return nil
	}
}

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

func testAccPreCheck(t *testing.T) {
}

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

func testEC2Conn() (*ec2.EC2, error) {
	access := &common.AccessConfig{RawRegion: "us-east-1"}
	session, err := access.Session()
	if err != nil {
		return nil, err
	}

	return ec2.New(session), nil
}

const testBuilderAccBasic = `
{
	"builders": [{
		"type": "test",
		"region": "us-east-1",
		"instance_type": "m3.medium",
		"source_ami": "ami-76b2a71e",
		"ssh_username": "ubuntu",
		"ami_name": "packer-test {{timestamp}}"
	}]
}
`

const testBuilderAccRegionCopy = `
{
	"builders": [{
		"type": "test",
		"region": "us-east-1",
		"instance_type": "m3.medium",
		"source_ami": "ami-76b2a71e",
		"ssh_username": "ubuntu",
		"ami_name": "packer-test {{timestamp}}",
		"ami_regions": ["us-east-1", "us-west-2"]
	}]
}
`

const testBuilderAccForceDeregister = `
{
	"builders": [{
		"type": "test",
		"region": "us-east-1",
		"instance_type": "m3.medium",
		"source_ami": "ami-76b2a71e",
		"ssh_username": "ubuntu",
		"force_deregister": "%s",
		"ami_name": "%s"
	}]
}
`

const testBuilderAccForceDeleteSnapshot = `
{
	"builders": [{
		"type": "test",
		"region": "us-east-1",
		"instance_type": "m3.medium",
		"source_ami": "ami-76b2a71e",
		"ssh_username": "ubuntu",
		"force_deregister": "%s",
		"force_delete_snapshot": "%s",
		"ami_name": "%s"
	}]
}
`

const testBuilderAccSharing = `
{
	"builders": [{
		"type": "test",
		"region": "us-east-1",
		"instance_type": "m3.medium",
		"source_ami": "ami-76b2a71e",
		"ssh_username": "ubuntu",
		"ami_name": "packer-test {{timestamp}}",
		"ami_users":["%s"],
		"ami_groups":["all"]
	}]
}
`

const testBuilderAccEncrypted = `
{
	"builders": [{
		"type": "test",
		"region": "us-east-1",
		"instance_type": "m3.medium",
		"source_ami":"ami-c15bebaa",
		"ssh_username": "ubuntu",
		"ami_name": "packer-enc-test {{timestamp}}",
		"encrypt_boot": true
	}]
}
`

const testBuilderAccSessionManagerInterface = `
{
	"builders": [{
		"type": "test",
		"region": "us-east-1",
		"instance_type": "m3.medium",
		"source_ami_filter": {
				"filters": {
						"virtualization-type": "hvm",
						"name": "ubuntu/images/*ubuntu-xenial-16.04-amd64-server-*",
						"root-device-type": "ebs"
				},
				"owners": [
						"099720109477"
				],
				"most_recent": true
		},
		"ssh_username": "ubuntu",
		"ssh_interface": "session_manager",
		"iam_instance_profile": "SSMInstanceProfile",
		"ami_name": "packer-ssm-test-{{timestamp}}"
	}]
}
`

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

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

func buildSharingConfig(val string) string {
	return fmt.Sprintf(testBuilderAccSharing, val)
}
