//go:build e2e
// +build e2e

package helm

import (
	"fmt"
	"os"
	"os/exec"
	"strings"

	"github.com/Azure/secrets-store-csi-driver-provider-azure/test/e2e/framework"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
)

const (
	// setting a long chartName to test it doesn't break with dependency charts
	// see https://github.com/Azure/secrets-store-csi-driver-provider-azure/issues/596
	chartName            = "csi-secrets-store-provider-azure"
	podIdentityChartName = "pi"
	providerChartsRepo   = "https://azure.github.io/secrets-store-csi-driver-provider-azure/charts"
)

// InstallInput is the input for Install.
type InstallInput struct {
	Config         *framework.Config
	NamespacedMode bool
}

// Install installs csi-secrets-store-provider-azure via Helm 3.
func Install(input InstallInput) {
	Expect(input.Config).NotTo(BeNil(), "input.Config is required for Helm.Install")

	cwd, err := os.Getwd()
	Expect(err).To(BeNil())

	// Change current working directory to repo root
	// Before installing csi-secrets-store-provider-azure through Helm
	os.Chdir("../..")
	defer os.Chdir(cwd)

	chartDir := input.Config.HelmChartDir
	// if chartDir is the official helm repo, then add the repo and update so the latest package is used
	if chartDir == providerChartsRepo {
		args := append([]string{"repo", "add", "csi-secrets-store-provider-azure", providerChartsRepo})
		err := helm(args)
		Expect(err).To(BeNil())

		args = append([]string{"repo", "update"})
		err = helm(args)
		Expect(err).To(BeNil())
		chartDir = "csi-secrets-store-provider-azure/csi-secrets-store-provider-azure"
	}

	args := append([]string{
		"install",
		chartName,
		chartDir,
		fmt.Sprintf("--namespace=%s", framework.NamespaceKubeSystem),
		fmt.Sprintf("--set=secrets-store-csi-driver.enableSecretRotation=true"),
		fmt.Sprintf("--set=secrets-store-csi-driver.rotationPollInterval=30s"),
		fmt.Sprintf("--set=secrets-store-csi-driver.syncSecret.enabled=true"),
		fmt.Sprintf(`--set=secrets-store-csi-driver.tokenRequests[0].audience=api://AzureADTokenExchange`),
		fmt.Sprintf(`--set=linux.podAnnotations.foo=bar`),
		fmt.Sprintf("--set=logVerbosity=5"),
		fmt.Sprintf("--set=linux.customUserAgent=csi-e2e"),
		fmt.Sprintf("--set=windows.customUserAgent=csi-e2e"),
		fmt.Sprintf("--set=writeCertAndKeyInSeparateFiles=true"),
		"--dependency-update",
		"--wait",
		"--timeout=5m",
		"--debug",
	})

	if input.Config.IsWindowsTest {
		args = append(args,
			fmt.Sprintf("--set=windows.enabled=true"),
			fmt.Sprintf("--set=secrets-store-csi-driver.windows.enabled=true"))
	}

	args = append(args, generateValueArgs(input.Config)...)

	err = helm(args)
	Expect(err).To(BeNil())
}

// UpgradeInput is the input for helm upgrade.
type UpgradeInput struct {
	Config *framework.Config
}

// Upgrade upgrades csi-secrets-store-provider-azure to chart specified in config
func Upgrade(input UpgradeInput) {
	Expect(input.Config).NotTo(BeNil(), "input.Config is required for Helm upgrade")

	cwd, err := os.Getwd()
	Expect(err).To(BeNil())

	// Change current working directory to repo root
	// Before installing csi-secrets-store-provider-azure through Helm
	os.Chdir("../..")
	defer os.Chdir(cwd)

	chartDir := input.Config.HelmChartDir

	//resolve helm dependency
	dependencyArgs := append([]string{
		"dependency",
		"update",
		chartDir,
		fmt.Sprintf("--namespace=%s", framework.NamespaceKubeSystem),
		"--debug",
	})
	err = helm(dependencyArgs)
	Expect(err).To(BeNil())

	args := append([]string{
		"upgrade",
		chartName,
		chartDir,
		fmt.Sprintf("--namespace=%s", framework.NamespaceKubeSystem),
		fmt.Sprintf("--set=secrets-store-csi-driver.enableSecretRotation=true"),
		fmt.Sprintf("--set=secrets-store-csi-driver.rotationPollInterval=30s"),
		fmt.Sprintf("--set=secrets-store-csi-driver.syncSecret.enabled=true"),
		fmt.Sprintf(`--set=secrets-store-csi-driver.tokenRequests[0].audience=api://AzureADTokenExchange`),
		fmt.Sprintf("--set=logVerbosity=1"),
		fmt.Sprintf("--set=linux.customUserAgent=csi-e2e"),
		fmt.Sprintf("--set=windows.customUserAgent=csi-e2e"),
		fmt.Sprintf("--set=writeCertAndKeyInSeparateFiles=true"),
		"--wait",
		"--timeout=5m",
		"--debug",
	})

	if input.Config.IsWindowsTest {
		args = append(args,
			fmt.Sprintf("--set=windows.enabled=true"),
			fmt.Sprintf("--set=secrets-store-csi-driver.windows.enabled=true"))
	}

	args = append(args, generateValueArgs(input.Config)...)

	err = helm(args)
	Expect(err).To(BeNil())
}

