package handler

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/goodrain/rainbond/api/model"
	dbmodel "github.com/goodrain/rainbond/db/model"
	"github.com/sirupsen/logrus"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"path"
	"sigs.k8s.io/yaml"
	"strconv"
	"strings"
)

// PodTemplateSpecResource -
func PodTemplateSpecResource(parameter model.YamlResourceParameter, volumeClaimTemplate []corev1.PersistentVolumeClaim, clientset *kubernetes.Clientset) {
	logrus.Infof("into function PodTemplateSpecResource")
	//Port
	var ps []model.PortManagement
	NameAndPort := make(map[string]int32)
	var po int32
	for _, port := range parameter.Template.Spec.Containers[0].Ports {
		NameAndPort[port.Name] = port.ContainerPort
		switch string(port.Protocol) {
		case "UDP", "udp":
			po = port.ContainerPort
			ps = append(ps, model.PortManagement{
				Name:     port.Name,
				Port:     port.ContainerPort,
				Protocol: "udp",
				Inner:    false,
				Outer:    false,
			})
		case "HTTP", "http":
			po = port.ContainerPort
			ps = append(ps, model.PortManagement{
				Name:     port.Name,
				Port:     port.ContainerPort,
				Protocol: "http",
				Inner:    false,
				Outer:    false,
			})
		default:
			po = port.ContainerPort
			ps = append(ps, model.PortManagement{
				Name:     port.Name,
				Port:     port.ContainerPort,
				Protocol: "tcp",
				Inner:    false,
				Outer:    false,
			})
		}
		logrus.Warningf("Transport protocol type not recognized%v", port.Protocol)
	}

	//ENV
	var envs []model.ENVManagement
	for i := 0; i < len(parameter.Template.Spec.Containers[0].Env); i++ {
		if cm := parameter.Template.Spec.Containers[0].Env[i].ValueFrom; cm == nil {
			envs = append(envs, model.ENVManagement{
				ENVKey:     parameter.Template.Spec.Containers[0].Env[i].Name,
				ENVValue:   parameter.Template.Spec.Containers[0].Env[i].Value,
				ENVExplain: "",
			})
			parameter.Template.Spec.Containers[0].Env = append(parameter.Template.Spec.Containers[0].Env[:i], parameter.Template.Spec.Containers[0].Env[i+1:]...)
		}
	}

	//Configs
	var configs []model.ConfigManagement
	//这一块是处理配置文件
	//配置文件的名字最终都是configmap里面的key值。
	//volume在被挂载后存在四种情况
	//第一种是volume存在items,volumeMount的SubPath不等于空。路径直接是volumeMount里面的mountPath。
	//第二种是volume存在items,volumeMount的SubPath等于空。路径则变成volumeMount里面的mountPath拼接上items里面每一个元素的key值。
	//第三种是volume不存在items,volumeMount的SubPath不等于空。路径直接是volumeMount里面的mountPath。
	//第四种是volume不存在items,volumeMount的SubPath等于空。路径则变成volumeMount里面的mountPath拼接上configmap资源里面每一个元素的key值
	cmMap := make(map[string]corev1.ConfigMap)
	cmList, err := clientset.CoreV1().ConfigMaps(parameter.Namespace).List(context.Background(), metav1.ListOptions{})
	if err != nil {
		logrus.Errorf("Failed to get ConfigMap%v", err)
	}
	cmList.Items = append(cmList.Items, parameter.CMs...)
	for _, cm := range cmList.Items {
		cmMap[cm.Name] = cm
	}
	var volumeAttributes []corev1.Volume
	volumeMountAttributes := parameter.Template.Spec.Containers[0].VolumeMounts
	for _, volume := range parameter.Template.Spec.Volumes {
		if volume.ConfigMap != nil && err == nil {
			cm, _ := cmMap[volume.ConfigMap.Name]
			cmData := cm.Data
			isLog := true
			for i, volumeMount := range volumeMountAttributes {
				if volume.Name != volumeMount.Name {
					continue
				}
				volumeMountAttributes = append(volumeMountAttributes[:i], volumeMountAttributes[i+1:]...)
				if len(volumeMountAttributes) == 0 {
					volumeMountAttributes = nil
				}
				isLog = false
				if volume.ConfigMap.Items != nil {
					if volumeMount.SubPath != "" {
						configName := ""
						var itemMode int32
						for _, item := range volume.ConfigMap.Items {
							if item.Path == volumeMount.SubPath {
								configName = item.Key
								if item.Mode != nil {
									itemMode = *item.Mode
								}
								break
							}
						}
						mode8 := strconv.FormatInt(int64(itemMode), 8)
						mode, err := strconv.ParseInt(mode8, 10, 32)
						if err != nil || mode == 0 {
							mode = 755
						}
						configs = append(configs, model.ConfigManagement{
							ConfigName:  configName,
							ConfigPath:  volumeMount.MountPath,
							ConfigValue: cmData[configName],
							Mode:        int32(mode),
						})
					} else {
						p := volumeMount.MountPath
						for _, item := range volume.ConfigMap.Items {
							p := path.Join(p, item.Path)
							var itemMode int32
							if item.Mode != nil {
								itemMode = *item.Mode
							}
							mode8 := strconv.FormatInt(int64(itemMode), 8)
							mode, err := strconv.ParseInt(mode8, 10, 32)
							if err != nil || mode == 0 {
								mode = 755
							}
							configs = append(configs, model.ConfigManagement{
								ConfigName:  item.Key,
								ConfigPath:  p,
								ConfigValue: cmData[item.Key],
								Mode:        int32(mode),
							})
						}
					}
				} else {
					var mode10 int32
					if volume.ConfigMap.DefaultMode != nil {
						mode10 = *volume.ConfigMap.DefaultMode
					}
					mode8 := strconv.FormatInt(int64(mode10), 8)
					mode, err := strconv.ParseInt(mode8, 10, 32)
					if err != nil || mode == 0 {
						mode = 755
					}
					if volumeMount.SubPath != "" {
						configs = append(configs, model.ConfigManagement{
							ConfigName:  volumeMount.SubPath,
							ConfigPath:  volumeMount.MountPath,
							ConfigValue: cmData[volumeMount.SubPath],
							Mode:        int32(mode),
						})
					} else {
						mountPath := volumeMount.MountPath
						for key, val := range cmData {
							volumeMountPath := path.Join(mountPath, key)
							configs = append(configs, model.ConfigManagement{
								ConfigName:  key,
								ConfigPath:  volumeMountPath,
								ConfigValue: val,
								Mode:        int32(mode),
							})
						}
					}
				}
				break
			}
			if isLog {
				volumeAttributes = append(volumeAttributes, volume)
				logrus.Warningf("configmap type resource %v is not mounted in volumemount", volume.ConfigMap.Name)
			}
			continue
		}
		volumeAttributes = append(volumeAttributes, volume)
	}

	//TelescopicManagement
	HPAList, err := clientset.AutoscalingV1().HorizontalPodAutoscalers(parameter.Namespace).List(context.Background(), metav1.ListOptions{})
	if err != nil {
		logrus.Errorf("failed to get HorizontalPodAutoscalers list:%v", err)
	}
	HPAList.Items = append(HPAList.Items, parameter.HPAs...)
	var t model.TelescopicManagement
	//这一块就是自动伸缩的对应解析,
	//需要注意的一点是hpa的cpu和memory的阈值设置是通过Annotations["autoscaling.alpha.kubernetes.io/metrics"]字段设置
	//而且它的返回值是个json字符串所以设置了一个结构体进行解析。
	for _, hpa := range HPAList.Items {
		if (hpa.Spec.ScaleTargetRef.Kind != model.Deployment && hpa.Spec.ScaleTargetRef.Kind != model.StateFulSet) || hpa.Spec.ScaleTargetRef.Name != parameter.Name {
			t.Enable = false
			continue
		}
		t.Enable = true
		t.MinReplicas = *hpa.Spec.MinReplicas
		t.MaxReplicas = hpa.Spec.MaxReplicas
		var cpuormemorys []*dbmodel.TenantServiceAutoscalerRuleMetrics
		cpuUsage := hpa.Spec.TargetCPUUtilizationPercentage
		if cpuUsage != nil {
			cpuormemorys = append(cpuormemorys, &dbmodel.TenantServiceAutoscalerRuleMetrics{
				MetricsType:       "resource_metrics",
				MetricsName:       "cpu",
				MetricTargetType:  "utilization",
				MetricTargetValue: int(*cpuUsage),
			})
		}
		CPUAndMemoryJSON, ok := hpa.Annotations["autoscaling.alpha.kubernetes.io/metrics"]
		if ok {
			type com struct {
				T        string `json:"type"`
				Resource map[string]interface{}
			}
			var c []com
			err := json.Unmarshal([]byte(CPUAndMemoryJSON), &c)
			if err != nil {
				logrus.Errorf("autoscaling.alpha.kubernetes.io/metrics parsing failed:%v", err)
			}

			for _, cpuormemory := range c {
				switch cpuormemory.Resource["name"] {
				case "cpu":
					cpu := fmt.Sprint(cpuormemory.Resource["targetAverageValue"])
					cpuUnit := cpu[len(cpu)-1:]
					var cpuUsage int
					if cpuUnit == "m" {
						cpuUsage, _ = strconv.Atoi(cpu[:len(cpu)-1])
					}
					if cpuUnit == "g" || cpuUnit == "G" {
						cpuUsage, _ = strconv.Atoi(cpu[:len(cpu)-1])
						cpuUsage = cpuUsage * 1024
					}
					cpuormemorys = append(cpuormemorys, &dbmodel.TenantServiceAutoscalerRuleMetrics{
						MetricsType:       "resource_metrics",
						MetricsName:       "cpu",
						MetricTargetType:  "average_value",
						MetricTargetValue: cpuUsage,
					})
				case "memory":
					memory := fmt.Sprint(cpuormemory.Resource["targetAverageValue"])
					memoryUnit := memory[:len(memory)-1]
					var MemoryUsage int
					if memoryUnit == "m" {
						MemoryUsage, _ = strconv.Atoi(memory[:len(memory)-1])
					}
					if memoryUnit == "g" || memoryUnit == "G" {
						MemoryUsage, _ = strconv.Atoi(memory[:len(memory)-1])
						MemoryUsage = MemoryUsage * 1024
					}
					cpuormemorys = append(cpuormemorys, &dbmodel.TenantServiceAutoscalerRuleMetrics{
						MetricsType:       "resource_metrics",
						MetricsName:       "cpu",
						MetricTargetType:  "average_value",
						MetricTargetValue: MemoryUsage,
					})
				}

			}
		}
		t.CPUOrMemory = cpuormemorys
	}

	//HealthyCheckManagement
	var hcm model.HealthyCheckManagement
	livenessProbe := parameter.Template.Spec.Containers[0].LivenessProbe
	if livenessProbe != nil {
		var httpHeaders []string
		if livenessProbe.HTTPGet != nil {
			for _, httpHeader := range livenessProbe.HTTPGet.HTTPHeaders {
				nv := httpHeader.Name + "=" + httpHeader.Value
				httpHeaders = append(httpHeaders, nv)
			}
			hcm.DetectionMethod = strings.ToLower(string(livenessProbe.HTTPGet.Scheme))
			if hcm.DetectionMethod == "" {
				hcm.DetectionMethod = "tcp"
				if len(httpHeaders) > 0 {
					hcm.DetectionMethod = "http"
				}
			}
			hcm.Path = livenessProbe.HTTPGet.Path
			hcm.Port = int(livenessProbe.HTTPGet.Port.IntVal)
			if hcm.Port == 0 {
				hcm.Port = int(NameAndPort[livenessProbe.HTTPGet.Port.StrVal])
			}
		}
		if hcm.Port == 0 {
			hcm.Port = int(po)
		}
		hcm.Status = 1
		if livenessProbe.Exec != nil {
			hcm.Command = strings.Join(livenessProbe.Exec.Command, " ")
		}
		hcm.HTTPHeader = strings.Join(httpHeaders, ",")
		hcm.Mode = "liveness"
		hcm.InitialDelaySecond = int(livenessProbe.InitialDelaySeconds)
		hcm.PeriodSecond = int(livenessProbe.PeriodSeconds)
		hcm.TimeoutSecond = int(livenessProbe.TimeoutSeconds)
		hcm.FailureThreshold = int(livenessProbe.FailureThreshold)
		hcm.SuccessThreshold = int(livenessProbe.SuccessThreshold)
	} else {
		readinessProbe := parameter.Template.Spec.Containers[0].ReadinessProbe
		if readinessProbe != nil {
			var httpHeaders []string
			if readinessProbe.HTTPGet != nil {
				for _, httpHeader := range readinessProbe.HTTPGet.HTTPHeaders {
					nv := httpHeader.Name + "=" + httpHeader.Value
					httpHeaders = append(httpHeaders, nv)
				}
				hcm.DetectionMethod = strings.ToLower(string(readinessProbe.HTTPGet.Scheme))
				if hcm.DetectionMethod == "" {
					hcm.DetectionMethod = "tcp"
					if len(httpHeaders) > 0 {
						hcm.DetectionMethod = "http"
					}
				}
				hcm.Path = readinessProbe.HTTPGet.Path
				hcm.Port = int(readinessProbe.HTTPGet.Port.IntVal)
				if hcm.Port == 0 {
					hcm.Port = int(NameAndPort[livenessProbe.HTTPGet.Port.StrVal])
				}
				if hcm.Port == 0 {
					hcm.Port = int(po)
				}
			}
			if hcm.Port == 0 {
				hcm.Port = int(po)
			}
			hcm.Status = 1
			hcm.Mode = "readiness"
			if readinessProbe.Exec != nil {
				hcm.Command = strings.Join(readinessProbe.Exec.Command, " ")
			}
			hcm.HTTPHeader = strings.Join(httpHeaders, ",")
			hcm.InitialDelaySecond = int(readinessProbe.InitialDelaySeconds)
			hcm.PeriodSecond = int(readinessProbe.PeriodSeconds)
			hcm.TimeoutSecond = int(readinessProbe.TimeoutSeconds)
			hcm.FailureThreshold = int(readinessProbe.FailureThreshold)
			hcm.SuccessThreshold = int(readinessProbe.SuccessThreshold)
		}
	}

	var attributes []*dbmodel.ComponentK8sAttributes
	if parameter.Template.Spec.Containers[0].Env != nil && len(parameter.Template.Spec.Containers[0].Env) > 0 {
		envYaml, err := ObjectToJSONORYaml("yaml", parameter.Template.Spec.Containers[0].Env)
		if err != nil {
			logrus.Errorf("pod %v template env transformation yaml failure: %v", parameter.Name, err)
		}
		envAttributes := &dbmodel.ComponentK8sAttributes{
			Name:           dbmodel.K8sAttributeNameENV,
			SaveType:       "yaml",
			AttributeValue: envYaml,
		}
		attributes = append(attributes, envAttributes)
	}
	if volumeAttributes != nil {
		volumesYaml, err := ObjectToJSONORYaml("yaml", volumeAttributes)
		if err != nil {
			logrus.Errorf("pod %v template volume transformation yaml failure: %v", parameter.Name, err)
		}
		volumesAttributes := &dbmodel.ComponentK8sAttributes{
			Name:           dbmodel.K8sAttributeNameVolumes,
			SaveType:       "yaml",
			AttributeValue: volumesYaml,
		}
		attributes = append(attributes, volumesAttributes)

	}
	if volumeMountAttributes != nil {
		volumeMountsYaml, err := ObjectToJSONORYaml("yaml", volumeMountAttributes)
		if err != nil {
			logrus.Errorf("pod %v template volumemount transformation yaml failure: %v", parameter.Name, err)
		}
		volumeMountsAttributes := &dbmodel.ComponentK8sAttributes{
			Name:           dbmodel.K8sAttributeNameVolumeMounts,
			SaveType:       "yaml",
			AttributeValue: volumeMountsYaml,
		}
		attributes = append(attributes, volumeMountsAttributes)
	}
	if parameter.Template.Spec.ServiceAccountName != "" {
		serviceAccountAttributes := &dbmodel.ComponentK8sAttributes{
			Name:           dbmodel.K8sAttributeNameServiceAccountName,
			SaveType:       "string",
			AttributeValue: parameter.Template.Spec.ServiceAccountName,
		}
		attributes = append(attributes, serviceAccountAttributes)
	}
	if parameter.Template.Labels != nil {
		labelsJSON, err := ObjectToJSONORYaml("json", parameter.Template.Labels)
		if err != nil {
			logrus.Errorf("pod %v template label transformation json failure: %v", parameter.Name, err)
		} else {
			labelsAttributes := &dbmodel.ComponentK8sAttributes{
				Name:           dbmodel.K8sAttributeNameLabels,
				SaveType:       "json",
				AttributeValue: labelsJSON,
			}
			attributes = append(attributes, labelsAttributes)
		}
	}

	if parameter.Template.Annotations != nil {
		annotationsJSON, err := ObjectToJSONORYaml("json", parameter.Template.Annotations)
		if err != nil {
			logrus.Errorf("pod %v template annotations transformation json failure: %v", parameter.Name, err)
		} else {
			labelsAttributes := &dbmodel.ComponentK8sAttributes{
				Name:           dbmodel.K8sAttributeNameAnnotations,
				SaveType:       "json",
				AttributeValue: annotationsJSON,
			}
			attributes = append(attributes, labelsAttributes)
		}
	}

	if parameter.Template.Spec.NodeSelector != nil {
		NodeSelectorJSON, err := ObjectToJSONORYaml("json", parameter.Template.Spec.NodeSelector)
		if err != nil {
			logrus.Errorf("pod %v template nodeselector transformation json failure: %v", parameter.Name, err)
		}
		nodeSelectorAttributes := &dbmodel.ComponentK8sAttributes{
			Name:           dbmodel.K8sAttributeNameNodeSelector,
			SaveType:       "json",
			AttributeValue: NodeSelectorJSON,
		}
		attributes = append(attributes, nodeSelectorAttributes)
	}
	if volumeClaimTemplate != nil {
		volumeClaimTemplateYaml, err := ObjectToJSONORYaml("yaml", volumeClaimTemplate)
		if err != nil {
			logrus.Errorf("pod %v template volumeClaimTemplate transformation yaml failure: %v", parameter.Name, err)
		}
		vctAttributes := &dbmodel.ComponentK8sAttributes{
			Name:           dbmodel.K8sAttributeNameVolumeClaimTemplate,
			SaveType:       "yaml",
			AttributeValue: volumeClaimTemplateYaml,
		}
		attributes = append(attributes, vctAttributes)
	}

	if parameter.Template.Spec.Containers[0].EnvFrom != nil {
		envFromYaml, err := ObjectToJSONORYaml("yaml", parameter.Template.Spec.Containers[0].EnvFrom)
		if err != nil {
			logrus.Errorf("pod %v template envFrom transformation yaml failure: %v", parameter.Name, err)
		} else {
			envFromAttributes := &dbmodel.ComponentK8sAttributes{
				Name:           dbmodel.K8sAttributeNameENVFromSource,
				SaveType:       "yaml",
				AttributeValue: envFromYaml,
			}
			attributes = append(attributes, envFromAttributes)
		}
	}

	if parameter.Template.Spec.Tolerations != nil {
		tolerationsYaml, err := ObjectToJSONORYaml("yaml", parameter.Template.Spec.Tolerations)
		if err != nil {
			logrus.Errorf("pod %v template Tolerations transformation yaml failure: %v", parameter.Name, err)
		}
		tolerationsAttributes := &dbmodel.ComponentK8sAttributes{
			Name:           dbmodel.K8sAttributeNameTolerations,
			SaveType:       "yaml",
			AttributeValue: tolerationsYaml,
		}
		attributes = append(attributes, tolerationsAttributes)
	}
	if parameter.Template.Spec.Affinity != nil {
		affinityYaml, err := ObjectToJSONORYaml("yaml", parameter.Template.Spec.Affinity)
		if err != nil {
			logrus.Errorf("pod %v template Affinity transformation yaml failure: %v", parameter.Name, err)
		}
		affinityAttributes := &dbmodel.ComponentK8sAttributes{
			Name:           dbmodel.K8sAttributeNameAffinity,
			SaveType:       "yaml",
			AttributeValue: affinityYaml,
		}
		attributes = append(attributes, affinityAttributes)
	}
	if securityContext := parameter.Template.Spec.Containers[0].SecurityContext; securityContext != nil && securityContext.Privileged != nil {
		privilegedAttributes := &dbmodel.ComponentK8sAttributes{
			Name:           dbmodel.K8sAttributeNamePrivileged,
			SaveType:       "string",
			AttributeValue: strconv.FormatBool(*securityContext.Privileged),
		}
		attributes = append(attributes, privilegedAttributes)
	}

	*parameter.ComponentsCR = append(*parameter.ComponentsCR, model.ConvertResource{
		ComponentsName:                   parameter.Name,
		BasicManagement:                  parameter.Basic,
		PortManagement:                   ps,
		ENVManagement:                    envs,
		ConfigManagement:                 configs,
		TelescopicManagement:             t,
		HealthyCheckManagement:           hcm,
		ComponentK8sAttributesManagement: attributes,
	})
}

// ObjectToJSONORYaml changeType true is json / yaml
func ObjectToJSONORYaml(changeType string, data interface{}) (string, error) {
	if data == nil {
		return "", nil
	}
	dataJSON, err := json.Marshal(data)
	if err != nil {
		return "", fmt.Errorf("json serialization failed err:%v", err)
	}
	if changeType == "json" {
		return string(dataJSON), nil
	}
	dataYaml, err := yaml.JSONToYAML(dataJSON)
	if err != nil {
		return "", fmt.Errorf("yaml serialization failed err:%v", err)
	}
	return string(dataYaml), nil
}