package gardener

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"go.opentelemetry.io/otel/trace"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

	"github.com/pingcap/errors"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"github.com/tidbcloud/infra-api/pkg/k8s"
	tracerutil "github.com/tidbcloud/infra-api/pkg/tracer"
)

const (
	imageTagVersionPrefix = "v"
)

func deployOperator(
	ctx context.Context,
	cli client.Client,
	kubeconfig []byte,
	operatorNamespace string,
	manifestDirectoryPrefix string,
	k8sVersion string,
	region string,
) (_ controllerutil.OperationResult, errResult error) {
	spanCtx, span := tracer.Start(ctx, "deployOperator", trace.WithSpanKind(trace.SpanKindClient))
	defer func() { tracerutil.EndSpan(span, errResult) }()

	var (
		op  controllerutil.OperationResult
		err error
	)
	ns := &corev1.Namespace{
		ObjectMeta: metav1.ObjectMeta{Name: operatorNamespace},
	}
	_, err = k8s.CreateIgnoreExists(spanCtx, cli, ns)
	if err != nil {
		return op, err
	}

	kubeconfigFilename, err := k8s.WriteTempFile(kubeconfig)
	if err != nil {
		return op, err
	}
	defer os.Remove(kubeconfigFilename)

	var (
		helm    = k8s.NewHelm().SetDir(manifestDirectoryPrefix).WithKubeConfig(kubeconfigFilename)
		kubectl = k8s.NewKubectl().SetDir(manifestDirectoryPrefix).WithKubeConfig(kubeconfigFilename)
	)

	schedulerTag := k8sVersion
	if !strings.HasPrefix(k8sVersion, imageTagVersionPrefix) {
		schedulerTag = "v" + schedulerTag
	}

	// Render operator yaml manifest and apply it
	// Install operator, we can remove --namespace when https://github.com/pingcap/tidb-operator/pull/4120 merged
	span.AddEvent("helm install operator")
	_, out, err := helm.Apply(kubectl.WithNamespace(operatorNamespace), "tidb-operator",
		filepath.Join(manifestDirectoryPrefix, "/charts/tidb-operator.tgz"),
		"--namespace", operatorNamespace,
		"--kube-version", k8sVersion,
		"--set", "features={AdvancedStatefulSet=true,VolumeModifying=true}",
		"--set", fmt.Sprintf("operatorImage=%s", operatorImage),
		"--set", fmt.Sprintf("tidbBackupManagerImage=%s", backupManagerImage),
		"--set", "advancedStatefulset.create=true",
		"--set", fmt.Sprintf("advancedStatefulset.image=%s", advanceStatefulsetImage),
		"--set", "controllerManager.resources.limits.cpu=4",
		"--set", fmt.Sprintf("scheduler.kubeSchedulerImageTag=%s", schedulerTag),
		"--set", "controllerManager.resources.limits.memory=4Gi",
		"--set", "controllerManager.env[0].name=AWS_REGION",
		"--set", fmt.Sprintf("controllerManager.env[0].value=%s", region),
	)
	if err != nil {
		return op, errors.Annotatef(err, "failed to install tidb-operator, output: %s", out)
	}

	// Install operator CRD
	span.AddEvent("install operator CRD")
	_, out, err = kubectl.ReplaceWithFile(filepath.Join(manifestDirectoryPrefix, "/manifests/tidb-operator-crd.yaml"))
	if err != nil {
		return op, errors.Errorf("failed to run kubectl: output: %s, err: %s", out, err.Error())
	}

	// Install advanced statefulset crd
	span.AddEvent("Install advanced statefulset crd")
	op, out, err = kubectl.ApplyWithFile(filepath.Join(manifestDirectoryPrefix, "manifests/advanced-statefulset-crd.v1.yaml"))
	if err != nil {
		return op, errors.Errorf("failed to run kubectl: output: %s, err: %s", out, err.Error())
	}

	return op, nil
}
