package kubeapi

import (
	"context"
	"time"

	"github.com/tidbcloud/aws-shared-provider/pkg/types"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

// CreateIgnoreExists create the obj and ignore the already exists error.
func CreateIgnoreExists(ctx context.Context, cli client.Client, obj client.Object, opts ...client.CreateOption) error {
	if err := cli.Create(ctx, obj, opts...); err != nil {
		if !apierrors.IsAlreadyExists(err) {
			return err
		}
	}

	return nil
}

func IsPodReady(p *corev1.Pod) bool {
	if p.DeletionTimestamp == nil {
		for i := range p.Status.Conditions {
			c := &p.Status.Conditions[i]
			if c.Type != corev1.PodReady {
				continue
			}
			if c.Status == corev1.ConditionTrue {
				return true
			}
		}
	}
	return false
}

func IsPodAbnormal(p *corev1.Pod) bool {
	if p.DeletionTimestamp == nil {
		// ignore the pod that is pending and has no node assigned, which is used for tidb rescheduling.
		if p.Annotations[types.AnnoTidbReschedule] != "" && p.Status.Phase == corev1.PodPending && p.Spec.NodeName == "" {
			return false
		}

		var readyCond, initCond *corev1.PodCondition
		for i := range p.Status.Conditions {
			c := &p.Status.Conditions[i]
			if c.Type == corev1.PodReady {
				readyCond = c
			} else if c.Type == corev1.PodInitialized {
				initCond = c
			}
		}
		if readyCond == nil || initCond == nil {
			return true
		}
		if readyCond.Status == corev1.ConditionFalse && readyCond.LastTransitionTime.Sub(initCond.LastTransitionTime.Time) < 5*time.Second {
			return true
		}
	}
	return false
}

func GetPodDisruptionMsg(p *corev1.Pod) string {
	for i := range p.Status.Conditions {
		c := &p.Status.Conditions[i]
		if c.Type == corev1.DisruptionTarget && c.Status == corev1.ConditionTrue {
			return c.Message
		}
	}
	return ""
}

func IsPodUnschedulable(p *corev1.Pod) bool {
	if p.Status.Phase == corev1.PodPending {
		for i := range p.Status.Conditions {
			c := &p.Status.Conditions[i]
			if c.Type != corev1.PodScheduled {
				continue
			}
			if c.Status == corev1.ConditionFalse && c.Reason == corev1.PodReasonUnschedulable {
				return true
			}
		}
	}
	return false
}

// IsNodeReady returns true if a node is ready; false otherwise.
func IsNodeReady(node *corev1.Node) bool {
	for i := range node.Status.Conditions {
		c := &node.Status.Conditions[i]
		if c.Type == corev1.NodeReady {
			return c.Status == corev1.ConditionTrue
		}
	}
	return false
}

// IsNodeLongUnready returns true if a node is long unready; false otherwise.
func IsNodeLongUnready(node *corev1.Node, duration time.Duration) bool {
	for i := range node.Status.Conditions {
		c := &node.Status.Conditions[i]
		if c.Type == corev1.NodeReady && c.Status != corev1.ConditionTrue {
			return time.Since(c.LastTransitionTime.Time) > duration
		}
	}
	return false
}

// IsNodeUnreachable returns true if a node is unreachable; false otherwise.
func IsNodeUnreachable(node *corev1.Node) bool {
	for i := range node.Spec.Taints {
		taint := &node.Spec.Taints[i]
		if taint.Key == corev1.TaintNodeUnreachable {
			return true
		}
	}
	return false
}

// UpdateNode updates the node object, retry once on conflict.
func UpdateNode(ctx context.Context, c client.Client, f func() *corev1.Node) error {
	node := f()
	if err := c.Update(ctx, node); err == nil {
		return nil
	} else if !apierrors.IsConflict(err) {
		return err
	}
	if err := c.Get(ctx, client.ObjectKeyFromObject(node), node); err != nil {
		return err
	}
	return c.Update(ctx, f())
}
