package config_tests

import (
	"context"
	"testing"

	"github.com/databricks/cli/bundle"
	"github.com/databricks/cli/bundle/config/mutator"
	"github.com/databricks/databricks-sdk-go/experimental/mocks"
	"github.com/databricks/databricks-sdk-go/service/compute"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/require"
)

func TestVariables(t *testing.T) {
	t.Setenv("BUNDLE_VAR_b", "def")
	b := load(t, "./variables/vanilla")
	diags := bundle.Apply(context.Background(), b, bundle.Seq(
		mutator.SetVariables(),
		mutator.ResolveVariableReferences(
			"variables",
		),
	))
	require.NoError(t, diags.Error())
	assert.Equal(t, "abc def", b.Config.Bundle.Name)
}

func TestVariablesLoadingFailsWhenRequiredVariableIsNotSpecified(t *testing.T) {
	b := load(t, "./variables/vanilla")
	diags := bundle.Apply(context.Background(), b, bundle.Seq(
		mutator.SetVariables(),
		mutator.ResolveVariableReferences(
			"variables",
		),
	))
	assert.ErrorContains(t, diags.Error(), "no value assigned to required variable b. Assignment can be done through the \"--var\" flag or by setting the BUNDLE_VAR_b environment variable")
}

func TestVariablesTargetsBlockOverride(t *testing.T) {
	b := load(t, "./variables/env_overrides")
	diags := bundle.Apply(context.Background(), b, bundle.Seq(
		mutator.SelectTarget("env-with-single-variable-override"),
		mutator.SetVariables(),
		mutator.ResolveVariableReferences(
			"variables",
		),
	))
	require.NoError(t, diags.Error())
	assert.Equal(t, "default-a dev-b", b.Config.Workspace.Profile)
}

func TestVariablesTargetsBlockOverrideForMultipleVariables(t *testing.T) {
	b := load(t, "./variables/env_overrides")
	diags := bundle.Apply(context.Background(), b, bundle.Seq(
		mutator.SelectTarget("env-with-two-variable-overrides"),
		mutator.SetVariables(),
		mutator.ResolveVariableReferences(
			"variables",
		),
	))
	require.NoError(t, diags.Error())
	assert.Equal(t, "prod-a prod-b", b.Config.Workspace.Profile)
}

func TestVariablesTargetsBlockOverrideWithProcessEnvVars(t *testing.T) {
	t.Setenv("BUNDLE_VAR_b", "env-var-b")
	b := load(t, "./variables/env_overrides")
	diags := bundle.Apply(context.Background(), b, bundle.Seq(
		mutator.SelectTarget("env-with-two-variable-overrides"),
		mutator.SetVariables(),
		mutator.ResolveVariableReferences(
			"variables",
		),
	))
	require.NoError(t, diags.Error())
	assert.Equal(t, "prod-a env-var-b", b.Config.Workspace.Profile)
}

func TestVariablesTargetsBlockOverrideWithMissingVariables(t *testing.T) {
	b := load(t, "./variables/env_overrides")
	diags := bundle.Apply(context.Background(), b, bundle.Seq(
		mutator.SelectTarget("env-missing-a-required-variable-assignment"),
		mutator.SetVariables(),
		mutator.ResolveVariableReferences(
			"variables",
		),
	))
	assert.ErrorContains(t, diags.Error(), "no value assigned to required variable b. Assignment can be done through the \"--var\" flag or by setting the BUNDLE_VAR_b environment variable")
}

func TestVariablesTargetsBlockOverrideWithUndefinedVariables(t *testing.T) {
	b := load(t, "./variables/env_overrides")
	diags := bundle.Apply(context.Background(), b, bundle.Seq(
		mutator.SelectTarget("env-using-an-undefined-variable"),
		mutator.SetVariables(),
		mutator.ResolveVariableReferences(
			"variables",
		),
	))
	assert.ErrorContains(t, diags.Error(), "variable c is not defined but is assigned a value")
}

