//go:build e2e
// +build e2e

package e2e

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

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

	"github.com/tidbcloud/k8s-version-manager/test/utils"
)

const (
	// namespace where the project is deployed in
	namespace = "k8s-version-manager-system"
	// serviceAccountName created for the project
	serviceAccountName = "k8s-version-manager-controller-manager"
	// metricsServiceName is the name of the metrics service of the project
	metricsServiceName = "k8s-version-manager-controller-manager-metrics-service"
	// metricsRoleBindingName is the name of the RBAC that will be created to allow get the metrics data
	metricsRoleBindingName = "k8s-version-manager-metrics-binding"
)

var (
	// Optional Environment Variables:
	// - CERT_MANAGER_INSTALL_SKIP=true: Skips CertManager installation during test setup.
	// These variables are useful if CertManager is already installed, avoiding
	// re-installation and conflicts.
	skipCertManagerInstall = os.Getenv("CERT_MANAGER_INSTALL_SKIP") == "true"
	// isCertManagerAlreadyInstalled will be set true when CertManager CRDs be found on the cluster
	isCertManagerAlreadyInstalled = false

	// projectImage is the name of the image which will be build and loaded
	// with the code source changes to be tested.
	projectImage = "ng.tidbcloud.com/k8s-version-manager:v0.0.1"
	// mockServerImage is the name of the image for the mock server
	mockServerImage = "ng.tidbcloud.com/mock-server:v0.0.1"

	// suiteFailed tracks if any test failed to skip cleanup if needed
	suiteFailed = false
)

// TestE2E runs the end-to-end (e2e) test suite for the project. These tests execute in an isolated,
// temporary environment to validate project changes with the purpose of being used in CI jobs.
// The default setup requires Kind, builds/loads the Manager Docker image locally, and installs
// CertManager.
func TestE2E(t *testing.T) {
	RegisterFailHandler(Fail)
	_, _ = fmt.Fprintf(GinkgoWriter, "Starting k8s-version-manager integration test suite\n")
	RunSpecs(t, "e2e suite")
}

