package api

import (
	"io/ioutil"
	"os"
	"strings"
	"testing"
)

var (
	// encrypted data generated by `oadm ca encrypt`
	// if the output of `oadm ca encrypt` changes, add additional tests,
	// but do not modify this, to ensure we continue to decrypt these values
	encryptedData = []byte(`-----BEGIN ENCRYPTED STRING-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-256-CBC,8db8d0db237a56c8ea8d3c5c21acc5b6

9JUj8ezx/3MDgiiWbnx9QA==
-----END ENCRYPTED STRING-----`)

	// encrypting key generated by `oadm ca encrypt`
	// if the output of `oadm ca encrypt` changes, add additional tests,
	// but do not modify this, to ensure we continue to decrypt these values
	encryptingKey = []byte(`-----BEGIN ENCRYPTING KEY-----
f3zQfReuhwI1BvBNglhZdjgSocKKqABwyGafHJcdORw=
-----END ENCRYPTING KEY-----`)
)

func TestStringSource(t *testing.T) {
	os.Setenv("TestStringSource_present_env", "envvalue")
	os.Setenv("TestStringSource_encrypted_env", string(encryptedData))

	emptyFile, err := ioutil.TempFile("", "empty_file")
	if err != nil {
		t.Fatal(err)
	}
	defer os.Remove(emptyFile.Name()) // clean up

	fooFile, err := ioutil.TempFile("", "foo_file")
	if err != nil {
		t.Fatal(err)
	}
	defer os.Remove(emptyFile.Name()) // clean up
	if err := ioutil.WriteFile(fooFile.Name(), []byte(`filevalue`), os.FileMode(0755)); err != nil {
		t.Fatal(err)
	}

	encryptedFile, err := ioutil.TempFile("", "encrypted_file")
	if err != nil {
		t.Fatal(err)
	}
	defer os.Remove(encryptedFile.Name()) // clean up
	if err := ioutil.WriteFile(encryptedFile.Name(), encryptedData, os.FileMode(0755)); err != nil {
		t.Fatal(err)
	}

	validKeyFile, err := ioutil.TempFile("", "valid_key_file")
	if err != nil {
		t.Fatal(err)
	}
	defer os.Remove(validKeyFile.Name()) // clean up
	if err := ioutil.WriteFile(validKeyFile.Name(), encryptingKey, os.FileMode(0755)); err != nil {
		t.Fatal(err)
	}

	invalidKeyFile, err := ioutil.TempFile("", "invalid_key_file")
	if err != nil {
		t.Fatal(err)
	}
	defer os.Remove(invalidKeyFile.Name()) // clean up
	if err := ioutil.WriteFile(invalidKeyFile.Name(), []byte(`invalid key value`), os.FileMode(0600)); err != nil {
		t.Fatal(err)
	}

	testcases := map[string]struct {
		StringSource  StringSource
		ExpectedValue string
		ExpectedError string
	}{
		"empty": {
			StringSource:  StringSource{},
			ExpectedValue: "",
			ExpectedError: "",
		},
		"value": {
			StringSource:  StringSource{StringSourceSpec: StringSourceSpec{Value: "foo"}},
			ExpectedValue: "foo",
			ExpectedError: "",
		},
		"env empty": {
			StringSource:  StringSource{StringSourceSpec: StringSourceSpec{Env: "empty_env"}},
			ExpectedValue: "",
			ExpectedError: "",
		},
		"env present": {
			StringSource:  StringSource{StringSourceSpec: StringSourceSpec{Env: "TestStringSource_present_env"}},
			ExpectedValue: "envvalue",
			ExpectedError: "",
		},
		"file missing": {
			StringSource:  StringSource{StringSourceSpec: StringSourceSpec{File: "missing_file"}},
			ExpectedValue: "",
			ExpectedError: "missing_file: no such file",
		},
		"file empty": {
			StringSource:  StringSource{StringSourceSpec: StringSourceSpec{File: emptyFile.Name()}},
			ExpectedValue: "",
			ExpectedError: "",
		},
		"file present": {
			StringSource:  StringSource{StringSourceSpec: StringSourceSpec{File: fooFile.Name()}},
			ExpectedValue: "filevalue",
			ExpectedError: "",
		},

		"encrypted env with missing key": {
			StringSource:  StringSource{StringSourceSpec: StringSourceSpec{Env: "TestStringSource_encrypted_env", KeyFile: "missing_key"}},
			ExpectedValue: "",
			ExpectedError: "missing_key: no such file",
		},
		"encrypted env with invalid key": {
			StringSource:  StringSource{StringSourceSpec: StringSourceSpec{Env: "TestStringSource_encrypted_env", KeyFile: invalidKeyFile.Name()}},
			ExpectedValue: "",
			ExpectedError: "no valid PEM block",
		},
		"encrypted env with valid key": {
			StringSource:  StringSource{StringSourceSpec: StringSourceSpec{Env: "TestStringSource_encrypted_env", KeyFile: validKeyFile.Name()}},
			ExpectedValue: "encryptedvalue",
			ExpectedError: "",
		},

		"encrypted value with missing key": {
			StringSource:  StringSource{StringSourceSpec: StringSourceSpec{Value: string(encryptedData), KeyFile: "missing_key"}},
			ExpectedValue: "",
			ExpectedError: "missing_key: no such file",
		},
		"encrypted value with invalid key": {
			StringSource:  StringSource{StringSourceSpec: StringSourceSpec{Value: string(encryptedData), KeyFile: invalidKeyFile.Name()}},
			ExpectedValue: "",
			ExpectedError: "no valid PEM block",
		},
		"encrypted value with valid key": {
			StringSource:  StringSource{StringSourceSpec: StringSourceSpec{Value: string(encryptedData), KeyFile: validKeyFile.Name()}},
			ExpectedValue: "encryptedvalue",
			ExpectedError: "",
		},

		"missing encrypted file with valid key": {
			StringSource:  StringSource{StringSourceSpec: StringSourceSpec{File: "missing_file", KeyFile: validKeyFile.Name()}},
			ExpectedValue: "",
			ExpectedError: "missing_file: no such file",
		},
		"encrypted file with missing key": {
			StringSource:  StringSource{StringSourceSpec: StringSourceSpec{File: encryptedFile.Name(), KeyFile: "missing_key"}},
			ExpectedValue: "",
			ExpectedError: "missing_key: no such file",
		},
		"encrypted file with invalid key": {
			StringSource:  StringSource{StringSourceSpec: StringSourceSpec{File: encryptedFile.Name(), KeyFile: invalidKeyFile.Name()}},
			ExpectedValue: "",
			ExpectedError: "no valid PEM block",
		},
		"encrypted file with valid key": {
			StringSource:  StringSource{StringSourceSpec: StringSourceSpec{File: encryptedFile.Name(), KeyFile: validKeyFile.Name()}},
			ExpectedValue: "encryptedvalue",
			ExpectedError: "",
		},
	}
	for k, tc := range testcases {
		value, err := ResolveStringValue(tc.StringSource)

		if len(tc.ExpectedError) > 0 && (err == nil || !strings.Contains(err.Error(), tc.ExpectedError)) {
			t.Errorf("%s: expected error containing %q, got %q", k, tc.ExpectedError, err.Error())
		}
		if len(tc.ExpectedError) == 0 && err != nil {
			t.Errorf("%s: got unexpected error: %v", k, err)
		}
		if tc.ExpectedValue != value {
			t.Errorf("%s: Expected value=%q, got %q", k, tc.ExpectedValue, value)
		}
	}
}