package chef

import (
	"fmt"
	"path"
	"testing"

	"github.com/hashicorp/terraform/communicator"
	"github.com/hashicorp/terraform/configs/hcl2shim"
	"github.com/hashicorp/terraform/helper/schema"
	"github.com/hashicorp/terraform/terraform"
)

func TestResourceProvisioner_impl(t *testing.T) {
	var _ terraform.ResourceProvisioner = Provisioner()
}

func TestProvisioner(t *testing.T) {
	if err := Provisioner().(*schema.Provisioner).InternalValidate(); err != nil {
		t.Fatalf("err: %s", err)
	}
}

func TestResourceProvider_Validate_good(t *testing.T) {
	c := testConfig(t, map[string]interface{}{
		"environment": "_default",
		"node_name":   "nodename1",
		"run_list":    []interface{}{"cookbook::recipe"},
		"server_url":  "https://chef.local",
		"user_name":   "bob",
		"user_key":    "USER-KEY",
	})

	warn, errs := Provisioner().Validate(c)
	if len(warn) > 0 {
		t.Fatalf("Warnings: %v", warn)
	}
	if len(errs) > 0 {
		t.Fatalf("Errors: %v", errs)
	}
}

func TestResourceProvider_Validate_bad(t *testing.T) {
	c := testConfig(t, map[string]interface{}{
		"invalid": "nope",
	})

	warn, errs := Provisioner().Validate(c)
	if len(warn) > 0 {
		t.Fatalf("Warnings: %v", warn)
	}
	if len(errs) == 0 {
		t.Fatalf("Should have errors")
	}
}

// Test that the JSON attributes with an unknown value don't
// validate.
func TestResourceProvider_Validate_computedValues(t *testing.T) {
	c := testConfig(t, map[string]interface{}{
		"environment":     "_default",
		"node_name":       "nodename1",
		"run_list":        []interface{}{"cookbook::recipe"},
		"server_url":      "https://chef.local",
		"user_name":       "bob",
		"user_key":        "USER-KEY",
		"attributes_json": hcl2shim.UnknownVariableValue,
	})

	warn, errs := Provisioner().Validate(c)
	if len(warn) > 0 {
		t.Fatalf("Warnings: %v", warn)
	}
	if len(errs) > 0 {
		t.Fatalf("Errors: %v", errs)
	}
}

func TestResourceProvider_runChefClient(t *testing.T) {
	cases := map[string]struct {
		Config   map[string]interface{}
		ChefCmd  string
		ConfDir  string
		Commands map[string]bool
	}{
		"Sudo": {
			Config: map[string]interface{}{
				"node_name":  "nodename1",
				"run_list":   []interface{}{"cookbook::recipe"},
				"server_url": "https://chef.local",
				"user_name":  "bob",
				"user_key":   "USER-KEY",
			},

			ChefCmd: linuxChefCmd,

			ConfDir: linuxConfDir,

			Commands: map[string]bool{
				fmt.Sprintf(`sudo %s -j %q -E "_default"`,
					linuxChefCmd,
					path.Join(linuxConfDir, "first-boot.json")): true,
			},
		},

		"NoSudo": {
			Config: map[string]interface{}{
				"node_name":    "nodename1",
				"prevent_sudo": true,
				"run_list":     []interface{}{"cookbook::recipe"},
				"server_url":   "https://chef.local",
				"user_name":    "bob",
				"user_key":     "USER-KEY",
			},

			ChefCmd: linuxChefCmd,

			ConfDir: linuxConfDir,

			Commands: map[string]bool{
				fmt.Sprintf(`%s -j %q -E "_default"`,
					linuxChefCmd,
					path.Join(linuxConfDir, "first-boot.json")): true,
			},
		},

		"Environment": {
			Config: map[string]interface{}{
				"environment":  "production",
				"node_name":    "nodename1",
				"prevent_sudo": true,
				"run_list":     []interface{}{"cookbook::recipe"},
				"server_url":   "https://chef.local",
				"user_name":    "bob",
				"user_key":     "USER-KEY",
			},

			ChefCmd: windowsChefCmd,

			ConfDir: windowsConfDir,

			Commands: map[string]bool{
				fmt.Sprintf(`%s -j %q -E "production"`,
					windowsChefCmd,
					path.Join(windowsConfDir, "first-boot.json")): true,
			},
		},
	}

	o := new(terraform.MockUIOutput)
	c := new(communicator.MockCommunicator)

	for k, tc := range cases {
		c.Commands = tc.Commands

		p, err := decodeConfig(
			schema.TestResourceDataRaw(t, Provisioner().(*schema.Provisioner).Schema, tc.Config),
		)
		if err != nil {
			t.Fatalf("Error: %v", err)
		}

		p.runChefClient = p.runChefClientFunc(tc.ChefCmd, tc.ConfDir)
		p.useSudo = !p.PreventSudo

		err = p.runChefClient(o, c)
		if err != nil {
			t.Fatalf("Test %q failed: %v", k, err)
		}
	}
}

