package vector_agent

import (
	"context"

	"github.com/pingcap/errors"
	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/utils/pointer"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	gardencorev1beta1 "github.com/tidbcloud/infra-api/pkg/gardener/core/apis/v1beta1"
	"github.com/tidbcloud/infra-api/pkg/o11y/metric_agent"
)

const (
	volumeNameConfig = "config"
	volumeNameData   = "data"
	volumeNameTLS    = "tls"

	clusterTLSSecretName = "tidbmonitor-db-tls-assets"
)

func CreateDeployment(ctx context.Context, cli client.Client, deployName, cmName, pvcName, namespace, image string, replicaCount int32, envLabels *EnvLabels) error {
	labels := map[string]string{
		"app": deployName,
	}

	deployment := &appsv1.Deployment{
		ObjectMeta: metav1.ObjectMeta{
			Name:      deployName,
			Namespace: namespace,
		},
	}

	if _, err := controllerutil.CreateOrUpdate(ctx, cli, deployment, func() error {
		deployment.ObjectMeta.Labels = labels
		deployment.Spec.Replicas = pointer.Int32(replicaCount)
		deployment.Spec.Selector = &metav1.LabelSelector{MatchLabels: labels}
		// Vector Agent deployment pod uses one pvc and shares state file. An agent process will lock the state file to prevent
		// other agents from processing at the same time.
		// If we use rollingupdate strategy, the newer agent will never be ready because the old one won't release the file lock.
		deployment.Spec.Strategy.Type = appsv1.RecreateDeploymentStrategyType
		deployment.Spec.Template.ObjectMeta.Labels = labels
		deployment.Spec.Template.Spec.Volumes = buildVolumes(cmName, pvcName)
		deployment.Spec.Template.Spec.Containers = buildContainers(deployName, image, envLabels, deployment.Spec.Template.Spec.Containers)
		return nil
	}); err != nil {
		return errors.Annotate(err, "failed to create or update vector deployment")
	}

	return nil
}

func buildContainers(deployName, image string, envLabels *EnvLabels, previous []corev1.Container) (res []corev1.Container) {
	var vectorContainer corev1.Container
	for _, c := range previous {
		if c.Name == deployName {
			vectorContainer = c
		} else {
			res = append(res, c)
		}
	}
	vectorContainer.Name = deployName
	vectorContainer.Image = image
	vectorContainer.Env = buildEnv(envLabels)
	vectorContainer.VolumeMounts = buildMounts()
	vectorContainer.Resources = buildResources()
	res = append(res, vectorContainer)
	return
}

func buildEnv(envLabels *EnvLabels) []corev1.EnvVar {
	return []corev1.EnvVar{
		{
			Name:  "K8S_CLUSTER_NAME",
			Value: envLabels.K8sClusterName,
		},
		{
			Name:  "K8S_NAMESPACE",
			Value: envLabels.K8sNamespace,
		},
		{
			Name:  "PROVIDER",
			Value: envLabels.Provider,
		},
		{
			Name:  "REGION",
			Value: envLabels.Region,
		},
		{
			Name:  "PROJECT_ID",
			Value: envLabels.ProjectID,
		},
		{
			Name:  "TENANT_ID",
			Value: envLabels.TenantID,
		},
		{
			Name:  "CLUSTER_ID",
			Value: envLabels.ClusterID,
		},
	}
}

func buildMounts() []corev1.VolumeMount {
	return []corev1.VolumeMount{
		{
			Name:      volumeNameConfig,
			MountPath: configDir,
		},
		{
			Name:      volumeNameData,
			MountPath: dataDir,
		},
		{
			Name:      volumeNameTLS,
			MountPath: tlsMountPath,
		},
	}
}

func buildResources() corev1.ResourceRequirements {
	return corev1.ResourceRequirements{
		Limits: corev1.ResourceList{
			corev1.ResourceCPU:    resource.MustParse("500m"),
			corev1.ResourceMemory: resource.MustParse("512Mi"),
		},
		Requests: corev1.ResourceList{
			corev1.ResourceCPU:    resource.MustParse("10m"),
			corev1.ResourceMemory: resource.MustParse("64Mi"),
		},
	}
}

func buildVolumes(cmName, pvcName string) []corev1.Volume {
	return []corev1.Volume{
		{
			Name: volumeNameConfig,
			VolumeSource: corev1.VolumeSource{
				ConfigMap: &corev1.ConfigMapVolumeSource{
					LocalObjectReference: corev1.LocalObjectReference{
						Name: cmName,
					},
				},
			},
		},
		{
			Name: volumeNameData,
			VolumeSource: corev1.VolumeSource{
				PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
					ClaimName: pvcName,
				},
			},
		},
		{
			Name: volumeNameTLS,
			VolumeSource: corev1.VolumeSource{
				Secret: &corev1.SecretVolumeSource{
					SecretName:  clusterTLSSecretName,
					DefaultMode: pointer.Int32(420),
				},
			},
		},
	}
}

func BuildEnvLabels(shoot *gardencorev1beta1.Shoot, c *cloudv1.Cluster, n *cloudv1.Network) *EnvLabels {
	k8sClusterName := shoot.Status.TechnicalID
	K8sNamespace := c.PrefixedId()

	provider := n.Spec.Provider
	region := n.Spec.Region

	tenantID, projectID, clusterID := metric_agent.GetIDsFromClusterCRD(c, n.Name)

	return &EnvLabels{
		K8sClusterName: k8sClusterName,
		K8sNamespace:   K8sNamespace,

		Region:   region,
		Provider: provider,

		ProjectID: projectID,
		TenantID:  tenantID,
		ClusterID: clusterID,
	}
}
