package core

import (
	"encoding/json"
	"fmt"
	"strconv"

	"github.com/guo-shaoge/tiflash-autoscale/pkg/config"
	"github.com/openkruise/kruise-api/apps/v1alpha1"
	"go.uber.org/zap"
	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// check the prom query of AutoScaler.
const (
	LabelAPP                  = "app"
	LabelNameForAlarm         = "app.kubernetes.io/component"
	LabelValueForAlarm        = "tiflash-autoscale"
	LabelNameForMetricsTopic  = "metrics_topic"
	LabelValueForMetricsTopic = "tiflash"

	LabelSideCarIstio = "sidecar.istio.io/inject"
	LabelMTLS         = "free-tier.tidbcloud.com/mtls"

	AnnotationKeyOfSupervisorRDVersion = "tiflash.autoscale.rdversion"
)

func getCloneSetTemplate(cloneSetName string, config *config.Config, logger *zap.Logger) *v1alpha1.CloneSet {
	res := getCloneSetTemplateInner(cloneSetName, config)
	if *config.GetClusterConfig().IsMinikubeTest {
		logger.Info("in minikube test env, will change podDesc")
		podSpec := &res.Spec.Template.Spec
		podSpec.NodeSelector = nil
		podSpec.Tolerations = nil
		podSpec.Affinity = nil
		podSpec.ServiceAccountName = "default"
		podSpec.Volumes = []v1.Volume{
			{
				Name: "sharedtmpdisk",
			},
			{
				Name: "cachedisk",
			},
			{
				Name: "server-cert",
			},
		}
		res.Spec.VolumeClaimTemplates = nil
	}
	jsonBytes, err := json.Marshal(*res)
	logger.Info("cloneset definition", zap.String("cloneset", string(jsonBytes)), zap.Error(err))
	if err != nil {
		// TODO return err?
		panic(err)
	}
	return res
}

func getCloneSetTemplateInner(cloneSetName string, cfg *config.Config) *v1alpha1.CloneSet {
	clusterConfig := cfg.GetClusterConfig()
	enableIstioProxy := *clusterConfig.EnableIstioProxy
	enableTLS := *clusterConfig.EnableTLS
	lbls := map[string]string{
		"app":                          cloneSetName,
		LabelNameForAlarm:              LabelValueForAlarm,
		LabelNameForMetricsTopic:       LabelValueForMetricsTopic,
		"sidecar.istio.io/inject":      strconv.FormatBool(enableIstioProxy),
		"free-tier.tidbcloud.com/mtls": strconv.FormatBool(enableTLS),

		"zr-evicter.tidbcloud.com/ignored": "true",
	}

	annos := map[string]string{
		"prometheus.io/path":   "/metrics",
		"prometheus.io/port":   "8234",
		"prometheus.io/scrape": "true",
	}

	volumes := []v1.Volume{
		{
			Name: "sharedtmpdisk",
			// EmptyDir: nil,
		},
	}

	// If the certificate is enabled, add the volume and volume mount for the certificate
	if enableIstioProxy || enableTLS {
		volumes = append(volumes, v1.Volume{
			Name: "server-cert",
			VolumeSource: v1.VolumeSource{
				Secret: &v1.SecretVolumeSource{
					SecretName: "tiflash-cn-cluster-secret",
				},
			},
		})
	}

	if enableIstioProxy {
		annos["proxy.istio.io/config"] = `{"holdApplicationUntilProxyStarts": true}`
		annos["sidecar.istio.io/userVolumeMount"] = `[{"name":"server-cert","mountPath":"/server-cert"}]`
	}

	mkCertEnv := func(path string) string {
		if enableTLS {
			return fmt.Sprintf("/server-cert/%s", path)
		}
		return ""
	}

	envs := []v1.EnvVar{
		{
			Name: "POD_IP",
			ValueFrom: &v1.EnvVarSource{
				FieldRef: &v1.ObjectFieldSelector{
					FieldPath: "status.podIP",
				},
			},
		},
		{
			Name: "POD_NAME",
			ValueFrom: &v1.EnvVarSource{
				FieldRef: &v1.ObjectFieldSelector{
					FieldPath: "metadata.name",
				},
			},
		},
		// no need anymore
		// {
		// 	Name:  "S3_FOR_TIFLASH_LOG",
		// 	Value: ReadNodeLogUploadS3Bucket,
		// },
		{
			Name:  "TIFLASH_CACHE_PATH",
			Value: *clusterConfig.CNLocalCachePath,
		},
		{
			Name:  "TIFLASH_CACHE_CAP",
			Value: *clusterConfig.CNLocalCacheSize,
		},
		// no need anymore
		// {
		// 	Name:  "AS_RUN_MODE_ENV",
		// 	Value: RunModeEnvString4Supervisor(),
		// },
		{
			Name:  "CA_PATH",
			Value: mkCertEnv("ca.crt"),
		},
		{
			Name:  "CERT_PATH",
			Value: mkCertEnv("tls.crt"),
		},
		{
			Name:  "KEY_PATH",
			Value: mkCertEnv("tls.key"),
		},
		{
			Name:  config.EnvKeyCloudProvider,
			Value: *clusterConfig.CloudProvider,
		},
		{
			Name:  config.EnvKeyRegion,
			Value: *clusterConfig.Region,
		},
	}

	switch *clusterConfig.CloudProvider {
	case "aliyun":
		envs = append(envs, v1.EnvVar{
			Name:  "ALIBABA_CLOUD_REGION_ID",
			Value: *clusterConfig.Region,
		})
	}

	cloneSet := &v1alpha1.CloneSet{
		ObjectMeta: metav1.ObjectMeta{
			Name: cloneSetName,
			Labels: map[string]string{
				"app":                    cloneSetName,
				LabelNameForAlarm:        LabelValueForAlarm,
				LabelNameForMetricsTopic: LabelValueForMetricsTopic,
			},
			Annotations: map[string]string{
				AnnotationKeyOfSupervisorRDVersion: getSupervisorRdVersion(),
			},
		},
		Spec: v1alpha1.CloneSetSpec{
			Replicas: Int32Ptr(int32(*clusterConfig.WarmupPoolCap)),
			Selector: &metav1.LabelSelector{
				MatchLabels: map[string]string{
					"app": cloneSetName,
				}},
			Template: v1.PodTemplateSpec{
				ObjectMeta: metav1.ObjectMeta{
					Labels:      lbls,
					Annotations: annos,
				},
				// pod anti affinity
				Spec: v1.PodSpec{
					NodeSelector: clusterConfig.NodeSelector,
					Tolerations:  getComputePodToleration(cfg),
					Affinity: &v1.Affinity{
						PodAntiAffinity: getComputePodAntiAffinity(cloneSetName),
					},
					ServiceAccountName: "s3-sa",
					// container
					Containers: []v1.Container{
						{
							// ENV
							Env:  envs,
							Name: "supervisor",
							// docker image
							Image:           *clusterConfig.SupervisorImage,
							ImagePullPolicy: "IfNotPresent",
							VolumeMounts:    getVolumesMount(enableTLS, enableIstioProxy),
							Resources: v1.ResourceRequirements{
								Requests: v1.ResourceList{
									v1.ResourceCPU: resource.MustParse(fmt.Sprintf("%d", *clusterConfig.CoreNumPerPod-1)),
								},
								Limits: v1.ResourceList{
									v1.ResourceCPU: resource.MustParse(fmt.Sprintf("%d", *clusterConfig.CoreNumPerPod)),
								},
							},
						},
						{
							Name:            "tiflash-log",
							Image:           getBusyBoxDockerImager(),
							ImagePullPolicy: "IfNotPresent",
							Args: []string{
								"/bin/sh",
								"-c",
								"touch /tiflash/log/tiflash.log; tail -n0 -F /tiflash/log/tiflash.log;",
							},
							VolumeMounts: []v1.VolumeMount{
								{
									Name:      "sharedtmpdisk",
									MountPath: "/tiflash/log/",
								},
							},
						},
						{
							Name:            "tiflash-err-log",
							Image:           getBusyBoxDockerImager(),
							ImagePullPolicy: "IfNotPresent",
							Args: []string{
								"/bin/sh",
								"-c",
								"touch /tiflash/log/tiflash_error.log; tail -n0 -F /tiflash/log/tiflash_error.log;",
							},
							VolumeMounts: []v1.VolumeMount{
								{
									Name:      "sharedtmpdisk",
									MountPath: "/tiflash/log/",
								},
							},
						},
					},
					Volumes: volumes,
				},
			},
			VolumeClaimTemplates: getVolumeClaimTemplates(),
		},
	}
	if clusterConfig.PodMemoryRequest != nil && *clusterConfig.PodMemoryRequest != "" {
		cloneSet.Spec.Template.Spec.Containers[0].Resources.Requests[v1.ResourceMemory] =
			resource.MustParse(*clusterConfig.PodMemoryRequest)
	}
	if clusterConfig.PodMemoryLimit != nil && *clusterConfig.PodMemoryLimit != "" {
		cloneSet.Spec.Template.Spec.Containers[0].Resources.Limits[v1.ResourceMemory] =
			resource.MustParse(*clusterConfig.PodMemoryLimit)
	}
	if clusterConfig.PodCpuRequest != nil && *clusterConfig.PodCpuRequest != "" {
		cloneSet.Spec.Template.Spec.Containers[0].Resources.Requests[v1.ResourceCPU] =
			resource.MustParse(*clusterConfig.PodCpuRequest)
	}

	return cloneSet
}

// func getCloneSetTemplateInner(cloneSetName string, config *config.Config) *v1alpha1.CloneSet {
// 	clusterConfig := config.GetClusterConfig()
// 	lbls := map[string]string{
// 		LabelAPP:                 cloneSetName,
// 		LabelNameForAlarm:        LabelValueForAlarm,
// 		LabelNameForMetricsTopic: LabelValueForMetricsTopic,
// 		LabelSideCarIstio:        strconv.FormatBool(*clusterConfig.EnableIstioProxy),
// 		LabelMTLS:                strconv.FormatBool(*clusterConfig.EnableTLS),
// 	}
// 	annos := map[string]string{
// 		"prometheus.io/path":   "/metrics",
// 		"prometheus.io/port":   "8234",
// 		"prometheus.io/scrape": "true",
// 	}
// 	volumes := []v1.Volume{
// 		{
// 			Name: "sharedtmpdisk",
// 			// EmptyDir: nil,
// 		},
// 		{
// 			Name: "server-cert",
// 			VolumeSource: v1.VolumeSource{
// 				Secret: &v1.SecretVolumeSource{
// 					SecretName: "tiflash-cn-cluster-secret",
// 				},
// 			},
// 		},
// 	}
//
// 	if *clusterConfig.EnableIstioProxy {
// 		annos["proxy.istio.io/config"] = `{"holdApplicationUntilProxyStarts": true}`
// 		annos["sidecar.istio.io/userVolumeMount"] = `[{"name":"server-cert","mountPath":"/server-cert"}]`
// 	}
//
// 	resourceLimit := v1.ResourceList{
// 		v1.ResourceCPU: resource.MustParse(fmt.Sprintf("%v", *clusterConfig.CoreNumPerPod)),
// 		// mem is unlimited.
// 		// v1.ResourceMemory: resource.MustParse("128Mi"),
// 	}
// 	// request is same with limit.
// 	resourceRequest := resourceLimit
//
// 	res := &v1alpha1.CloneSet{
// 		ObjectMeta: metav1.ObjectMeta{
// 			Name: cloneSetName,
// 			Labels: map[string]string{
// 				LabelAPP:                 cloneSetName,
// 				LabelNameForAlarm:        LabelValueForAlarm,
// 				LabelNameForMetricsTopic: LabelValueForMetricsTopic,
// 			},
// 			Annotations: map[string]string{
// 				AnnotationKeyOfSupervisorRDVersion: getSupervisorRdVersion(),
// 			},
// 		},
// 		Spec: v1alpha1.CloneSetSpec{
// 			Replicas: Int32Ptr(int32(*clusterConfig.WarmupPoolCap)),
// 			Selector: &metav1.LabelSelector{
// 				MatchLabels: map[string]string{
// 					"app": cloneSetName,
// 				}},
// 			Template: v1.PodTemplateSpec{
// 				ObjectMeta: metav1.ObjectMeta{
// 					Labels:      lbls,
// 					Annotations: annos,
// 				},
// 				// pod anti affinity
// 				Spec: v1.PodSpec{
// 					NodeSelector: map[string]string{
// 						"tiflash.used-for-compute": "true",
// 						// "node.kubernetes.io/instance-type": "m6a.2xlarge",
// 					},
// 					Tolerations: getComputePodToleration(),
// 					Affinity: &v1.Affinity{
// 						PodAntiAffinity: getComputePodAntiAffinity(cloneSetName),
// 					},
// 					ServiceAccountName: "s3-sa",
// 					// container
// 					Containers: []v1.Container{
// 						{
// 							Resources: v1.ResourceRequirements{
// 								Limits:   resourceLimit,
// 								Requests: resourceRequest,
// 							},
// 							// ENV
// 							Env: []v1.EnvVar{
// 								{
// 									Name: "POD_IP",
// 									ValueFrom: &v1.EnvVarSource{
// 										FieldRef: &v1.ObjectFieldSelector{
// 											FieldPath: "status.podIP",
// 										},
// 									},
// 								},
// 								{
// 									Name: "POD_NAME",
// 									ValueFrom: &v1.EnvVarSource{
// 										FieldRef: &v1.ObjectFieldSelector{
// 											FieldPath: "metadata.name",
// 										},
// 									},
// 								},
// 								{
// 									Name:  "TIFLASH_CACHE_PATH",
// 									Value: *clusterConfig.CNLocalCachePath,
// 								},
// 								{
// 									Name:  "TIFLASH_CACHE_CAP",
// 									Value: *clusterConfig.CNLocalCacheSize,
// 								},
// 							},
// 							Name: "supervisor",
// 							// docker image
// 							Image:           *config.GetClusterConfig().SupervisorImage,
// 							ImagePullPolicy: "IfNotPresent",
// 							VolumeMounts:    getVolumesMount(),
// 						},
// 						{
// 							Name:            "tiflash-log",
// 							Image:           getBusyBoxDockerImager(),
// 							ImagePullPolicy: "IfNotPresent",
// 							Args: []string{
// 								"/bin/sh",
// 								"-c",
// 								"touch /tiflash/log/tiflash.log; tail -n0 -F /tiflash/log/tiflash.log;",
// 							},
// 							VolumeMounts: []v1.VolumeMount{
// 								{
// 									Name:      "sharedtmpdisk",
// 									MountPath: "/tiflash/log/",
// 								},
// 							},
// 						},
// 						{
// 							Name:            "tiflash-err-log",
// 							Image:           getBusyBoxDockerImager(),
// 							ImagePullPolicy: "IfNotPresent",
// 							Args: []string{
// 								"/bin/sh",
// 								"-c",
// 								"touch /tiflash/log/tiflash_error.log; tail -n0 -F /tiflash/log/tiflash_error.log;",
// 							},
// 							VolumeMounts: []v1.VolumeMount{
// 								{
// 									Name:      "sharedtmpdisk",
// 									MountPath: "/tiflash/log/",
// 								},
// 							},
// 						},
// 					},
// 					Volumes: volumes,
// 				},
// 			},
// 			VolumeClaimTemplates: getVolumeClaimTemplates(),
// 		},
// 	}
// 	return res
// }

func getComputePodToleration(cfg *config.Config) []v1.Toleration {
	tolerations := make([]v1.Toleration, 0)
	for label, value := range cfg.ClusterConfig.NodeSelector {
		tolerations = append(tolerations, v1.Toleration{
			Key:      label,
			Value:    value,
			Operator: v1.TolerationOpEqual,
			Effect:   v1.TaintEffectNoSchedule,
		})
	}
	return tolerations
}

func getComputePodAntiAffinity(cloneSetName string) *v1.PodAntiAffinity {
	return &v1.PodAntiAffinity{
		RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{
			{
				TopologyKey: "kubernetes.io/hostname",
				LabelSelector: &metav1.LabelSelector{
					MatchExpressions: []metav1.LabelSelectorRequirement{
						{
							Key:      "app",
							Operator: "In",
							Values:   []string{cloneSetName, "autoscale"},
						},
					},
				},
			},
		},
	}
}

func getVolumesMount(enableTLS, enableIstioProxy bool) []v1.VolumeMount {
	mounts := []v1.VolumeMount{
		{
			Name:      "sharedtmpdisk",
			MountPath: "/tiflash/log/",
		},

		{
			Name:      "cachedisk",
			MountPath: "/data",
		},
	}

	// Mount the certificates only when it is created
	if enableTLS || enableIstioProxy {
		mounts = append(mounts, v1.VolumeMount{
			Name:      "server-cert",
			MountPath: "/server-cert",
		})
	}

	return mounts
}

func getBusyBoxDockerImager() string {
	return "gcr.io/pingcap-public/dbaas/busybox:1.31.1"
}

func getVolumeClaimTemplates() []v1.PersistentVolumeClaim {
	scn := "tiflash-gp3-6000-600"
	vm := v1.PersistentVolumeFilesystem
	return []v1.PersistentVolumeClaim{
		{
			ObjectMeta: metav1.ObjectMeta{
				Name: "cachedisk",
			},
			Spec: v1.PersistentVolumeClaimSpec{
				AccessModes: []v1.PersistentVolumeAccessMode{
					"ReadWriteOnce",
				},
				Resources: v1.ResourceRequirements{
					Requests: v1.ResourceList{
						"storage": resource.MustParse("105Gi"),
					},
				},
				StorageClassName: &scn,
				VolumeMode:       &vm,
			},
		},
	}
}

func getSupervisorRdVersion() string {
	return "8"
}

func Int32Ptr(val int32) *int32 {
	ret := new(int32)
	*ret = int32(val)
	return &val
}