// Uninstall uninstalls csi-secrets-store-provider-azure via Helm 3.
func Uninstall() {
	args := []string{
		"uninstall",
		chartName,
		fmt.Sprintf("--namespace=%s", framework.NamespaceKubeSystem),
		"--debug",
	}

	// ignore error to allow cleanup completion
	_ = helm(args)
}

// ReleaseExists checks if csi release exists
func ReleaseExists() bool {
	args := []string{
		"status",
		chartName,
		fmt.Sprintf("--namespace=%s", framework.NamespaceKubeSystem),
	}

	err := helm(args)
	// chart not found error
	return err == nil
}

func generateValueArgs(config *framework.Config) []string {
	args := []string{
		fmt.Sprintf("--set=linux.image.repository=%s/%s", config.Registry, config.ImageName),
		fmt.Sprintf("--set=windows.image.repository=%s/%s", config.Registry, config.ImageName),
	}

	// Set image.tag only if there is an image version provided. Else rely on default values.
	if config.ImageVersion != "" {
		args = append(args, fmt.Sprintf("--set=linux.image.tag=%s", config.ImageVersion), fmt.Sprintf("--set=windows.image.tag=%s", config.ImageVersion))
	}

	// add the custom env file volume and mount if exists
	if config.AzureEnvironmentFilePath != "" {
		args = append(args,
			fmt.Sprintf("--set=linux.volumes[0].name=cloudenvfile-vol,linux.volumes[0].hostPath.path=%s,linux.volumes[0].hostPath.type=File", config.AzureEnvironmentFilePath),
			fmt.Sprintf("--set=linux.volumeMounts[0].name=cloudenvfile-vol,linux.volumeMounts[0].mountPath=%s", config.AzureEnvironmentFilePath),
		)
	}
	return args
}

func helm(args []string) error {
	By(fmt.Sprintf("helm %s", strings.Join(args, " ")))

	cmd := exec.Command("helm", args...)
	stdoutStderr, err := cmd.CombinedOutput()
	fmt.Printf("%s", stdoutStderr)

	return err
}

// InstallPodIdentity installs aad-pod-identity via Helm3
func InstallPodIdentity() {
	// add aad-pod-identity chart repo
	args := append([]string{
		"repo", "add", "aad-pod-identity", "https://raw.githubusercontent.com/Azure/aad-pod-identity/master/charts",
	})
	err := helm(args)
	Expect(err).To(BeNil())

	// update helm repo
	args = append([]string{
		"repo", "update",
	})
	err = helm(args)
	Expect(err).To(BeNil())

	// Install aad-pod-identity helm chart
	args = append([]string{
		"install",
		podIdentityChartName,
		fmt.Sprintf("--namespace=%s", framework.NamespaceKubeSystem),
		"aad-pod-identity/aad-pod-identity", "--set", "nmi.allowNetworkPluginKubenet=true", "--wait", "--timeout=5m", "--debug",
	})
	err = helm(args)
	Expect(err).To(BeNil())
}

// UninstallPodIdentity uninstalls aad-pod-identity
func UninstallPodIdentity() {
	args := []string{
		"uninstall",
		podIdentityChartName,
		fmt.Sprintf("--namespace=%s", framework.NamespaceKubeSystem),
		"--debug",
	}

	// ignore error to allow cleanup completion
	_ = helm(args)
}
