package common

import (
	"io/ioutil"
	"os"
	"regexp"
	"testing"

	"github.com/hashicorp/packer/packer-plugin-sdk/communicator"
	"github.com/hashicorp/packer/packer-plugin-sdk/template/config"
)

func init() {
	// Clear out the AWS access key env vars so they don't
	// affect our tests.
	os.Setenv("AWS_ACCESS_KEY_ID", "")
	os.Setenv("AWS_ACCESS_KEY", "")
	os.Setenv("AWS_SECRET_ACCESS_KEY", "")
	os.Setenv("AWS_SECRET_KEY", "")
}

func testConfig() *RunConfig {
	return &RunConfig{
		SourceAmi:    "abcd",
		InstanceType: "m1.small",

		Comm: communicator.Config{
			SSH: communicator.SSH{
				SSHUsername: "foo",
			},
		},
	}
}

func testConfigFilter() *RunConfig {
	config := testConfig()
	config.SourceAmi = ""
	config.SourceAmiFilter = AmiFilterOptions{}
	return config
}

func TestRunConfigPrepare(t *testing.T) {
	c := testConfig()
	err := c.Prepare(nil)
	if len(err) > 0 {
		t.Fatalf("err: %s", err)
	}
}

func TestRunConfigPrepare_InstanceType(t *testing.T) {
	c := testConfig()
	c.InstanceType = ""
	if err := c.Prepare(nil); len(err) != 1 {
		t.Fatalf("Should error if an instance_type is not specified")
	}
}

func TestRunConfigPrepare_SourceAmi(t *testing.T) {
	c := testConfig()
	c.SourceAmi = ""
	if err := c.Prepare(nil); len(err) != 2 {
		t.Fatalf("Should error if a source_ami (or source_ami_filter) is not specified")
	}
}

func TestRunConfigPrepare_SourceAmiFilterBlank(t *testing.T) {
	c := testConfigFilter()
	if err := c.Prepare(nil); len(err) != 2 {
		t.Fatalf("Should error if source_ami_filter is empty or not specified (and source_ami is not specified)")
	}
}

func TestRunConfigPrepare_SourceAmiFilterOwnersBlank(t *testing.T) {
	c := testConfigFilter()
	filter_key := "name"
	filter_value := "foo"
	c.SourceAmiFilter.Filters = map[string]string{filter_key: filter_value}
	if err := c.Prepare(nil); len(err) != 1 {
		t.Fatalf("Should error if Owners is not specified)")
	}
}

func TestRunConfigPrepare_SourceAmiFilterGood(t *testing.T) {
	c := testConfigFilter()
	owner := "123"
	filter_key := "name"
	filter_value := "foo"
	goodFilter := AmiFilterOptions{
		Owners: []string{owner},
		KeyValueFilter: config.KeyValueFilter{
			Filters: map[string]string{filter_key: filter_value},
		},
	}
	c.SourceAmiFilter = goodFilter
	if err := c.Prepare(nil); len(err) != 0 {
		t.Fatalf("err: %s", err)
	}
}

func TestRunConfigPrepare_EnableT2UnlimitedGood(t *testing.T) {
	c := testConfig()
	// Must have a T2 instance type if T2 Unlimited is enabled
	c.InstanceType = "t2.micro"
	c.EnableT2Unlimited = true
	err := c.Prepare(nil)
	if len(err) > 0 {
		t.Fatalf("err: %s", err)
	}
}

func TestRunConfigPrepare_EnableT2UnlimitedBadInstanceType(t *testing.T) {
	c := testConfig()
	// T2 Unlimited cannot be used with instance types other than T2
	c.InstanceType = "m5.large"
	c.EnableT2Unlimited = true
	err := c.Prepare(nil)
	if len(err) != 1 {
		t.Fatalf("Should error if T2 Unlimited is enabled with non-T2 instance_type")
	}
}

func TestRunConfigPrepare_EnableT2UnlimitedBadWithSpotInstanceRequest(t *testing.T) {
	c := testConfig()
	// T2 Unlimited cannot be used with Spot Instances
	c.InstanceType = "t2.micro"
	c.EnableT2Unlimited = true
	c.SpotPrice = "auto"
	err := c.Prepare(nil)
	if len(err) != 1 {
		t.Fatalf("Should error if T2 Unlimited has been used in conjuntion with a Spot Price request")
	}
}

