package provider

import (
	"testing"

	eboptions "github.com/coder/envbuilder/options"
	"github.com/hashicorp/terraform-plugin-framework/attr"
	"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
	"github.com/stretchr/testify/assert"
)

func Test_optionsFromDataModel(t *testing.T) {
	t.Parallel()

	for _, tc := range []struct {
		name                  string
		data                  CachedImageResourceModel
		expectOpts            eboptions.Options
		expectNumErrorDiags   int
		expectNumWarningDiags int
	}{
		{
			name: "required only",
			data: CachedImageResourceModel{
				BuilderImage: basetypes.NewStringValue("envbuilder:latest"),
				CacheRepo:    basetypes.NewStringValue("localhost:5000/cache"),
				GitURL:       basetypes.NewStringValue("git@git.local/devcontainer.git"),
			},
			expectOpts: eboptions.Options{
				CacheRepo:           "localhost:5000/cache",
				GitURL:              "git@git.local/devcontainer.git",
				RemoteRepoBuildMode: true,
			},
		},
		{
			name: "all options without extra_env",
			data: CachedImageResourceModel{
				BuilderImage:      basetypes.NewStringValue("envbuilder:latest"),
				CacheRepo:         basetypes.NewStringValue("localhost:5000/cache"),
				GitURL:            basetypes.NewStringValue("git@git.local/devcontainer.git"),
				BaseImageCacheDir: basetypes.NewStringValue("/tmp/cache"),
				BuildContextPath:  basetypes.NewStringValue("."),
				BuildSecrets: basetypes.NewMapValueMust(basetypes.StringType{}, map[string]attr.Value{
					"FOO": basetypes.NewStringValue("bar"),
					"BAZ": basetypes.NewStringValue("qux"),
				}),
				CacheTTLDays:         basetypes.NewInt64Value(7),
				DevcontainerDir:      basetypes.NewStringValue(".devcontainer"),
				DevcontainerJSONPath: basetypes.NewStringValue(".devcontainer/devcontainer.json"),
				DockerfilePath:       basetypes.NewStringValue("Dockerfile"),
				DockerConfigBase64:   basetypes.NewStringValue("some base64"),
				ExitOnBuildFailure:   basetypes.NewBoolValue(true),
				// ExtraEnv: map[string]basetypes.Value{},
				FallbackImage:        basetypes.NewStringValue("fallback"),
				GitCloneDepth:        basetypes.NewInt64Value(1),
				GitCloneSingleBranch: basetypes.NewBoolValue(true),
				GitHTTPProxyURL:      basetypes.NewStringValue("http://proxy"),
				GitPassword:          basetypes.NewStringValue("password"),
				GitSSHPrivateKeyPath: basetypes.NewStringValue("/tmp/id_rsa"),
				GitUsername:          basetypes.NewStringValue("user"),
				IgnorePaths:          listValue("ignore", "paths"),
				Insecure:             basetypes.NewBoolValue(true),
				RemoteRepoBuildMode:  basetypes.NewBoolValue(false),
				SSLCertBase64:        basetypes.NewStringValue("cert"),
				Verbose:              basetypes.NewBoolValue(true),
				WorkspaceFolder:      basetypes.NewStringValue("workspace"),
			},
			expectOpts: eboptions.Options{
				CacheRepo:            "localhost:5000/cache",
				GitURL:               "git@git.local/devcontainer.git",
				BaseImageCacheDir:    "/tmp/cache",
				BuildContextPath:     ".",
				BuildSecrets:         []string{"BAZ=qux", "FOO=bar"}, // Sorted
				CacheTTLDays:         7,
				DevcontainerDir:      ".devcontainer",
				DevcontainerJSONPath: ".devcontainer/devcontainer.json",
				DockerfilePath:       "Dockerfile",
				DockerConfigBase64:   "some base64",
				ExitOnBuildFailure:   true,
				FallbackImage:        "fallback",
				GitCloneDepth:        1,
				GitCloneSingleBranch: true,
				GitHTTPProxyURL:      "http://proxy",
				GitPassword:          "password",
				GitSSHPrivateKeyPath: "/tmp/id_rsa",
				GitUsername:          "user",
				IgnorePaths:          []string{"ignore", "paths"},
				Insecure:             true,
				RemoteRepoBuildMode:  false,
				SSLCertBase64:        "cert",
				Verbose:              true,
				WorkspaceFolder:      "workspace",
			},
		},
		{
			name: "extra env override",
			data: CachedImageResourceModel{
				BuilderImage: basetypes.NewStringValue("envbuilder:latest"),
				CacheRepo:    basetypes.NewStringValue("localhost:5000/cache"),
				GitURL:       basetypes.NewStringValue("git@git.local/devcontainer.git"),
				ExtraEnv: extraEnvMap(t,
					"CODER_AGENT_TOKEN", "token",
					"CODER_AGENT_URL", "http://coder",
					"FOO", "bar",
					"ENVBUILDER_BUILD_SECRETS", "FOO=bar,BAZ=qux",
				),
			},
			expectOpts: eboptions.Options{
				BuildSecrets:        []string{"FOO=bar", "BAZ=qux"},
				CacheRepo:           "localhost:5000/cache",
				GitURL:              "git@git.local/devcontainer.git",
				RemoteRepoBuildMode: true,
				CoderAgentToken:     "token",
				CoderAgentURL:       "http://coder",
			},
		},
		{
			name: "extra_env override warnings",
			data: CachedImageResourceModel{
				BuilderImage:      basetypes.NewStringValue("envbuilder:latest"),
				CacheRepo:         basetypes.NewStringValue("localhost:5000/cache"),
				GitURL:            basetypes.NewStringValue("git@git.local/devcontainer.git"),
				BaseImageCacheDir: basetypes.NewStringValue("/tmp/cache"),
				BuildContextPath:  basetypes.NewStringValue("."),
				BuildSecrets: basetypes.NewMapValueMust(basetypes.StringType{}, map[string]attr.Value{
					"FOO": basetypes.NewStringValue("bar"),
				}),
				CacheTTLDays:         basetypes.NewInt64Value(7),
				DevcontainerDir:      basetypes.NewStringValue(".devcontainer"),
				DevcontainerJSONPath: basetypes.NewStringValue(".devcontainer/devcontainer.json"),
				DockerfilePath:       basetypes.NewStringValue("Dockerfile"),
				DockerConfigBase64:   basetypes.NewStringValue("some base64"),
				ExitOnBuildFailure:   basetypes.NewBoolValue(true),
				// ExtraEnv: map[string]basetypes.Value{},
				FallbackImage:        basetypes.NewStringValue("fallback"),
				GitCloneDepth:        basetypes.NewInt64Value(1),
				GitCloneSingleBranch: basetypes.NewBoolValue(true),
				GitHTTPProxyURL:      basetypes.NewStringValue("http://proxy"),
				GitPassword:          basetypes.NewStringValue("password"),
				GitSSHPrivateKeyPath: basetypes.NewStringValue("/tmp/id_rsa"),
				GitUsername:          basetypes.NewStringValue("user"),
				IgnorePaths:          listValue("ignore", "paths"),
				Insecure:             basetypes.NewBoolValue(true),
				RemoteRepoBuildMode:  basetypes.NewBoolValue(false),
				SSLCertBase64:        basetypes.NewStringValue("cert"),
				Verbose:              basetypes.NewBoolValue(true),
				WorkspaceFolder:      basetypes.NewStringValue("workspace"),
				ExtraEnv: extraEnvMap(t,
					"ENVBUILDER_BUILD_SECRETS", "FOO=bar,BAZ=qux",
					"ENVBUILDER_CACHE_REPO", "override",
					"ENVBUILDER_GIT_URL", "override",
					"ENVBUILDER_BASE_IMAGE_CACHE_DIR", "override",
					"ENVBUILDER_BUILD_CONTEXT_PATH", "override",
					"ENVBUILDER_CACHE_TTL_DAYS", "8",
					"ENVBUILDER_DEVCONTAINER_DIR", "override",
					"ENVBUILDER_DEVCONTAINER_JSON_PATH", "override",
					"ENVBUILDER_DOCKERFILE_PATH", "override",
					"ENVBUILDER_DOCKER_CONFIG_BASE64", "override",
					"ENVBUILDER_EXIT_ON_BUILD_FAILURE", "false",
					"ENVBUILDER_FALLBACK_IMAGE", "override",
					"ENVBUILDER_GIT_CLONE_DEPTH", "2",
					"ENVBUILDER_GIT_CLONE_SINGLE_BRANCH", "false",
					"ENVBUILDER_GIT_HTTP_PROXY_URL", "override",
					"ENVBUILDER_GIT_PASSWORD", "override",
					"ENVBUILDER_GIT_SSH_PRIVATE_KEY_PATH", "override",
					"ENVBUILDER_GIT_USERNAME", "override",
					"ENVBUILDER_IGNORE_PATHS", "override",
					"ENVBUILDER_INSECURE", "false",
					"ENVBUILDER_REMOTE_REPO_BUILD_MODE", "true",
					"ENVBUILDER_SSL_CERT_BASE64", "override",
					"ENVBUILDER_VERBOSE", "false",
					"ENVBUILDER_WORKSPACE_FOLDER", "override",
					"FOO", "bar",
				),
			},
			expectOpts: eboptions.Options{
				// not overridden
				CacheRepo: "localhost:5000/cache",
				GitURL:    "git@git.local/devcontainer.git",
				// overridden
				BaseImageCacheDir:    "override",
				BuildContextPath:     "override",
				BuildSecrets:         []string{"FOO=bar", "BAZ=qux"},
				CacheTTLDays:         8,
				DevcontainerDir:      "override",
				DevcontainerJSONPath: "override",
				DockerfilePath:       "override",
				DockerConfigBase64:   "override",
				ExitOnBuildFailure:   false,
				FallbackImage:        "override",
				GitCloneDepth:        2,
				GitCloneSingleBranch: false,
				GitHTTPProxyURL:      "override",
				GitPassword:          "override",
				GitSSHPrivateKeyPath: "override",
				GitUsername:          "override",
				IgnorePaths:          []string{"override"},
				Insecure:             false,
				RemoteRepoBuildMode:  true,
				SSLCertBase64:        "override",
				Verbose:              false,
				WorkspaceFolder:      "override",
			},
			expectNumWarningDiags: 24,
		},
		{
			name: "extra_env override errors",
			data: CachedImageResourceModel{
				BuilderImage: basetypes.NewStringValue("envbuilder:latest"),
				CacheRepo:    basetypes.NewStringValue("localhost:5000/cache"),
				GitURL:       basetypes.NewStringValue("git@git.local/devcontainer.git"),
				ExtraEnv: extraEnvMap(t,
					"ENVBUILDER_CACHE_TTL_DAYS", "not a number",
					"ENVBUILDER_VERBOSE", "not a bool",
					"FOO", "bar",
				),
			},
			expectOpts: eboptions.Options{
				// not overridden
				CacheRepo:           "localhost:5000/cache",
				GitURL:              "git@git.local/devcontainer.git",
				RemoteRepoBuildMode: true,
			},
			expectNumErrorDiags: 2,
		},
		{
			name: "errors when git ssh private key path and base64 are set",
			data: CachedImageResourceModel{
				BuilderImage:           basetypes.NewStringValue("envbuilder:latest"),
				CacheRepo:              basetypes.NewStringValue("localhost:5000/cache"),
				GitURL:                 basetypes.NewStringValue("git@git.local/devcontainer.git"),
				GitSSHPrivateKeyPath:   basetypes.NewStringValue("/tmp/id_rsa"),
				GitSSHPrivateKeyBase64: basetypes.NewStringValue("cHJpdmF0ZUtleQo="),
			},
			expectOpts: eboptions.Options{
				CacheRepo:              "localhost:5000/cache",
				GitURL:                 "git@git.local/devcontainer.git",
				RemoteRepoBuildMode:    true,
				GitSSHPrivateKeyPath:   "/tmp/id_rsa",
				GitSSHPrivateKeyBase64: "cHJpdmF0ZUtleQo=",
			},
			expectNumErrorDiags: 1,
		},
		{
			name: "extra_env override errors when git ssh private key path and base64 are set",
			data: CachedImageResourceModel{
				BuilderImage:           basetypes.NewStringValue("envbuilder:latest"),
				CacheRepo:              basetypes.NewStringValue("localhost:5000/cache"),
				GitURL:                 basetypes.NewStringValue("git@git.local/devcontainer.git"),
				GitSSHPrivateKeyBase64: basetypes.NewStringValue("cHJpdmF0ZUtleQo="),
				ExtraEnv: extraEnvMap(t,
					"ENVBUILDER_GIT_SSH_PRIVATE_KEY_PATH", "/tmp/id_rsa",
				),
			},
			expectOpts: eboptions.Options{
				CacheRepo:              "localhost:5000/cache",
				GitURL:                 "git@git.local/devcontainer.git",
				RemoteRepoBuildMode:    true,
				GitSSHPrivateKeyPath:   "/tmp/id_rsa",
				GitSSHPrivateKeyBase64: "cHJpdmF0ZUtleQo=",
			},
			expectNumErrorDiags: 1,
		},
		{
			name: "required only with base64 ssh key",
			data: CachedImageResourceModel{
				BuilderImage:           basetypes.NewStringValue("envbuilder:latest"),
				CacheRepo:              basetypes.NewStringValue("localhost:5000/cache"),
				GitURL:                 basetypes.NewStringValue("git@git.local/devcontainer.git"),
				GitSSHPrivateKeyBase64: basetypes.NewStringValue("cHJpdmF0ZUtleQo="),
			},
			expectOpts: eboptions.Options{
				CacheRepo:              "localhost:5000/cache",
				GitURL:                 "git@git.local/devcontainer.git",
				RemoteRepoBuildMode:    true,
				GitSSHPrivateKeyBase64: "cHJpdmF0ZUtleQo=",
			},
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			actual, diags := optionsFromDataModel(tc.data)
			assert.Equal(t, tc.expectNumErrorDiags, diags.ErrorsCount())
			assert.Equal(t, tc.expectNumWarningDiags, diags.WarningsCount())
			assert.EqualValues(t, tc.expectOpts, actual)
		})
	}
}

