package test

import (
	"errors"
	"fmt"
	"regexp"
	"strings"
	"testing"

	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/terraform"
)

func TestDataSource_dataSourceCount(t *testing.T) {
	resource.UnitTest(t, resource.TestCase{
		Providers: testAccProviders,
		CheckDestroy: func(s *terraform.State) error {
			return nil
		},
		Steps: []resource.TestStep{
			{
				Config: strings.TrimSpace(`
data "test_data_source" "test" {
  count = 3
  input = "count-${count.index}"
}

resource "test_resource" "foo" {
  required     = "yep"
  required_map = {
    key = "value"
  }

  list = "${data.test_data_source.test.*.output}"
}
				`),
				Check: func(s *terraform.State) error {
					res, hasRes := s.RootModule().Resources["test_resource.foo"]
					if !hasRes {
						return errors.New("No test_resource.foo in state")
					}
					if res.Primary.Attributes["list.#"] != "3" {
						return errors.New("Wrong list.#, expected 3")
					}
					if res.Primary.Attributes["list.0"] != "count-0" {
						return errors.New("Wrong list.0, expected count-0")
					}
					if res.Primary.Attributes["list.1"] != "count-1" {
						return errors.New("Wrong list.0, expected count-1")
					}
					if res.Primary.Attributes["list.2"] != "count-2" {
						return errors.New("Wrong list.0, expected count-2")
					}
					return nil
				},
			},
		},
	})
}

// Test that the output of a data source can be used as the value for
// a "count" in a real resource. This would fail with "count cannot be computed"
// at some point.
func TestDataSource_valueAsResourceCount(t *testing.T) {
	resource.UnitTest(t, resource.TestCase{
		Providers: testAccProviders,
		CheckDestroy: func(s *terraform.State) error {
			return nil
		},
		Steps: []resource.TestStep{
			{
				Config: strings.TrimSpace(`
data "test_data_source" "test" {
  input = "4"
}

resource "test_resource" "foo" {
  count = "${data.test_data_source.test.output}"

  required     = "yep"
  required_map = {
    key = "value"
  }
}
				`),
				Check: func(s *terraform.State) error {
					count := 0
					for k, _ := range s.RootModule().Resources {
						if strings.HasPrefix(k, "test_resource.foo.") {
							count++
						}
					}

					if count != 4 {
						return fmt.Errorf("bad count: %d", count)
					}
					return nil
				},
			},
		},
	})
}

// TestDataSource_dataSourceCountGrandChild tests that a grandchild data source
// that is based off of count works, ie: dependency chain foo -> bar -> baz.
// This was failing because CountBoundaryTransformer is being run during apply
// instead of plan, which meant that it wasn't firing after data sources were
// potentially changing state and causing diff/interpolation issues.
//
// This happens after the initial apply, after state is saved.
func TestDataSource_dataSourceCountGrandChild(t *testing.T) {
	resource.UnitTest(t, resource.TestCase{
		Providers: testAccProviders,
		CheckDestroy: func(s *terraform.State) error {
			return nil
		},
		Steps: []resource.TestStep{
			{
				Config: dataSourceCountGrandChildConfig,
			},
			{
				Config: dataSourceCountGrandChildConfig,
				Check: func(s *terraform.State) error {
					for _, v := range []string{"foo", "bar", "baz"} {
						count := 0
						for k := range s.RootModule().Resources {
							if strings.HasPrefix(k, fmt.Sprintf("data.test_data_source.%s.", v)) {
								count++
							}
						}

						if count != 2 {
							return fmt.Errorf("bad count for data.test_data_source.%s: %d", v, count)
						}
					}
					return nil
				},
			},
		},
	})
}

const dataSourceCountGrandChildConfig = `
data "test_data_source" "foo" {
  count = 2
  input = "one"
}

data "test_data_source" "bar" {
  count = "${length(data.test_data_source.foo.*.id)}"
  input = "${data.test_data_source.foo.*.output[count.index]}"
}

data "test_data_source" "baz" {
  count = "${length(data.test_data_source.bar.*.id)}"
  input = "${data.test_data_source.bar.*.output[count.index]}"
}
`

func TestDataSource_nilComputedValues(t *testing.T) {
	check := func(s *terraform.State) error {
		return nil
	}

	resource.UnitTest(t, resource.TestCase{
		Providers: testAccProviders,
		Steps: []resource.TestStep{
			{
				Check: check,
				Config: `
variable "index" {
  default = "d"
}

locals {
  name = {
    a = "something"
    b = "else"
  }
}

data "test_data_source" "x" {
  input = "${lookup(local.name, var.index, local.name["a"])}"
}

data "test_data_source" "y" {
  input = data.test_data_source.x.nil == "something" ? "something" : "else"
}`,
			},
		},
	})
}

// referencing test_data_source.one.output_map["a"] should produce an error when
// there's a count.
func TestDataSource_indexedCountOfOne(t *testing.T) {
	resource.UnitTest(t, resource.TestCase{
		Providers: testAccProviders,
		Steps: []resource.TestStep{
			{
				Config: strings.TrimSpace(`
data "test_data_source" "one" {
	count = 1
  input_map = {
		"a" = "b"
	}
}

data "test_data_source" "two" {
	input_map = {
		"x" = data.test_data_source.one.output_map["a"]
	}
}
				`),
				ExpectError: regexp.MustCompile("Because data.test_data_source.one has \"count\" set, its attributes must be accessed on specific instances"),
			},
		},
	})
}

// Verify that we can destroy when a data source references something with a
// count of 1.
func TestDataSource_countRefDestroyError(t *testing.T) {
	resource.UnitTest(t, resource.TestCase{
		Providers: testAccProviders,
		Steps: []resource.TestStep{
			{
				Config: strings.TrimSpace(`
data "test_data_source" "one" {
  count = 1
  input = "a"
}

data "test_data_source" "two" {
  input = data.test_data_source.one[0].output
}
				`),
			},
		},
	})
}

func TestDataSource_planUpdate(t *testing.T) {
	resource.UnitTest(t, resource.TestCase{
		Providers: testAccProviders,
		Steps: []resource.TestStep{
			{
				Config: strings.TrimSpace(`
resource "test_resource" "a" {
	required = "first"
	required_map = {
	    key = "1"
	}
	optional_force_new = "first"
}

data "test_data_source" "a" {
  input = "${test_resource.a.computed_from_required}"
}

output "out" {
  value = "${data.test_data_source.a.output}"
}
				`),
			},
			{
				Config: strings.TrimSpace(`
resource "test_resource" "a" {
	required = "second"
	required_map = {
	    key = "1"
	}
	optional_force_new = "second"
}

data "test_data_source" "a" {
  input = "${test_resource.a.computed_from_required}"
}

output "out" {
  value = "${data.test_data_source.a.output}"
}
				`),
				Check: resource.ComposeAggregateTestCheckFunc(
					resource.TestCheckResourceAttr("data.test_data_source.a", "output", "second"),
					resource.TestCheckOutput("out", "second"),
				),
			},
		},
	})
}
