package cluster

import (
	"fmt"

	"github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1"
	"golang.org/x/mod/semver"
	corev1 "k8s.io/api/core/v1"

	"github.com/tidbcloud/infra-api/pkg/o11y/metric_agent"
)

const (
	tidbOOMRecordVolumeName = "tidb-oom-record"

	// Fixed oom record path for tidb of version in [6.1.0, 6.4.0)
	// It's encoded from "/tmp/{uid}_tidb/{{ base64({host}:{port}/{status_host}:{status_port}) }}/tmp-storage/record"
	// in which all the variables are fixed:
	//     uid = 0, host = 0.0.0.0, port = 4000, status_host = 0.0.0.0, status_port = 10080
	tidbOOMRecordContainerPathV1 = "/tmp/0_tidb/MC4wLjAuMDo0MDAwLzAuMC4wLjA6MTAwODA=/tmp-storage/record"
	// Fixed oom record path for tidb of version in [6.4.0, +inf)
	// It's a sub path in log directory. Due to not setting the log directory, it's placed to the root directory.
	tidbOOMRecordContainerPathV2 = "/oom_record"
	// "/var/tidb_oom_record/<tenant_id>/<project_id>/<cluster_id>"
	tidbOOMRecordHostPathTmp      = "/var/tidb_oom_record/%s/%s/%s"
	tidbOOMRecordMountSubPathExpr = "$(NAMESPACE)/$(POD_NAME)"

	tidbPlanReplayerVolumeName = "tidb-plan-replayer"
	// Fixed plan replayer path for tidb of version in [5.3.0, 6.4.0)
	// It's encoded from "/tmp/replayer/{pid}" in which pid is always 1.
	tidbPlanReplayerContainerPathV1 = "/tmp/replayer/1"
	// Fixed plan replayer path for tidb of version in [6.4.0, +inf)
	// It's a sub path in log directory. Due to not setting the log directory, it's placed to the root directory.
	tidbPlanReplayerContainerPathV2 = "/replayer"
	// "/var/tidb_plan_replayer/<tenant_id>/<project_id>/<cluster_id>"
	tidbPlanReplayerHostPathTmp      = "/var/tidb_plan_replayer/%s/%s/%s"
	tidbPlanReplayerMountSubPathExpr = "$(NAMESPACE)/$(POD_NAME)"
)

func (c *ClusterContext) MountOOMPathToHost(tc *v1alpha1.TidbCluster) {
	if c.checkMountOOMPathSupportVersionV1(tc) {
		c.mountOOMPathToHostV1(tc)
	} else if c.checkMountOOMPathSupportVersionV2(tc) {
		c.mountOOMPathToHostV2(tc)
	}
}

// [v6.1.0, v6.4.0)
func (c *ClusterContext) checkMountOOMPathSupportVersionV1(tc *v1alpha1.TidbCluster) bool {
	version := tc.Spec.Version
	return semver.Compare(version, "v6.1.0") >= 0 && semver.Compare(version, "v6.4.0") < 0
}

func (c *ClusterContext) mountOOMPathToHostV1(tc *v1alpha1.TidbCluster) {
	vol, mount := c.tidbOOMRecordVolumeAndMount(tidbOOMRecordContainerPathV1)
	overlayVolume(&tc.Spec.TiDB.AdditionalVolumes, vol)
	overlayVolumeMount(&tc.Spec.TiDB.AdditionalVolumeMounts, mount)
}

// [v6.4.0, +inf)
func (c *ClusterContext) checkMountOOMPathSupportVersionV2(tc *v1alpha1.TidbCluster) bool {
	version := tc.Spec.Version
	return semver.Compare(version, "v6.4.0") >= 0
}

func (c *ClusterContext) mountOOMPathToHostV2(tc *v1alpha1.TidbCluster) {
	vol, mount := c.tidbOOMRecordVolumeAndMount(tidbOOMRecordContainerPathV2)
	overlayVolume(&tc.Spec.TiDB.AdditionalVolumes, vol)
	overlayVolumeMount(&tc.Spec.TiDB.AdditionalVolumeMounts, mount)
}