func TestResourceProvider_fetchChefCertificates(t *testing.T) {
	cases := map[string]struct {
		Config   map[string]interface{}
		KnifeCmd string
		ConfDir  string
		Commands map[string]bool
	}{
		"Sudo": {
			Config: map[string]interface{}{
				"fetch_chef_certificates": true,
				"node_name":               "nodename1",
				"run_list":                []interface{}{"cookbook::recipe"},
				"server_url":              "https://chef.local",
				"user_name":               "bob",
				"user_key":                "USER-KEY",
			},

			KnifeCmd: linuxKnifeCmd,

			ConfDir: linuxConfDir,

			Commands: map[string]bool{
				fmt.Sprintf(`sudo %s ssl fetch -c %s`,
					linuxKnifeCmd,
					path.Join(linuxConfDir, "client.rb")): true,
			},
		},

		"NoSudo": {
			Config: map[string]interface{}{
				"fetch_chef_certificates": true,
				"node_name":               "nodename1",
				"prevent_sudo":            true,
				"run_list":                []interface{}{"cookbook::recipe"},
				"server_url":              "https://chef.local",
				"user_name":               "bob",
				"user_key":                "USER-KEY",
			},

			KnifeCmd: windowsKnifeCmd,

			ConfDir: windowsConfDir,

			Commands: map[string]bool{
				fmt.Sprintf(`%s ssl fetch -c %s`,
					windowsKnifeCmd,
					path.Join(windowsConfDir, "client.rb")): true,
			},
		},
	}

	o := new(terraform.MockUIOutput)
	c := new(communicator.MockCommunicator)

	for k, tc := range cases {
		c.Commands = tc.Commands

		p, err := decodeConfig(
			schema.TestResourceDataRaw(t, Provisioner().(*schema.Provisioner).Schema, tc.Config),
		)
		if err != nil {
			t.Fatalf("Error: %v", err)
		}

		p.fetchChefCertificates = p.fetchChefCertificatesFunc(tc.KnifeCmd, tc.ConfDir)
		p.useSudo = !p.PreventSudo

		err = p.fetchChefCertificates(o, c)
		if err != nil {
			t.Fatalf("Test %q failed: %v", k, err)
		}
	}
}