func TestRunConfigPrepare_SpotAuto(t *testing.T) {
	c := testConfig()
	c.SpotPrice = "auto"
	if err := c.Prepare(nil); len(err) != 0 {
		t.Fatalf("err: %s", err)
	}

	// Shouldn't error (YET) even though SpotPriceAutoProduct is deprecated
	c.SpotPriceAutoProduct = "Linux/Unix"
	if err := c.Prepare(nil); len(err) != 0 {
		t.Fatalf("err: %s", err)
	}
}

func TestRunConfigPrepare_SSHPort(t *testing.T) {
	c := testConfig()
	c.Comm.SSHPort = 0
	if err := c.Prepare(nil); len(err) != 0 {
		t.Fatalf("err: %s", err)
	}

	if c.Comm.SSHPort != 22 {
		t.Fatalf("invalid value: %d", c.Comm.SSHPort)
	}

	c.Comm.SSHPort = 44
	if err := c.Prepare(nil); len(err) != 0 {
		t.Fatalf("err: %s", err)
	}

	if c.Comm.SSHPort != 44 {
		t.Fatalf("invalid value: %d", c.Comm.SSHPort)
	}
}

func TestRunConfigPrepare_UserData(t *testing.T) {
	c := testConfig()
	tf, err := ioutil.TempFile("", "packer")
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	defer os.Remove(tf.Name())
	defer tf.Close()

	c.UserData = "foo"
	c.UserDataFile = tf.Name()
	if err := c.Prepare(nil); len(err) != 1 {
		t.Fatalf("Should error if user_data string and user_data_file have both been specified")
	}
}

func TestRunConfigPrepare_UserDataFile(t *testing.T) {
	c := testConfig()
	if err := c.Prepare(nil); len(err) != 0 {
		t.Fatalf("err: %s", err)
	}

	c.UserDataFile = "idontexistidontthink"
	if err := c.Prepare(nil); len(err) != 1 {
		t.Fatalf("Should error if the file specified by user_data_file does not exist")
	}

	tf, err := ioutil.TempFile("", "packer")
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	defer os.Remove(tf.Name())
	defer tf.Close()

	c.UserDataFile = tf.Name()
	if err := c.Prepare(nil); len(err) != 0 {
		t.Fatalf("err: %s", err)
	}
}

func TestRunConfigPrepare_TemporaryKeyPairName(t *testing.T) {
	c := testConfig()
	c.Comm.SSHTemporaryKeyPairName = ""
	if err := c.Prepare(nil); len(err) != 0 {
		t.Fatalf("err: %s", err)
	}

	if c.Comm.SSHTemporaryKeyPairName == "" {
		t.Fatal("keypair name is empty")
	}

	// Match prefix and UUID, e.g. "packer_5790d491-a0b8-c84c-c9d2-2aea55086550".
	r := regexp.MustCompile(`\Apacker_(?:(?i)[a-f\d]{8}(?:-[a-f\d]{4}){3}-[a-f\d]{12}?)\z`)
	if !r.MatchString(c.Comm.SSHTemporaryKeyPairName) {
		t.Fatal("keypair name is not valid")
	}

	c.Comm.SSHTemporaryKeyPairName = "ssh-key-123"
	if err := c.Prepare(nil); len(err) != 0 {
		t.Fatalf("err: %s", err)
	}

	if c.Comm.SSHTemporaryKeyPairName != "ssh-key-123" {
		t.Fatal("keypair name does not match")
	}
}

func TestRunConfigPrepare_TenancyBad(t *testing.T) {
	c := testConfig()
	c.Tenancy = "not_real"

	if err := c.Prepare(nil); len(err) != 1 {
		t.Fatal("Should error if tenancy is set to an invalid type")
	}
}

func TestRunConfigPrepare_TenancyGood(t *testing.T) {
	validTenancy := []string{"", "default", "dedicated", "host"}
	for _, vt := range validTenancy {
		c := testConfig()
		c.Tenancy = vt
		if err := c.Prepare(nil); len(err) != 0 {
			t.Fatalf("Should not error if tenancy is set to %s", vt)
		}
	}
}
