package handler

import (
	"github.com/goodrain/rainbond/api/model"
	"github.com/goodrain/rainbond/api/util"
	"github.com/sirupsen/logrus"
	"golang.org/x/net/context"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

//GetNamespaceSource Get all resources in the current namespace
func (c *clusterAction) GetNamespaceSource(ctx context.Context, content string, namespace string) (map[string]model.LabelResource, *util.APIHandleError) {
	logrus.Infof("GetNamespaceSource function begin")
	//存储workloads们的ConfigMap
	cmsMap := make(map[string][]string)
	//存储workloads们的secrets
	secretsMap := make(map[string][]string)
	deployments, cmMap, secretMap := c.getResourceName(context.Background(), namespace, content, model.Deployment)
	if len(cmsMap) != 0 {
		cmsMap = MergeMap(cmMap, cmsMap)
	}
	if len(secretMap) != 0 {
		secretsMap = MergeMap(secretMap, secretsMap)
	}
	jobs, cmMap, secretMap := c.getResourceName(ctx, namespace, content, model.Job)
	if len(cmsMap) != 0 {
		cmsMap = MergeMap(cmMap, cmsMap)
	}
	if len(secretMap) != 0 {
		secretsMap = MergeMap(secretMap, secretsMap)
	}
	cronJobs, cmMap, secretMap := c.getResourceName(ctx, namespace, content, model.CronJob)
	if len(cmsMap) != 0 {
		cmsMap = MergeMap(cmMap, cmsMap)
	}
	if len(secretMap) != 0 {
		secretsMap = MergeMap(secretMap, secretsMap)
	}
	stateFulSets, cmMap, secretMap := c.getResourceName(ctx, namespace, content, model.StateFulSet)
	if len(cmsMap) != 0 {
		cmsMap = MergeMap(cmMap, cmsMap)
	}
	if len(secretMap) != 0 {
		secretsMap = MergeMap(secretMap, secretsMap)
	}
	processWorkloads := model.LabelWorkloadsResourceProcess{
		Deployments:  deployments,
		Jobs:         jobs,
		CronJobs:     cronJobs,
		StateFulSets: stateFulSets,
	}
	services, _, _ := c.getResourceName(ctx, namespace, content, model.Service)
	pvc, _, _ := c.getResourceName(ctx, namespace, content, model.PVC)
	ingresses, _, _ := c.getResourceName(ctx, namespace, content, model.Ingress)
	networkPolicies, _, _ := c.getResourceName(ctx, namespace, content, model.NetworkPolicy)
	cms, _, _ := c.getResourceName(ctx, namespace, content, model.ConfigMap)
	secrets, _, _ := c.getResourceName(ctx, namespace, content, model.Secret)
	serviceAccounts, _, _ := c.getResourceName(ctx, namespace, content, model.ServiceAccount)
	roleBindings, _, _ := c.getResourceName(ctx, namespace, content, model.RoleBinding)
	horizontalPodAutoscalers, _, _ := c.getResourceName(ctx, namespace, content, model.HorizontalPodAutoscaler)
	roles, _, _ := c.getResourceName(ctx, namespace, content, model.Role)
	processOthers := model.LabelOthersResourceProcess{
		Services:                 services,
		PVC:                      pvc,
		Ingresses:                ingresses,
		NetworkPolicies:          networkPolicies,
		ConfigMaps:               MergeMap(cmsMap, cms),
		Secrets:                  MergeMap(secretsMap, secrets),
		ServiceAccounts:          serviceAccounts,
		RoleBindings:             roleBindings,
		HorizontalPodAutoscalers: horizontalPodAutoscalers,
		Roles:                    roles,
	}
	labelResource := resourceProcessing(processWorkloads, processOthers)
	logrus.Infof("GetNamespaceSource function end")
	return labelResource, nil
}

//resourceProcessing 将处理好的资源类型数据格式再加工成可作为返回值的数据。
func resourceProcessing(processWorkloads model.LabelWorkloadsResourceProcess, processOthers model.LabelOthersResourceProcess) map[string]model.LabelResource {
	labelResource := make(map[string]model.LabelResource)
	for label, deployments := range processWorkloads.Deployments {
		if val, ok := labelResource[label]; ok {
			val.Workloads.Deployments = deployments
			labelResource[label] = val
			continue
		}
		labelResource[label] = model.LabelResource{
			Workloads: model.WorkLoadsResource{
				Deployments: deployments,
			},
		}
	}
	for label, jobs := range processWorkloads.Jobs {
		if val, ok := labelResource[label]; ok {
			val.Workloads.Jobs = jobs
			labelResource[label] = val
			continue
		}
		labelResource[label] = model.LabelResource{
			Workloads: model.WorkLoadsResource{
				Jobs: jobs,
			},
		}

	}
	for label, cronJobs := range processWorkloads.CronJobs {
		if val, ok := labelResource[label]; ok {
			val.Workloads.CronJobs = cronJobs
			labelResource[label] = val
			continue
		}
		labelResource[label] = model.LabelResource{
			Workloads: model.WorkLoadsResource{
				CronJobs: cronJobs,
			},
		}
	}
	for label, stateFulSets := range processWorkloads.StateFulSets {
		if val, ok := labelResource[label]; ok {
			val.Workloads.StateFulSets = stateFulSets
			labelResource[label] = val
			continue
		}
		labelResource[label] = model.LabelResource{
			Workloads: model.WorkLoadsResource{
				StateFulSets: stateFulSets,
			},
		}
	}
	for label, service := range processOthers.Services {
		if val, ok := labelResource[label]; ok {
			val.Others.Services = service
			labelResource[label] = val
			continue
		}
		labelResource[label] = model.LabelResource{
			Others: model.OtherResource{
				Services: service,
			},
		}

	}
	for label, pvc := range processOthers.PVC {
		if val, ok := labelResource[label]; ok {
			val.Others.PVC = pvc
			labelResource[label] = val
			continue
		}
		labelResource[label] = model.LabelResource{
			Others: model.OtherResource{
				PVC: pvc,
			},
		}

	}
	for label, ingresses := range processOthers.Ingresses {
		if val, ok := labelResource[label]; ok {
			val.Others.Ingresses = ingresses
			labelResource[label] = val
			continue
		}
		labelResource[label] = model.LabelResource{
			Others: model.OtherResource{
				Ingresses: ingresses,
			},
		}
	}
	for label, networkPolicies := range processOthers.NetworkPolicies {
		if val, ok := labelResource[label]; ok {
			val.Others.NetworkPolicies = networkPolicies
			labelResource[label] = val
			continue
		}
		labelResource[label] = model.LabelResource{
			Others: model.OtherResource{
				NetworkPolicies: networkPolicies,
			},
		}
	}
	for label, configMaps := range processOthers.ConfigMaps {
		if val, ok := labelResource[label]; ok {
			val.Others.ConfigMaps = configMaps
			labelResource[label] = val
			continue
		}
		labelResource[label] = model.LabelResource{
			Others: model.OtherResource{
				ConfigMaps: configMaps,
			},
		}
	}
	for label, secrets := range processOthers.Secrets {
		if val, ok := labelResource[label]; ok {
			val.Others.Secrets = secrets
			labelResource[label] = val
			continue
		}
		labelResource[label] = model.LabelResource{
			Others: model.OtherResource{
				Secrets: secrets,
			},
		}
	}
	for label, serviceAccounts := range processOthers.ServiceAccounts {
		if val, ok := labelResource[label]; ok {
			val.Others.ServiceAccounts = serviceAccounts
			labelResource[label] = val
			continue
		}
		labelResource[label] = model.LabelResource{
			Others: model.OtherResource{
				ServiceAccounts: serviceAccounts,
			},
		}
	}
	for label, roleBindings := range processOthers.RoleBindings {
		if val, ok := labelResource[label]; ok {
			val.Others.RoleBindings = roleBindings
			labelResource[label] = val
			continue
		}
		labelResource[label] = model.LabelResource{
			Others: model.OtherResource{
				RoleBindings: roleBindings,
			},
		}
	}
	for label, horizontalPodAutoscalers := range processOthers.HorizontalPodAutoscalers {
		if val, ok := labelResource[label]; ok {
			val.Others.HorizontalPodAutoscalers = horizontalPodAutoscalers
			labelResource[label] = val
			continue
		}
		labelResource[label] = model.LabelResource{
			Others: model.OtherResource{
				HorizontalPodAutoscalers: horizontalPodAutoscalers,
			},
		}
	}
	for label, roles := range processOthers.Roles {
		if val, ok := labelResource[label]; ok {
			val.Others.Roles = roles
			labelResource[label] = val
			continue
		}
		labelResource[label] = model.LabelResource{
			Others: model.OtherResource{
				Roles: roles,
			},
		}
	}
	return labelResource
}

//Resource -
type Resource struct {
	ObjectMeta metav1.ObjectMeta
	Template   corev1.PodTemplateSpec
}

//getResourceName 将指定资源类型按照【label名】:[]{资源名...}处理后返回
func (c *clusterAction) getResourceName(ctx context.Context, namespace string, content string, resourcesType string) (map[string][]string, map[string][]string, map[string][]string) {
	resourceName := make(map[string][]string)
	var tempResources []*Resource
	isWorkloads := false
	cmMap := make(map[string][]string)
	secretMap := make(map[string][]string)
	switch resourcesType {
	case model.Deployment:
		resources, err := c.clientset.AppsV1().Deployments(namespace).List(context.Background(), metav1.ListOptions{})
		if err != nil {
			logrus.Errorf("Failed to get Deployment list:%v", err)
			return nil, cmMap, secretMap
		}
		for _, dm := range resources.Items {
			tempResources = append(tempResources, &Resource{ObjectMeta: dm.ObjectMeta, Template: dm.Spec.Template})
		}
		isWorkloads = true
	case model.Job:
		resources, err := c.clientset.BatchV1().Jobs(namespace).List(context.Background(), metav1.ListOptions{})
		if err != nil {
			logrus.Errorf("Failed to get Job list:%v", err)
			return nil, cmMap, secretMap
		}
		for _, dm := range resources.Items {
			if dm.OwnerReferences != nil {
				if dm.OwnerReferences[0].Kind == model.CronJob {
					continue
				}
			}
			tempResources = append(tempResources, &Resource{ObjectMeta: dm.ObjectMeta, Template: dm.Spec.Template})
		}
		isWorkloads = true
	case model.CronJob:
		resources, err := c.clientset.BatchV1beta1().CronJobs(namespace).List(context.Background(), metav1.ListOptions{})
		if err != nil {
			logrus.Errorf("Failed to get CronJob list:%v", err)
			return nil, cmMap, secretMap
		}
		for _, dm := range resources.Items {
			tempResources = append(tempResources, &Resource{ObjectMeta: dm.ObjectMeta, Template: dm.Spec.JobTemplate.Spec.Template})
		}
		isWorkloads = true
	case model.StateFulSet:
		resources, err := c.clientset.AppsV1().StatefulSets(namespace).List(context.Background(), metav1.ListOptions{})
		if err != nil {
			logrus.Errorf("Failed to get StateFulSets list:%v", err)
			return nil, cmMap, secretMap
		}
		for _, dm := range resources.Items {
			tempResources = append(tempResources, &Resource{ObjectMeta: dm.ObjectMeta, Template: dm.Spec.Template})
		}
		isWorkloads = true
	case model.Service:
		resources, err := c.clientset.CoreV1().Services(namespace).List(context.Background(), metav1.ListOptions{})
		if err != nil {
			logrus.Errorf("Failed to get Services list:%v", err)
			return nil, cmMap, secretMap
		}
		for _, dm := range resources.Items {
			tempResources = append(tempResources, &Resource{ObjectMeta: dm.ObjectMeta})
		}
	case model.PVC:
		resources, err := c.clientset.CoreV1().PersistentVolumeClaims(namespace).List(context.Background(), metav1.ListOptions{})
		if err != nil {
			logrus.Errorf("Failed to get PersistentVolumeClaims list:%v", err)
			return nil, cmMap, secretMap
		}
		for _, dm := range resources.Items {

			tempResources = append(tempResources, &Resource{ObjectMeta: dm.ObjectMeta})
		}
	case model.Ingress:
		resources, err := c.clientset.NetworkingV1().Ingresses(namespace).List(context.Background(), metav1.ListOptions{})
		if err != nil {
			logrus.Errorf("Failed to get Ingresses list:%v", err)
			return nil, cmMap, secretMap
		}
		for _, dm := range resources.Items {
			tempResources = append(tempResources, &Resource{ObjectMeta: dm.ObjectMeta})
		}
	case model.NetworkPolicy:
		resources, err := c.clientset.NetworkingV1().NetworkPolicies(namespace).List(context.Background(), metav1.ListOptions{})
		if err != nil {
			logrus.Errorf("Failed to get NetworkPolicies list:%v", err)
			return nil, cmMap, secretMap
		}
		for _, dm := range resources.Items {
			tempResources = append(tempResources, &Resource{ObjectMeta: dm.ObjectMeta})
		}
	case model.ConfigMap:
		resources, err := c.clientset.CoreV1().ConfigMaps(namespace).List(context.Background(), metav1.ListOptions{})
		if err != nil {
			logrus.Errorf("Failed to get ConfigMaps list:%v", err)
			return nil, cmMap, secretMap
		}
		for _, dm := range resources.Items {
			tempResources = append(tempResources, &Resource{ObjectMeta: dm.ObjectMeta})
		}
	case model.Secret:
		resources, err := c.clientset.CoreV1().Secrets(namespace).List(context.Background(), metav1.ListOptions{})
		if err != nil {
			logrus.Errorf("Failed to get Secrets list:%v", err)
			return nil, cmMap, secretMap
		}
		for _, dm := range resources.Items {
			tempResources = append(tempResources, &Resource{ObjectMeta: dm.ObjectMeta})
		}
	case model.ServiceAccount:
		resources, err := c.clientset.CoreV1().ServiceAccounts(namespace).List(context.Background(), metav1.ListOptions{})
		if err != nil {
			logrus.Errorf("Failed to get ServiceAccounts list:%v", err)
			return nil, cmMap, secretMap
		}
		for _, dm := range resources.Items {
			tempResources = append(tempResources, &Resource{ObjectMeta: dm.ObjectMeta})
		}
	case model.RoleBinding:
		resources, err := c.clientset.RbacV1().RoleBindings(namespace).List(context.Background(), metav1.ListOptions{})
		if err != nil {
			logrus.Errorf("Failed to get RoleBindings list:%v", err)
			return nil, cmMap, secretMap
		}
		for _, dm := range resources.Items {
			tempResources = append(tempResources, &Resource{ObjectMeta: dm.ObjectMeta})
		}
	case model.HorizontalPodAutoscaler:
		resources, err := c.clientset.AutoscalingV1().HorizontalPodAutoscalers(namespace).List(context.Background(), metav1.ListOptions{})
		if err != nil {
			logrus.Errorf("Failed to get HorizontalPodAutoscalers list:%v", err)
			return nil, cmMap, secretMap
		}
		for _, hpa := range resources.Items {
			rbdResource := false
			labels := make(map[string]string)
			switch hpa.Spec.ScaleTargetRef.Kind {
			case model.Deployment:
				deploy, err := c.clientset.AppsV1().Deployments(namespace).Get(context.Background(), hpa.Spec.ScaleTargetRef.Name, metav1.GetOptions{})
				if err != nil {
					logrus.Errorf("The bound deployment does not exist:%v", err)
				}
				if hpa.ObjectMeta.Labels["creator"] == "Rainbond" {
					rbdResource = true
				}
				labels = deploy.ObjectMeta.Labels
			case model.StateFulSet:
				ss, err := c.clientset.AppsV1().StatefulSets(namespace).Get(context.Background(), hpa.Spec.ScaleTargetRef.Name, metav1.GetOptions{})
				if err != nil {
					logrus.Errorf("The bound deployment does not exist:%v", err)
				}
				if hpa.ObjectMeta.Labels["creator"] == "Rainbond" {
					rbdResource = true
				}
				labels = ss.ObjectMeta.Labels
			}
			var app string
			if content == "unmanaged" && rbdResource {
				continue
			}
			app = labels["app"]
			if labels["app.kubernetes.io/name"] != "" {
				app = labels["app.kubernetes.io/name"]
			}
			if app == "" {
				app = "unclassified"
			}
			if _, ok := resourceName[app]; ok {
				resourceName[app] = append(resourceName[app], hpa.Name)
			} else {
				resourceName[app] = []string{hpa.Name}
			}
		}
		return resourceName, nil, nil
	case model.Role:
		resources, err := c.clientset.RbacV1().Roles(namespace).List(context.Background(), metav1.ListOptions{})
		if err != nil {
			logrus.Errorf("Failed to get Roles list:%v", err)
			return nil, cmMap, secretMap
		}
		for _, dm := range resources.Items {
			tempResources = append(tempResources, &Resource{ObjectMeta: dm.ObjectMeta})
		}
	}
	//这一块是统一处理资源,按label划分出来
	for _, rs := range tempResources {
		if content == "unmanaged" && rs.ObjectMeta.Labels["creator"] == "Rainbond" {
			continue
		}
		app := rs.ObjectMeta.Labels["app"]
		if rs.ObjectMeta.Labels["app.kubernetes.io/name"] != "" {
			app = rs.ObjectMeta.Labels["app.kubernetes.io/name"]
		}
		if app == "" {
			app = "unclassified"
		}
		//如果是Workloads类型的资源需要检查其内部configmap、secret、PVC(防止没有这三种资源没有label但是用到了)
		if isWorkloads {
			cmList, secretList := c.replenishLabel(ctx, rs, namespace, app)
			if _, ok := cmMap[app]; ok {
				cmMap[app] = append(cmMap[app], cmList...)
			} else {
				cmMap[app] = cmList
			}
			if _, ok := secretMap[app]; ok {
				secretMap[app] = append(secretMap[app], secretList...)
			} else {
				secretMap[app] = secretList
			}
		}
		if _, ok := resourceName[app]; ok {
			resourceName[app] = append(resourceName[app], rs.ObjectMeta.Name)
		} else {
			resourceName[app] = []string{rs.ObjectMeta.Name}
		}
	}
	return resourceName, cmMap, secretMap
}

//replenishLabel 获取workloads资源上携带的ConfigMap和secret,以及把pvc加上标签。
func (c *clusterAction) replenishLabel(ctx context.Context, resource *Resource, namespace string, app string) ([]string, []string) {
	var cmList []string
	var secretList []string
	resourceVolume := resource.Template.Spec.Volumes
	for _, volume := range resourceVolume {
		if pvc := volume.PersistentVolumeClaim; pvc != nil {
			PersistentVolumeClaims, err := c.clientset.CoreV1().PersistentVolumeClaims(namespace).Get(context.Background(), pvc.ClaimName, metav1.GetOptions{})
			if err != nil {
				logrus.Errorf("Failed to get PersistentVolumeClaims %s/%s:%v", namespace, pvc.ClaimName, err)
			}
			if PersistentVolumeClaims.Labels == nil {
				PersistentVolumeClaims.Labels = make(map[string]string)
			}
			if _, ok := PersistentVolumeClaims.Labels["app"]; !ok {
				if _, ok := PersistentVolumeClaims.Labels["app.kubernetes.io/name"]; !ok {
					PersistentVolumeClaims.Labels["app"] = app
				}
			}
			_, err = c.clientset.CoreV1().PersistentVolumeClaims(namespace).Update(context.Background(), PersistentVolumeClaims, metav1.UpdateOptions{})
			if err != nil {
				logrus.Errorf("PersistentVolumeClaims label update error:%v", err)
			}
			continue
		}
		if cm := volume.ConfigMap; cm != nil {
			cm, err := c.clientset.CoreV1().ConfigMaps(namespace).Get(context.Background(), cm.Name, metav1.GetOptions{})
			if err != nil {
				logrus.Errorf("Failed to get ConfigMap:%v", err)
			}
			if _, ok := cm.Labels["app"]; !ok {
				if _, ok := cm.Labels["app.kubernetes.io/name"]; !ok {
					cmList = append(cmList, cm.Name)
				}
			}
		}
		if secret := volume.Secret; secret != nil {
			secret, err := c.clientset.CoreV1().Secrets(namespace).Get(context.Background(), secret.SecretName, metav1.GetOptions{})
			if err != nil {
				logrus.Errorf("Failed to get Scret:%v", err)
			}
			if _, ok := secret.Labels["app"]; !ok {
				if _, ok := secret.Labels["app.kubernetes.io/name"]; !ok {
					cmList = append(cmList, secret.Name)
				}
			}
		}
	}
	return cmList, secretList
}