var _ = BeforeSuite(func() {
	By("building the manager(Operator) image")
	cmd := exec.Command("make", "docker-build", fmt.Sprintf("IMG=%s", projectImage))
	_, err := utils.Run(cmd)
	ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Failed to build the manager(Operator) image")

	By("building the mock-server image")
	cmd = exec.Command("docker", "build", "-t", mockServerImage, "-f", "test/e2e/mock_server/Dockerfile", ".")
	_, err = utils.Run(cmd)
	ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Failed to build the mock-server image")

	// TODO(user): If you want to change the e2e test vendor from Kind, ensure the image is
	// built and available before running the tests. Also, remove the following block.
	By("loading the manager(Operator) image on Kind")
	err = utils.LoadImageToKindClusterWithName(projectImage)
	ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Failed to load the manager(Operator) image into Kind")

	By("loading the mock-server image on Kind")
	err = utils.LoadImageToKindClusterWithName(mockServerImage)
	ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Failed to load the mock-server image into Kind")

	// The tests-e2e are intended to run on a temporary cluster that is created and destroyed for testing.
	// To prevent errors when tests run in environments with CertManager already installed,
	// we check for its presence before execution.
	// Setup CertManager before the suite if not skipped and if not already installed
	if !skipCertManagerInstall {
		By("checking if cert manager is installed already")
		isCertManagerAlreadyInstalled = utils.IsCertManagerCRDsInstalled()
		if !isCertManagerAlreadyInstalled {
			_, _ = fmt.Fprintf(GinkgoWriter, "Installing CertManager...\n")
			Expect(utils.InstallCertManager()).To(Succeed(), "Failed to install CertManager")
		} else {
			_, _ = fmt.Fprintf(GinkgoWriter, "WARNING: CertManager is already installed. Skipping installation...\n")
		}
	}

	By("creating manager namespace")
	cmd = exec.Command("kubectl", "create", "ns", namespace)
	_, _ = utils.Run(cmd) // Ignore error if it already exists

	By("labeling the namespace to enforce the restricted security policy")
	cmd = exec.Command("kubectl", "label", "--overwrite", "ns", namespace,
		"pod-security.kubernetes.io/enforce=restricted")
	_, err = utils.Run(cmd)
	Expect(err).NotTo(HaveOccurred(), "Failed to label namespace")

	By("installing CRDs")
	cmd = exec.Command("make", "install")
	_, err = utils.Run(cmd)
	Expect(err).NotTo(HaveOccurred(), "Failed to install CRDs")

	By("deploying the mock-server")
	mockServerYaml := fmt.Sprintf(`
apiVersion: v1
kind: Service
metadata:
  name: mock-server
  namespace: %s
spec:
  selector:
    app: mock-server
  ports:
  - port: 8080
    targetPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mock-server
  namespace: %s
spec:
  selector:
    matchLabels:
      app: mock-server
  template:
    metadata:
      labels:
        app: mock-server
    spec:
      containers:
      - name: mock-server
        image: %s
        ports:
        - containerPort: 8080
        securityContext:
          allowPrivilegeEscalation: false
          capabilities:
            drop: ["ALL"]
          runAsNonRoot: true
          runAsUser: 65532
          seccompProfile:
            type: RuntimeDefault
`, namespace, namespace, mockServerImage)
	cmd = exec.Command("kubectl", "apply", "-f", "-")
	cmd.Stdin = strings.NewReader(mockServerYaml)
	_, err = utils.Run(cmd)
	Expect(err).NotTo(HaveOccurred(), "Failed to deploy mock-server")

	By("creating the controller-config ConfigMap")
	configYaml := `
serviceEndpoints:
  dataPlaneManager: http://mock-server:8080
  clusterService: http://mock-server:8080
metadataNamespace: k8s-version-manager-system
`
	tmpFile, err := os.CreateTemp("", "controller-config-*.yaml")
	Expect(err).NotTo(HaveOccurred(), "Failed to create temp file for controller-config")
	defer os.Remove(tmpFile.Name())

	_, err = tmpFile.WriteString(configYaml)
	Expect(err).NotTo(HaveOccurred(), "Failed to write config YAML to temp file")

	err = tmpFile.Close()
	Expect(err).NotTo(HaveOccurred(), "Failed to close temp file for controller-config")

	cmdStr := fmt.Sprintf("kubectl create configmap controller-config --from-file=config.yaml=%s -n %s --dry-run=client -o yaml | kubectl apply -f -", tmpFile.Name(), namespace)
	cmd = exec.Command("sh", "-c", cmdStr)
	_, err = utils.Run(cmd)
	Expect(err).NotTo(HaveOccurred(), "Failed to create/update controller-config ConfigMap")

	By("deploying the controller-manager with final config")
	// Use kustomize to generate the base YAML
	cmd = exec.Command("make", "build-installer", fmt.Sprintf("IMG=%s", projectImage))
	_, err = utils.Run(cmd)
	Expect(err).NotTo(HaveOccurred(), "Failed to build installer")

	// Apply the base deployment
	cmd = exec.Command("kubectl", "apply", "-f", "dist/install.yaml", "-n", namespace)
	_, err = utils.Run(cmd)
	Expect(err).NotTo(HaveOccurred(), "Failed to apply controller-manager deployment")

	// Immediately patch it with the required config (Scheme A - ensures first start has config)
	patchYaml := `
spec:
  template:
    spec:
      containers:
      - name: manager
        args:
        - --leader-elect
        - --health-probe-bind-address=:8081
        - --config=/etc/kvm/config.yaml
        volumeMounts:
        - name: config-volume
          mountPath: /etc/kvm
      volumes:
      - name: config-volume
        configMap:
          name: controller-config
`
	cmd = exec.Command("kubectl", "patch", "deployment", "k8s-version-manager-controller-manager",
		"-n", namespace, "--type=strategic", "--patch", patchYaml)
	_, err = utils.Run(cmd)
	Expect(err).NotTo(HaveOccurred(), "Failed to patch controller-manager deployment")

	By("forcing rollout of the deployment to use the latest image and config")
	cmd = exec.Command("kubectl", "rollout", "restart", "deployment/k8s-version-manager-controller-manager", "-n", namespace)
	_, _ = utils.Run(cmd)

	By("waiting for controller-manager rollout to complete")
	cmd = exec.Command("kubectl", "rollout", "status", "deployment/k8s-version-manager-controller-manager",
		"-n", namespace, "--timeout=2m")
	_, err = utils.Run(cmd)
	Expect(err).NotTo(HaveOccurred(), "Controller manager rollout failed")
})

var _ = AfterSuite(func() {
	if suiteFailed {
		_, _ = fmt.Fprintf(GinkgoWriter, "Skipping AfterSuite cleanup to preserve environment for debugging\n")
		return
	}

	By("cleaning up the mock-server")
	cmd := exec.Command("kubectl", "delete", "deployment,service", "mock-server", "-n", namespace, "--ignore-not-found")
	_, _ = utils.Run(cmd)

	By("cleaning up UpgradeOperation resources to allow finalizers to run")
	cmd = exec.Command("kubectl", "delete", "upgradeoperation", "--all", "-n", namespace, "--wait=true", "--timeout=1m")
	_, _ = utils.Run(cmd)

	By("undeploying the controller-manager")
	cmd = exec.Command("make", "undeploy")
	_, _ = utils.Run(cmd)

	By("uninstalling CRDs")
	cmd = exec.Command("make", "uninstall")
	_, _ = utils.Run(cmd)

	By("removing manager namespace")
	cmd = exec.Command("kubectl", "delete", "ns", namespace, "--ignore-not-found")
	_, _ = utils.Run(cmd)

	// Teardown CertManager after the suite if not skipped and if it was not already installed
	if !skipCertManagerInstall && !isCertManagerAlreadyInstalled {
		By("uninstalling CertManager")
		utils.UninstallCertManager()
	}
})