func TestResourceProvider_configureVaults(t *testing.T) {
	cases := map[string]struct {
		Config   map[string]interface{}
		GemCmd   string
		KnifeCmd string
		ConfDir  string
		Commands map[string]bool
	}{
		"Linux Vault string": {
			Config: map[string]interface{}{
				"node_name":    "nodename1",
				"prevent_sudo": true,
				"run_list":     []interface{}{"cookbook::recipe"},
				"server_url":   "https://chef.local",
				"user_name":    "bob",
				"user_key":     "USER-KEY",
				"vault_json":   `{"vault1": "item1"}`,
			},

			GemCmd:   linuxGemCmd,
			KnifeCmd: linuxKnifeCmd,
			ConfDir:  linuxConfDir,

			Commands: map[string]bool{
				fmt.Sprintf("%s install chef-vault", linuxGemCmd): true,
				fmt.Sprintf("%s vault update vault1 item1 -C nodename1 -M client -c %s/client.rb "+
					"-u bob --key %s/bob.pem", linuxKnifeCmd, linuxConfDir, linuxConfDir): true,
			},
		},

		"Linux Vault []string": {
			Config: map[string]interface{}{
				"fetch_chef_certificates": true,
				"node_name":               "nodename1",
				"prevent_sudo":            true,
				"run_list":                []interface{}{"cookbook::recipe"},
				"server_url":              "https://chef.local",
				"user_name":               "bob",
				"user_key":                "USER-KEY",
				"vault_json":              `{"vault1": ["item1", "item2"]}`,
			},

			GemCmd:   linuxGemCmd,
			KnifeCmd: linuxKnifeCmd,
			ConfDir:  linuxConfDir,

			Commands: map[string]bool{
				fmt.Sprintf("%s install chef-vault", linuxGemCmd): true,
				fmt.Sprintf("%s vault update vault1 item1 -C nodename1 -M client -c %s/client.rb "+
					"-u bob --key %s/bob.pem", linuxKnifeCmd, linuxConfDir, linuxConfDir): true,
				fmt.Sprintf("%s vault update vault1 item2 -C nodename1 -M client -c %s/client.rb "+
					"-u bob --key %s/bob.pem", linuxKnifeCmd, linuxConfDir, linuxConfDir): true,
			},
		},

		"Linux Vault []string (recreate-client for vault)": {
			Config: map[string]interface{}{
				"fetch_chef_certificates": true,
				"node_name":               "nodename1",
				"prevent_sudo":            true,
				"run_list":                []interface{}{"cookbook::recipe"},
				"server_url":              "https://chef.local",
				"user_name":               "bob",
				"user_key":                "USER-KEY",
				"vault_json":              `{"vault1": ["item1", "item2"]}`,
				"recreate_client":         true,
			},

			GemCmd:   linuxGemCmd,
			KnifeCmd: linuxKnifeCmd,
			ConfDir:  linuxConfDir,

			Commands: map[string]bool{
				fmt.Sprintf("%s install chef-vault", linuxGemCmd): true,
				fmt.Sprintf("%s vault remove vault1 item1 -C \"nodename1\" -M client -c %s/client.rb "+
					"-u bob --key %s/bob.pem", linuxKnifeCmd, linuxConfDir, linuxConfDir): true,
				fmt.Sprintf("%s vault remove vault1 item2 -C \"nodename1\" -M client -c %s/client.rb "+
					"-u bob --key %s/bob.pem", linuxKnifeCmd, linuxConfDir, linuxConfDir): true,
				fmt.Sprintf("%s vault update vault1 item1 -C nodename1 -M client -c %s/client.rb "+
					"-u bob --key %s/bob.pem", linuxKnifeCmd, linuxConfDir, linuxConfDir): true,
				fmt.Sprintf("%s vault update vault1 item2 -C nodename1 -M client -c %s/client.rb "+
					"-u bob --key %s/bob.pem", linuxKnifeCmd, linuxConfDir, linuxConfDir): true,
			},
		},

		"Windows Vault string": {
			Config: map[string]interface{}{
				"node_name":    "nodename1",
				"prevent_sudo": true,
				"run_list":     []interface{}{"cookbook::recipe"},
				"server_url":   "https://chef.local",
				"user_name":    "bob",
				"user_key":     "USER-KEY",
				"vault_json":   `{"vault1": "item1"}`,
			},

			GemCmd:   windowsGemCmd,
			KnifeCmd: windowsKnifeCmd,
			ConfDir:  windowsConfDir,

			Commands: map[string]bool{
				fmt.Sprintf("%s install chef-vault", windowsGemCmd): true,
				fmt.Sprintf("%s vault update vault1 item1 -C nodename1 -M client -c %s/client.rb "+
					"-u bob --key %s/bob.pem", windowsKnifeCmd, windowsConfDir, windowsConfDir): true,
			},
		},

		"Windows Vault []string": {
			Config: map[string]interface{}{
				"fetch_chef_certificates": true,
				"node_name":               "nodename1",
				"prevent_sudo":            true,
				"run_list":                []interface{}{"cookbook::recipe"},
				"server_url":              "https://chef.local",
				"user_name":               "bob",
				"user_key":                "USER-KEY",
				"vault_json":              `{"vault1": ["item1", "item2"]}`,
			},

			GemCmd:   windowsGemCmd,
			KnifeCmd: windowsKnifeCmd,
			ConfDir:  windowsConfDir,

			Commands: map[string]bool{
				fmt.Sprintf("%s install chef-vault", windowsGemCmd): true,
				fmt.Sprintf("%s vault update vault1 item1 -C nodename1 -M client -c %s/client.rb "+
					"-u bob --key %s/bob.pem", windowsKnifeCmd, windowsConfDir, windowsConfDir): true,
				fmt.Sprintf("%s vault update vault1 item2 -C nodename1 -M client -c %s/client.rb "+
					"-u bob --key %s/bob.pem", windowsKnifeCmd, windowsConfDir, windowsConfDir): true,
			},
		},

		"Windows Vault [] string (recreate-client for vault)": {
			Config: map[string]interface{}{
				"fetch_chef_certificates": true,
				"node_name":               "nodename1",
				"prevent_sudo":            true,
				"run_list":                []interface{}{"cookbook::recipe"},
				"server_url":              "https://chef.local",
				"user_name":               "bob",
				"user_key":                "USER-KEY",
				"vault_json":              `{"vault1": ["item1", "item2"]}`,
				"recreate_client":         true,
			},

			GemCmd:   windowsGemCmd,
			KnifeCmd: windowsKnifeCmd,
			ConfDir:  windowsConfDir,

			Commands: map[string]bool{
				fmt.Sprintf("%s install chef-vault", windowsGemCmd): true,
				fmt.Sprintf("%s vault remove vault1 item1 -C \"nodename1\" -M client -c %s/client.rb "+
					"-u bob --key %s/bob.pem", windowsKnifeCmd, windowsConfDir, windowsConfDir): true,
				fmt.Sprintf("%s vault remove vault1 item2 -C \"nodename1\" -M client -c %s/client.rb "+
					"-u bob --key %s/bob.pem", windowsKnifeCmd, windowsConfDir, windowsConfDir): true,
				fmt.Sprintf("%s vault update vault1 item1 -C nodename1 -M client -c %s/client.rb "+
					"-u bob --key %s/bob.pem", windowsKnifeCmd, windowsConfDir, windowsConfDir): true,
				fmt.Sprintf("%s vault update vault1 item2 -C nodename1 -M client -c %s/client.rb "+
					"-u bob --key %s/bob.pem", windowsKnifeCmd, windowsConfDir, windowsConfDir): true,
			},
		},
	}

	o := new(terraform.MockUIOutput)
	c := new(communicator.MockCommunicator)

	for k, tc := range cases {
		c.Commands = tc.Commands

		p, err := decodeConfig(
			schema.TestResourceDataRaw(t, Provisioner().(*schema.Provisioner).Schema, tc.Config),
		)
		if err != nil {
			t.Fatalf("Error: %v", err)
		}

		p.configureVaults = p.configureVaultsFunc(tc.GemCmd, tc.KnifeCmd, tc.ConfDir)
		p.useSudo = !p.PreventSudo

		err = p.configureVaults(o, c)
		if err != nil {
			t.Fatalf("Test %q failed: %v", k, err)
		}
	}
}

func testConfig(t *testing.T, c map[string]interface{}) *terraform.ResourceConfig {
	return terraform.NewResourceConfigRaw(c)
}