func Test_computeEnvFromOptions(t *testing.T) {
	t.Parallel()

	for _, tc := range []struct {
		name      string
		opts      eboptions.Options
		extraEnv  map[string]string
		expectEnv map[string]string
	}{
		{
			name:      "empty",
			opts:      eboptions.Options{},
			expectEnv: map[string]string{},
		},
		{
			name: "all options",
			opts: eboptions.Options{
				BaseImageCacheDir:    "string",
				BinaryPath:           "string",
				BuildContextPath:     "string",
				BuildSecrets:         []string{"FOO=bar", "BAZ=qux"},
				CacheRepo:            "string",
				CacheTTLDays:         1,
				CoderAgentSubsystem:  []string{"one", "two"},
				CoderAgentToken:      "string",
				CoderAgentURL:        "string",
				DevcontainerDir:      "string",
				DevcontainerJSONPath: "string",
				DockerConfigBase64:   "string",
				DockerfilePath:       "string",
				ExitOnBuildFailure:   true,
				ExportEnvFile:        "string",
				FallbackImage:        "string",
				ForceSafe:            true,
				GetCachedImage:       true,
				GitCloneDepth:        1,
				GitCloneSingleBranch: true,
				GitHTTPProxyURL:      "string",
				GitPassword:          "string",
				GitSSHPrivateKeyPath: "string",
				GitURL:               "string",
				GitUsername:          "string",
				IgnorePaths:          []string{"one", "two"},
				InitArgs:             "string",
				InitCommand:          "string",
				InitScript:           "string",
				Insecure:             true,
				LayerCacheDir:        "string",
				PostStartScriptPath:  "string",
				PushImage:            true,
				RemoteRepoBuildMode:  true,
				SetupScript:          "string",
				SkipRebuild:          true,
				SSLCertBase64:        "string",
				Verbose:              true,
				WorkspaceFolder:      "string",
			},
			extraEnv: map[string]string{
				"ENVBUILDER_SOMETHING": "string", // should be ignored
				"FOO":                  "bar",    // should be included
			},
			expectEnv: map[string]string{
				"ENVBUILDER_BASE_IMAGE_CACHE_DIR":     "string",
				"ENVBUILDER_BINARY_PATH":              "string",
				"ENVBUILDER_BUILD_CONTEXT_PATH":       "string",
				"ENVBUILDER_BUILD_SECRETS":            "FOO=bar,BAZ=qux",
				"ENVBUILDER_CACHE_REPO":               "string",
				"ENVBUILDER_CACHE_TTL_DAYS":           "1",
				"ENVBUILDER_DEVCONTAINER_DIR":         "string",
				"ENVBUILDER_DEVCONTAINER_JSON_PATH":   "string",
				"ENVBUILDER_DOCKER_CONFIG_BASE64":     "string",
				"ENVBUILDER_DOCKERFILE_PATH":          "string",
				"ENVBUILDER_EXIT_ON_BUILD_FAILURE":    "true",
				"ENVBUILDER_EXPORT_ENV_FILE":          "string",
				"ENVBUILDER_FALLBACK_IMAGE":           "string",
				"ENVBUILDER_FORCE_SAFE":               "true",
				"ENVBUILDER_GET_CACHED_IMAGE":         "true",
				"ENVBUILDER_GIT_CLONE_DEPTH":          "1",
				"ENVBUILDER_GIT_CLONE_SINGLE_BRANCH":  "true",
				"ENVBUILDER_GIT_HTTP_PROXY_URL":       "string",
				"ENVBUILDER_GIT_PASSWORD":             "string",
				"ENVBUILDER_GIT_SSH_PRIVATE_KEY_PATH": "string",
				"ENVBUILDER_GIT_URL":                  "string",
				"ENVBUILDER_GIT_USERNAME":             "string",
				"ENVBUILDER_IGNORE_PATHS":             "one,two",
				"ENVBUILDER_INIT_ARGS":                "string",
				"ENVBUILDER_INIT_COMMAND":             "string",
				"ENVBUILDER_INIT_SCRIPT":              "string",
				"ENVBUILDER_INSECURE":                 "true",
				"ENVBUILDER_LAYER_CACHE_DIR":          "string",
				"ENVBUILDER_POST_START_SCRIPT_PATH":   "string",
				"ENVBUILDER_PUSH_IMAGE":               "true",
				"ENVBUILDER_REMOTE_REPO_BUILD_MODE":   "true",
				"ENVBUILDER_SETUP_SCRIPT":             "string",
				"ENVBUILDER_SKIP_REBUILD":             "true",
				"ENVBUILDER_SSL_CERT_BASE64":          "string",
				"ENVBUILDER_VERBOSE":                  "true",
				"ENVBUILDER_WORKSPACE_FOLDER":         "string",
				"FOO":                                 "bar",
			},
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			if tc.extraEnv == nil {
				tc.extraEnv = map[string]string{}
			}
			actual := computeEnvFromOptions(tc.opts, tc.extraEnv)
			assert.EqualValues(t, tc.expectEnv, actual)
		})
	}
}

func listValue(vs ...string) basetypes.ListValue {
	vals := make([]attr.Value, len(vs))
	for i, s := range vs {
		vals[i] = basetypes.NewStringValue(s)
	}
	return basetypes.NewListValueMust(basetypes.StringType{}, vals)
}

func extraEnvMap(t *testing.T, kvs ...string) basetypes.MapValue {
	t.Helper()
	if len(kvs)%2 != 0 {
		t.Fatalf("extraEnvMap: expected even number of key-value pairs, got %d", len(kvs))
	}
	vals := make(map[string]attr.Value)
	for i := 0; i < len(kvs); i += 2 {
		vals[kvs[i]] = basetypes.NewStringValue(kvs[i+1])
	}
	return basetypes.NewMapValueMust(basetypes.StringType{}, vals)
}