package util

import (
	"fmt"
	"sort"
	"strings"

	k8sutil "github.com/goodrain/rainbond/util/k8s"
	"github.com/goodrain/rainbond/worker/server/pb"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/client-go/kubernetes"
)

// PodStatusAdvice -
type PodStatusAdvice string

// String converts PodStatusAdvice to string
func (p PodStatusAdvice) String() string {
	return string(p)
}

// PodStatusAdviceOOM -
var PodStatusAdviceOOM PodStatusAdvice = "OutOfMemory"

// PodStatusAdviceUnhealthy -
var PodStatusAdviceUnhealthy PodStatusAdvice = "Unhealthy"

// PodStatusAdviceInitiating -
var PodStatusAdviceInitiating PodStatusAdvice = "Initiating"

var podStatusTbl = map[string]pb.PodStatus_Type{
	string(corev1.PodPending):      pb.PodStatus_INITIATING,
	string(corev1.PodRunning):      pb.PodStatus_RUNNING,
	string(corev1.PodSucceeded):    pb.PodStatus_ABNORMAL,
	string(corev1.PodFailed):       pb.PodStatus_ABNORMAL,
	string(corev1.PodUnknown):      pb.PodStatus_UNKNOWN,
	string(corev1.PodReady):        pb.PodStatus_NOTREADY,
	string(corev1.PodInitialized):  pb.PodStatus_INITIATING,
	string(corev1.PodScheduled):    pb.PodStatus_SCHEDULING,
	string(corev1.ContainersReady): pb.PodStatus_NOTREADY,
}

// DescribePodStatus -
func DescribePodStatus(clientset kubernetes.Interface, pod *corev1.Pod, podStatus *pb.PodStatus, f k8sutil.ListEventsByPod) {
	defer func() {
		podStatus.TypeStr = podStatus.Type.String()
	}()
	if pod.DeletionTimestamp != nil {
		podStatus.Type = pb.PodStatus_TEMINATING
		podStatus.Message = fmt.Sprintf("Termination Grace Period:\t%ds", *pod.DeletionGracePeriodSeconds)
	} else if len(pod.Status.Conditions) == 0 {
		podStatus.Type = podStatusTbl[string(pod.Status.Phase)]
		if len(pod.Status.Reason) > 0 {
			podStatus.Reason = pod.Status.Reason
		}
		if len(pod.Status.Message) > 0 {
			podStatus.Message = pod.Status.Message
		}
	} else {
		// schedule, ready, init
		podStatus.Type = pb.PodStatus_RUNNING
		sort.Sort(SortableConditionType(pod.Status.Conditions))
		for _, condition := range pod.Status.Conditions {
			if condition.Status == corev1.ConditionTrue {
				continue
			}
			podStatus.Type = podStatusTbl[string(condition.Type)] // find the latest not ready condition
			podStatus.Reason = condition.Reason
			podStatus.Message = condition.Message
		}
	}
	if podStatus.Type == pb.PodStatus_INITIATING {
		podStatus.Advice = PodStatusAdviceInitiating.String()
		// if all main container ready
		if len(pod.Status.ContainerStatuses) > 0 {
			allMainCReady := true
			for _, mainC := range pod.Status.ContainerStatuses {
				if !mainC.Ready {
					allMainCReady = false
					break
				}
			}
			if allMainCReady {
				podStatus.Type = pb.PodStatus_RUNNING
				return
			}
		}
		return
	}
	if podStatus.Type == pb.PodStatus_NOTREADY {
		for _, cstatus := range pod.Status.ContainerStatuses {
			if !cstatus.Ready && cstatus.State.Terminated != nil {
				podStatus.Type = pb.PodStatus_ABNORMAL
				if cstatus.State.Terminated.Reason == "OOMKilled" {
					podStatus.Advice = PodStatusAdviceOOM.String()
				}
				return
			}
			if !cstatus.Ready {
				events := f(clientset, pod)
				if events != nil {
					for _, evt := range events.Items {
						if strings.Contains(evt.Message, "Liveness probe failed") || strings.Contains(evt.Message, "Readiness probe failed") {
							podStatus.Type = pb.PodStatus_UNHEALTHY
							podStatus.Advice = PodStatusAdviceUnhealthy.String()
							return
						}
					}
				}
			}
			if !cstatus.Ready && cstatus.State.Waiting != nil {
				w := cstatus.State.Waiting
				if w.Reason != "PodInitializing" && w.Reason != "ContainerCreating" {
					podStatus.Type = pb.PodStatus_ABNORMAL
					return
				}
			}
		}
	}
}

// SortableConditionType implements sort.Interface for []PodCondition based on
// the Type field.
type SortableConditionType []corev1.PodCondition

var podConditionTbl = map[corev1.PodConditionType]int{
	corev1.PodScheduled:    0,
	corev1.PodInitialized:  1,
	corev1.PodReady:        2,
	corev1.ContainersReady: 3,
}

func (s SortableConditionType) Len() int {
	return len(s)
}
func (s SortableConditionType) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}

func (s SortableConditionType) Less(i, j int) bool {
	return podConditionTbl[s[i].Type] > podConditionTbl[s[j].Type]
}
