package cluster

import (
	"fmt"
	"path"
	"strconv"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-api/pkg/config"
	"github.com/tidbcloud/infra-api/pkg/consts"
	"github.com/tidbcloud/infra-api/pkg/nodegroup"
	"github.com/tidbcloud/infra-api/pkg/pingcap/label"

	"github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1"
	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/intstr"
)

const (
	// paramerters of slowlogs
	slowQueryLogVolumeName = "slowlog"
	slowQueryLogDir        = "/var/log/tidb"
	slowQueryLogFile       = slowQueryLogDir + "/slowlog"
	// clusterCertPath is where the cert for inter-cluster communication stored (if any)
	clusterCertPath = "/var/lib/tidb-tls"
	// serverCertPath is where the tidb-server cert stored (if any)
	serverCertPath = "/var/lib/tidb-server-tls"
	// tlsSecretRootCAKey is the key used in tls secret for the root CA.
	// When user use self-signed certificates, the root CA must be provided. We
	// following the same convention used in Kubernetes service token.
	tlsSecretRootCAKey = corev1.ServiceAccountRootCAKey
)

func extraAccessLabels(tc *v1alpha1.TidbCluster) map[string]string {
	return map[string]string{
		label.ComponentLabelKey: getExtraAccessSvcName(tc.Name),
	}
}

func newExtraConfigMap(tc *v1alpha1.TidbCluster) *corev1.ConfigMap {
	config := v1alpha1.NewTiDBConfig()
	config.Set("security.skip-grant-table", true)
	config.Set("run-ddl", false)

	if tc.IsTLSClusterEnabled() {
		config.Set("security.cluster-ssl-ca", path.Join(clusterCertPath, tlsSecretRootCAKey))
		config.Set("security.cluster-ssl-cert", path.Join(clusterCertPath, corev1.TLSCertKey))
		config.Set("security.cluster-ssl-key", path.Join(clusterCertPath, corev1.TLSPrivateKeyKey))
	}
	if tc.Spec.TiDB.IsTLSClientEnabled() {
		config.Set("security.ssl-ca", path.Join(serverCertPath, tlsSecretRootCAKey))
		config.Set("security.ssl-cert", path.Join(serverCertPath, corev1.TLSCertKey))
		config.Set("security.ssl-key", path.Join(serverCertPath, corev1.TLSPrivateKeyKey))
	}
	confText, err := config.MarshalTOML()
	if err != nil {
		return nil
	}
	data := map[string]string{
		"config-file": string(confText),
	}
	name := getExtraAccessCMName(tc.GetName())

	return &corev1.ConfigMap{
		ObjectMeta: metav1.ObjectMeta{
			Name:      name,
			Namespace: tc.GetNamespace(),
		},
		Data: data,
	}
}

func newExtraSecret(tc *v1alpha1.TidbCluster) *corev1.Secret {
	return &corev1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Name:      getExtraAccessSecretName(tc.GetName()),
			Namespace: tc.GetNamespace(),
		},
		StringData: map[string]string{
			"password": "",
		},
	}
}

func newExtraAccessSvc(tc *v1alpha1.TidbCluster) *corev1.Service {
	ns := tc.GetNamespace()
	tcName := tc.GetName()
	svcName := getExtraAccessSvcName(tcName)

	return &corev1.Service{
		ObjectMeta: metav1.ObjectMeta{
			Name:      svcName,
			Namespace: ns,
		},
		Spec: corev1.ServiceSpec{
			Ports: []corev1.ServicePort{
				{
					Name:       "service",
					Port:       4000,
					TargetPort: intstr.FromInt(4000),
					Protocol:   corev1.ProtocolTCP,
				},
				{
					Name:       "status",
					Port:       10080,
					TargetPort: intstr.FromInt(10080),
					Protocol:   corev1.ProtocolTCP,
				},
			},
			Selector:                 extraAccessLabels(tc),
			PublishNotReadyAddresses: true,
		},
	}
}

