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, name string, namespace string, image string, envLabels *EnvLabels) error {
	labels := map[string]string{
		"app": name,
	}

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

	if _, err := controllerutil.CreateOrUpdate(ctx, cli, deployment, func() error {
		deployment.ObjectMeta.Labels = labels
		deployment.Spec = appsv1.DeploymentSpec{
			Replicas: pointer.Int32Ptr(1),
			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.
			Strategy: appsv1.DeploymentStrategy{
				Type: appsv1.RecreateDeploymentStrategyType,
			},
			Template: corev1.PodTemplateSpec{
				ObjectMeta: metav1.ObjectMeta{
					Labels: labels,
				},
				Spec: corev1.PodSpec{
					Volumes: buildVolumes(name),
					Containers: []corev1.Container{
						{
							Name:            name,
							Image:           image,
							Env:             buildEnv(envLabels),
							VolumeMounts:    buildMounts(),
							Resources:       buildResources(),
							ImagePullPolicy: corev1.PullAlways,
						},
					},
				},
			},
		}
		return nil
	}); err != nil {
		return errors.Annotate(err, "failed to create or update vector deployment")
	}

	return nil
}

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(name string) []corev1.Volume {
	return []corev1.Volume{
		{
			Name: volumeNameConfig,
			VolumeSource: corev1.VolumeSource{
				ConfigMap: &corev1.ConfigMapVolumeSource{
					LocalObjectReference: corev1.LocalObjectReference{
						Name: agentConfigMapName,
					},
				},
			},
		},
		{
			Name: volumeNameData,
			VolumeSource: corev1.VolumeSource{
				PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
					ClaimName: name,
				},
			},
		},
		{
			Name: volumeNameTLS,
			VolumeSource: corev1.VolumeSource{
				Secret: &corev1.SecretVolumeSource{
					SecretName:  clusterTLSSecretName,
					DefaultMode: pointer.Int32Ptr(420),
				},
			},
		},
	}
}

func BuildEnvLabels(networkGardenStatus *cloudv1.GardenerNetworkStatus, 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,
	}
}