func TestVariablesWithoutDefinition(t *testing.T) {
	t.Setenv("BUNDLE_VAR_a", "foo")
	t.Setenv("BUNDLE_VAR_b", "bar")
	b := load(t, "./variables/without_definition")
	diags := bundle.Apply(context.Background(), b, mutator.SetVariables())
	require.NoError(t, diags.Error())
	require.True(t, b.Config.Variables["a"].HasValue())
	require.True(t, b.Config.Variables["b"].HasValue())
	assert.Equal(t, "foo", b.Config.Variables["a"].Value)
	assert.Equal(t, "bar", b.Config.Variables["b"].Value)
}

func TestVariablesWithTargetLookupOverrides(t *testing.T) {
	b := load(t, "./variables/env_overrides")

	mockWorkspaceClient := mocks.NewMockWorkspaceClient(t)
	b.SetWorkpaceClient(mockWorkspaceClient.WorkspaceClient)
	instancePoolApi := mockWorkspaceClient.GetMockInstancePoolsAPI()
	instancePoolApi.EXPECT().GetByInstancePoolName(mock.Anything, "some-test-instance-pool").Return(&compute.InstancePoolAndStats{
		InstancePoolId: "1234",
	}, nil)

	clustersApi := mockWorkspaceClient.GetMockClustersAPI()
	clustersApi.EXPECT().ListAll(mock.Anything, compute.ListClustersRequest{
		FilterBy: &compute.ListClustersFilterBy{
			ClusterSources: []compute.ClusterSource{compute.ClusterSourceApi, compute.ClusterSourceUi},
		},
	}).Return([]compute.ClusterDetails{
		{ClusterId: "4321", ClusterName: "some-test-cluster"},
		{ClusterId: "9876", ClusterName: "some-other-cluster"},
	}, nil)

	clusterPoliciesApi := mockWorkspaceClient.GetMockClusterPoliciesAPI()
	clusterPoliciesApi.EXPECT().GetByName(mock.Anything, "some-test-cluster-policy").Return(&compute.Policy{
		PolicyId: "9876",
	}, nil)

	diags := bundle.Apply(context.Background(), b, bundle.Seq(
		mutator.SelectTarget("env-overrides-lookup"),
		mutator.SetVariables(),
		mutator.ResolveResourceReferences(),
	))

	require.NoError(t, diags.Error())
	assert.Equal(t, "4321", b.Config.Variables["d"].Value)
	assert.Equal(t, "1234", b.Config.Variables["e"].Value)
	assert.Equal(t, "9876", b.Config.Variables["f"].Value)
}

func TestVariableTargetOverrides(t *testing.T) {
	var tcases = []struct {
		targetName         string
		pipelineName       string
		pipelineContinuous bool
		pipelineNumWorkers int
	}{
		{
			"use-default-variable-values",
			"a_string",
			true,
			42,
		},
		{
			"override-string-variable",
			"overridden_string",
			true,
			42,
		},
		{
			"override-int-variable",
			"a_string",
			true,
			43,
		},
		{
			"override-both-bool-and-string-variables",
			"overridden_string",
			false,
			42,
		},
	}

	for _, tcase := range tcases {
		t.Run(tcase.targetName, func(t *testing.T) {
			b := loadTarget(t, "./variables/variable_overrides_in_target", tcase.targetName)
			diags := bundle.Apply(context.Background(), b, bundle.Seq(
				mutator.SetVariables(),
				mutator.ResolveVariableReferences("variables")),
			)
			require.NoError(t, diags.Error())

			assert.Equal(t, tcase.pipelineName, b.Config.Resources.Pipelines["my_pipeline"].Name)
			assert.Equal(t, tcase.pipelineContinuous, b.Config.Resources.Pipelines["my_pipeline"].Continuous)
			assert.Equal(t, tcase.pipelineNumWorkers, b.Config.Resources.Pipelines["my_pipeline"].Clusters[0].NumWorkers)
		})
	}
}

func TestBundleWithEmptyVariableLoads(t *testing.T) {
	b := load(t, "./variables/empty")
	diags := bundle.Apply(context.Background(), b, mutator.SetVariables())
	require.ErrorContains(t, diags.Error(), "no value assigned to required variable a")
}