package common

import (
	"context"
	"fmt"
	"sort"
	"time"

	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"github.com/tidbcloud/eks-provider/controllers/cluster/task"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/consts"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/pingcap/label"
)

const (
	NodeReadyCheckName          = "NodeReadyCheck"
	NodeNotReadyConditionReason = "NodeNotReady"
	NodeReadyConditionReason    = "NodeReady"
)

var (
	clusterCheckComponents = []string{"pd", "tikv", "tidb", "tiflash", "tiproxy"}
)

// NodeReadyCheck implements a task to check Pod status
type NodeReadyCheck struct {
	*task.BaseTask
}

// NewNodeReadyCheck creates a new NodeReadyCheck task
func NewNodeReadyCheck(ctx *task.Context) *NodeReadyCheck {
	return &NodeReadyCheck{
		BaseTask: &task.BaseTask{Context: ctx},
	}
}

// Name returns the name of the task
func (t *NodeReadyCheck) Name() string {
	return NodeReadyCheckName
}

// Create executes the Pod status check task
func (t *NodeReadyCheck) Create(ctx context.Context) deploy.Result {
	if !t.Cluster.GetDeletionTimestamp().IsZero() {
		t.Logger.Info("Cluster already deleted, skip NodeReadyCheck task", "cluster", t.Cluster.GetClusterID())
		return deploy.Done()
	}
	t.Logger.Info("Starting NodeReadyCheck task", "cluster", t.Cluster.GetClusterID())

	// Get the cluster namespace
	namespace := t.Namer.GetTiDBNamespace()

	// Query all Pods
	components := append([]string{consts.OverProvisioningComponentLabelValue}, clusterCheckComponents...)
	labelSelector, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{
		MatchExpressions: []metav1.LabelSelectorRequirement{
			{
				Key:      label.ComponentLabelKey,
				Operator: metav1.LabelSelectorOpIn,
				Values:   components,
			},
		},
	})
	if err != nil {
		return deploy.Error(fmt.Errorf("build label selector: %w", err))
	}
	var podList corev1.PodList
	if err := t.EnvProvider.GetClient().List(ctx, &podList, client.InNamespace(namespace), client.MatchingLabelsSelector{Selector: labelSelector}); err != nil {
		t.Logger.Error(err, "NodeReadyCheck task, Failed to list pods")
		return deploy.Error(fmt.Errorf("failed to list pods: %w", err))
	}
	t.Logger.Info("NodeReadyCheck task", "podList.Items size", len(podList.Items))

	// Filter out Pods in Pending state
	var pendingPods []corev1.Pod
	for _, pod := range podList.Items {
		if pod.Status.Phase == corev1.PodPending && pod.Spec.NodeName == "" {
			t.Logger.Info("NodeReadyCheck task", "add arr pendingPods", pod.Name)
			pendingPods = append(pendingPods, pod)
		}
	}

	// If there are no Pending Pods, remove the related condition
	if len(pendingPods) == 0 {
		t.Logger.Info("NodeReadyCheck task , Pod status check completed,no pending pod", "clusterID", t.Cluster.GetClusterID())
		if err = t.resetClusterNodeReadyCondition(ctx); err != nil {
			t.Logger.Error(err, "NodeReadyCheck task, Failed to update cluster status")
			return deploy.Error(fmt.Errorf("failed to update cluster status: %w", err))
		}
		return deploy.Done()
	}

	// Sort pendingPods: by CreationTimestamp, then by Name if timestamps are equal.
	sort.SliceStable(pendingPods, func(i, j int) bool {
		if pendingPods[i].CreationTimestamp.Equal(&pendingPods[j].CreationTimestamp) {
			return pendingPods[i].Name < pendingPods[j].Name
		}
		return pendingPods[i].CreationTimestamp.Before(&pendingPods[j].CreationTimestamp)
	})

	// Find the earliest created pending pod
	earliestPod := pendingPods[0]
	t.Logger.Info("NodeReadyCheck task", "earliestPod", earliestPod.Name, "clusterID", t.Cluster.GetClusterID())

	// Update the Conditions field of the cluster CR
	if err = t.updateClusterNodeReadyCondition(ctx, earliestPod); err != nil {
		t.Logger.Error(err, "NodeReadyCheck task, Failed to update cluster status")
		return deploy.Error(fmt.Errorf("failed to update cluster status: %w", err))
	}

	t.Logger.Info("NodeReadyCheck task completed", "earliestPod", earliestPod.Name, "clusterID", t.Cluster.GetClusterID())
	return deploy.Done()
}

// buildEarliestPendingPodMessage builds a message containing informatio
func buildEarliestPendingPodMessage(pod corev1.Pod) string {
	componentType := pod.Labels["app.kubernetes.io/component"]
	creationTime := pod.CreationTimestamp.Format(time.RFC3339)
	message := fmt.Sprintf("Earliest pending pod , Component: %s, PodName: %s, PodcreationTime: %s", componentType, pod.Name, creationTime)
	return message
}

func buildResetMessage() string {
	podCheckTime := time.Now().UTC().Format(time.RFC3339)
	message := fmt.Sprintf("All pod running , podCheckTime: %s", podCheckTime)
	return message
}

// updateClusterNodeReadyCondition updates the cluster condition with pending pod information
func (t *NodeReadyCheck) updateClusterNodeReadyCondition(ctx context.Context, earliestPod corev1.Pod) error {
	// Build the earliest Pending Pod information
	message := buildEarliestPendingPodMessage(earliestPod)

	condition := &cloudv1.ClusterCondition{
		Type:               cloudv1.ClusterNodeReady,
		Status:             cloudv1.ConditionFalse,
		LastTransitionTime: earliestPod.CreationTimestamp,
		Reason:             NodeNotReadyConditionReason,
		Message:            message,
	}
	cluster := t.Cluster // get Cluster

	// update Condition
	changed := cloudv1.UpdateClusterCondition(&cluster.Status, condition)
	t.Logger.Info("NodeReadyCheck , ClusterCondition is changed", "changed", changed, "cluster", t.Cluster.GetClusterID())

	// If Condition changes, update the Cluster CR
	if changed {
		if err := t.Local.Status().Update(ctx, t.Cluster); err != nil {
			t.Logger.Error(err, "NodeReadyCheck, Failed to update cluster status")
			return err
		}

		t.Logger.Info("NodeReadyCheck, UpdateClusterCondition completed", "podName", earliestPod.Name, "message", message)
	}
	return nil
}

// reset the pending pod condition from cluster status
// todo: reuse updateClusterNodeReadyCondition
func (t *NodeReadyCheck) resetClusterNodeReadyCondition(ctx context.Context) error {
	for _, cond := range t.Cluster.Status.Conditions {
		if cond.Type == cloudv1.ClusterNodeReady && cond.Status == cloudv1.ConditionFalse {

			cond.Status = cloudv1.ConditionTrue
			cond.Message = buildResetMessage()
			cond.Reason = NodeReadyConditionReason
			changed := cloudv1.UpdateClusterCondition(&t.Cluster.Status, &cond)
			// update cluster status update the Cluster CR
			if changed {
				if err := t.Local.Status().Update(ctx, t.Cluster); err != nil {
					t.Logger.Error(err, "NodeReadyCheck, Failed to update cluster status", "cluster", t.Cluster.GetClusterID())
					return err
				}
				t.Logger.Info("NodeReadyCheck, setPendingPodConditionStatus completed", "cond.Status", cond.Status, "cluster", t.Cluster.GetClusterID())
			}
		}
	}
	return nil
}