func newExtraAccessDeployment(tc *v1alpha1.TidbCluster, prefixId string) *appsv1.Deployment {
	ns := tc.GetNamespace()
	tcName := tc.GetName()
	var replica int32 = 1
	svcName := getExtraAccessSvcName(tcName)
	baseTiDBSpec := tc.BaseTiDBSpec()

	volMounts := []corev1.VolumeMount{
		{Name: "config", ReadOnly: true, MountPath: "/etc/tidb"},
	}
	if tc.IsTLSClusterEnabled() {
		volMounts = append(volMounts, corev1.VolumeMount{
			Name: "tidb-tls", ReadOnly: true, MountPath: clusterCertPath,
		})
	}
	if tc.Spec.TiDB.IsTLSClientEnabled() {
		volMounts = append(volMounts, corev1.VolumeMount{
			Name: "tidb-server-tls", ReadOnly: true, MountPath: serverCertPath,
		})
	}
	vols := []corev1.Volume{
		{
			Name: "config", VolumeSource: corev1.VolumeSource{
				ConfigMap: &corev1.ConfigMapVolumeSource{
					LocalObjectReference: corev1.LocalObjectReference{
						Name: getExtraAccessCMName(tc.GetName()),
					},
					Items: []corev1.KeyToPath{{Key: "config-file", Path: "tidb.toml"}},
				},
			},
		},
	}
	if tc.Spec.TiDB.IsTLSClientEnabled() {
		secretName := tidbServerSecretName(tcName)
		vols = append(vols, corev1.Volume{
			Name: "tidb-server-tls", VolumeSource: corev1.VolumeSource{
				Secret: &corev1.SecretVolumeSource{
					SecretName: secretName,
				},
			},
		})
	}
	if tc.IsTLSClusterEnabled() {
		vols = append(vols, corev1.Volume{
			Name: "tidb-tls", VolumeSource: corev1.VolumeSource{
				Secret: &corev1.SecretVolumeSource{
					SecretName: tidbClusterComponentSecretName(tc.Name, cloudv1.ComponentTiDB),
				},
			},
		})
	}

	waitScript := fmt.Sprintf(`trap exit TERM
          host=%s-tidb
          port=%d
          while true; do
            nc -zv -w 3 $host $port
            if [ $? -eq 0 ]; then
              break
            else
              echo "info: failed to connect to $host:$port, sleep 1 second then retry"
              sleep 1
            fi
          done
          echo "info: successfully connected to $host:$port, able to initialize TiDB now"`, tcName, tc.Spec.TiDB.GetServicePort())
	sysctls := "sysctl -w"
	initContainers := []corev1.Container{
		{
			Name:            "wait",
			Image:           config.GetBusyboxImage(),
			ImagePullPolicy: corev1.PullIfNotPresent,
			Command: []string{
				"sh",
				"-c",
				waitScript,
			},
		},
	}

	if baseTiDBSpec.Annotations() != nil {
		init, ok := baseTiDBSpec.Annotations()[consts.AnnSysctlInit]
		if ok && (init == consts.AnnSysctlInitVal) {
			if baseTiDBSpec.PodSecurityContext() != nil && len(baseTiDBSpec.PodSecurityContext().Sysctls) > 0 {
				for _, sysctl := range baseTiDBSpec.PodSecurityContext().Sysctls {
					sysctls += fmt.Sprintf(" %s=%s", sysctl.Name, sysctl.Value)
				}
				privileged := true
				initContainers = append(initContainers, corev1.Container{
					Name:  "init",
					Image: tc.HelperImage(),
					Command: []string{
						"sh",
						"-c",
						sysctls,
					},
					SecurityContext: &corev1.SecurityContext{
						Privileged: &privileged,
					},
				})
			}
		}
	}
	// Init container is only used for the case where allowed-unsafe-sysctls
	// cannot be enabled for kubelet, so clean the sysctl in Deployment
	// SecurityContext if init container is enabled
	podSecurityContext := baseTiDBSpec.PodSecurityContext().DeepCopy()
	if len(initContainers) > 0 {
		podSecurityContext.Sysctls = []corev1.Sysctl{}
	}

	var containers []corev1.Container
	if tc.Spec.TiDB.ShouldSeparateSlowLog() {
		// mount a shared volume and tail the slow log to STDOUT using a sidecar.
		vols = append(vols, corev1.Volume{
			Name: slowQueryLogVolumeName,
			VolumeSource: corev1.VolumeSource{
				EmptyDir: &corev1.EmptyDirVolumeSource{},
			},
		})
		volMounts = append(volMounts, corev1.VolumeMount{Name: slowQueryLogVolumeName, MountPath: slowQueryLogDir})
		containers = append(containers, corev1.Container{
			Name:            v1alpha1.ContainerSlowLogTailer.String(),
			Image:           tc.HelperImage(),
			ImagePullPolicy: tc.HelperImagePullPolicy(),
			VolumeMounts: []corev1.VolumeMount{
				{Name: slowQueryLogVolumeName, MountPath: slowQueryLogDir},
			},
			Command: []string{
				"sh",
				"-c",
				fmt.Sprintf("touch %s; tail -n0 -F %s;", slowQueryLogFile, slowQueryLogFile),
			},
		})
	}

	slowLogFileEnvVal := ""
	if tc.Spec.TiDB.ShouldSeparateSlowLog() {
		slowLogFileEnvVal = slowQueryLogFile
	}
	envs := []corev1.EnvVar{
		{
			Name:  "CLUSTER_NAME",
			Value: tc.GetName(),
		},
		{
			Name:  "TZ",
			Value: tc.Spec.Timezone,
		},
		{
			Name:  "BINLOG_ENABLED",
			Value: strconv.FormatBool(tc.IsTiDBBinlogEnabled()),
		},
		{
			Name:  "SLOW_LOG_FILE",
			Value: slowLogFileEnvVal,
		},
		{
			Name: "POD_NAME",
			ValueFrom: &corev1.EnvVarSource{
				FieldRef: &corev1.ObjectFieldSelector{
					FieldPath: "metadata.name",
				},
			},
		},
		{
			Name: "NAMESPACE",
			ValueFrom: &corev1.EnvVarSource{
				FieldRef: &corev1.ObjectFieldSelector{
					FieldPath: "metadata.namespace",
				},
			},
		},
		{
			Name:  "SERVICE_NAME",
			Value: svcName,
		},
	}

	args := []string{
		"--store=tikv",
		"--host=0.0.0.0",
		"--config=/etc/tidb/tidb.toml",
		fmt.Sprintf("--path=%s-pd:2379", tcName),
		fmt.Sprintf("--advertise-address=$(SERVICE_NAME).%s.svc", ns),
	}

	containers = append(containers, corev1.Container{
		Name:            v1alpha1.TiDBMemberType.String(),
		Image:           tc.TiDBImage(),
		Command:         []string{"/tidb-server"},
		Args:            args,
		ImagePullPolicy: baseTiDBSpec.ImagePullPolicy(),
		Ports: []corev1.ContainerPort{
			{
				Name:          "server",
				ContainerPort: int32(4000),
				Protocol:      corev1.ProtocolTCP,
			},
			{
				Name:          "status", // pprof, status, metrics
				ContainerPort: int32(10080),
				Protocol:      corev1.ProtocolTCP,
			},
		},
		VolumeMounts: volMounts,
		Env:          envs,
		ReadinessProbe: &corev1.Probe{
			ProbeHandler: corev1.ProbeHandler{
				TCPSocket: &corev1.TCPSocketAction{
					Port: intstr.FromInt(4000),
				},
			},
			InitialDelaySeconds: int32(10),
		},
	})

	podSpec := baseTiDBSpec.BuildPodSpec()
	podSpec.Containers = containers
	podSpec.Volumes = vols
	podSpec.SecurityContext = podSecurityContext
	podSpec.InitContainers = initContainers
	podSpec.Tolerations = []corev1.Toleration{
		{
			Effect:   "NoSchedule",
			Key:      "dedicated",
			Operator: "Equal",
			Value:    fmt.Sprintf("tidb-%s", cloudv1.ComponentAdminTiDB),
		},
	}
	podSpec.HostNetwork = false
	podSpec.NodeSelector = map[string]string{
		nodegroup.GardenerWorkerPrefixLabelName: nodegroup.NodeGroupNamePrefix(prefixId, string(cloudv1.ComponentAdminTiDB)),
	}
	tidbDeployment := &appsv1.Deployment{
		ObjectMeta: metav1.ObjectMeta{
			Name:      getExtraAccessName(tcName),
			Namespace: ns,
			Labels:    extraAccessLabels(tc),
		},
		Spec: appsv1.DeploymentSpec{
			Replicas: &replica,
			Selector: &metav1.LabelSelector{
				MatchLabels: extraAccessLabels(tc),
			},
			Template: corev1.PodTemplateSpec{
				ObjectMeta: metav1.ObjectMeta{
					Labels: extraAccessLabels(tc),
				},
				Spec: podSpec,
			},
		},
	}
	return tidbDeployment
}