func (c *ClusterContext) tidbOOMRecordVolumeAndMount(containerPath string) (corev1.Volume, corev1.VolumeMount) {
	tenantID, projectID, clusterID := metric_agent.GetIDsFromClusterCRD(c.Target, c.Network.Name)

	hostPathType := corev1.HostPathDirectoryOrCreate
	return corev1.Volume{
			Name: tidbOOMRecordVolumeName,
			VolumeSource: corev1.VolumeSource{
				HostPath: &corev1.HostPathVolumeSource{
					Path: fmt.Sprintf(tidbOOMRecordHostPathTmp, tenantID, projectID, clusterID),
					Type: &hostPathType,
				},
			},
		}, corev1.VolumeMount{
			Name:        tidbOOMRecordVolumeName,
			MountPath:   containerPath,
			SubPathExpr: tidbOOMRecordMountSubPathExpr,
		}
}

func (c *ClusterContext) MountPlanReplayerPathToHost(tc *v1alpha1.TidbCluster) {
	if c.checkMountPlanReplayerSupportVersionV1(tc) {
		c.mountPlanReplayerPathToHostV1(tc)
	} else if c.checkMountPlanReplayerSupportVersionV2(tc) {
		c.mountPlanReplayerPathToHostV2(tc)
	}
}

// [v5.3.0, v6.4.0)
func (c *ClusterContext) checkMountPlanReplayerSupportVersionV1(tc *v1alpha1.TidbCluster) bool {
	version := tc.Spec.Version
	return semver.Compare(version, "v5.3.0") >= 0 && semver.Compare(version, "v6.4.0") < 0
}

// [v6.4.0, +inf)
func (c *ClusterContext) checkMountPlanReplayerSupportVersionV2(tc *v1alpha1.TidbCluster) bool {
	version := tc.Spec.Version
	return semver.Compare(version, "v6.4.0") >= 0
}

func (c *ClusterContext) mountPlanReplayerPathToHostV1(tc *v1alpha1.TidbCluster) {
	vol, mount := c.tidbPlanReplayerVolumeAndMount(tidbPlanReplayerContainerPathV1)
	overlayVolume(&tc.Spec.TiDB.AdditionalVolumes, vol)
	overlayVolumeMount(&tc.Spec.TiDB.AdditionalVolumeMounts, mount)
}

func (c *ClusterContext) mountPlanReplayerPathToHostV2(tc *v1alpha1.TidbCluster) {
	vol, mount := c.tidbPlanReplayerVolumeAndMount(tidbPlanReplayerContainerPathV2)
	overlayVolume(&tc.Spec.TiDB.AdditionalVolumes, vol)
	overlayVolumeMount(&tc.Spec.TiDB.AdditionalVolumeMounts, mount)
}

func (c *ClusterContext) tidbPlanReplayerVolumeAndMount(containerPath string) (corev1.Volume, corev1.VolumeMount) {
	tenantID, projectID, clusterID := metric_agent.GetIDsFromClusterCRD(c.Target, c.Network.Name)

	hostPathType := corev1.HostPathDirectoryOrCreate
	return corev1.Volume{
			Name: tidbPlanReplayerVolumeName,
			VolumeSource: corev1.VolumeSource{
				HostPath: &corev1.HostPathVolumeSource{
					Path: fmt.Sprintf(tidbPlanReplayerHostPathTmp, tenantID, projectID, clusterID),
					Type: &hostPathType,
				},
			},
		}, corev1.VolumeMount{
			Name:        tidbPlanReplayerVolumeName,
			MountPath:   containerPath,
			SubPathExpr: tidbPlanReplayerMountSubPathExpr,
		}
}

func overlayVolume(volumes *[]corev1.Volume, vol corev1.Volume) {
	found := false
	for i := range *volumes {
		cur := &(*volumes)[i]
		if cur.Name == vol.Name {
			(*volumes)[i] = vol
			found = true
		}
	}
	if !found {
		*volumes = append(*volumes, vol)
	}
}

func overlayVolumeMount(mounts *[]corev1.VolumeMount, mount corev1.VolumeMount) {
	found := false
	for i := range *mounts {
		cur := &(*mounts)[i]
		if cur.Name == mount.Name {
			(*mounts)[i] = mount
			found = true
		}
	}
	if !found {
		*mounts = append(*mounts, mount)
	}
}
