package task

import (
	"fmt"
	"strings"

	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/config"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/consts"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/util"
)

func CloudTags(provider string, nodePool *v1alpha1.NodePool, dataPlane *v1alpha1.DataPlane, cfg config.RootConfig) map[string]string {
	merged := util.MergeMaps(nodePool.Labels, dataPlane.Spec.CloudTags)
	tags := controllers.ConvertToCloudTags(merged, provider)
	return util.MergeMaps(tags, cfg.AdditionalCloudTags)
}

func NormalizeNodeType(nodeType string) string {
	s := strings.TrimSpace(strings.ToLower(nodeType))
	switch s {
	case "shared":
		return "shared"
	case "dedicated":
		return "dedicated"
	default:
		return ""
	}
}

func NormalizeTaintEffect(effect string) string {
	if effect == "" {
		return string(corev1.TaintEffectNoSchedule)
	}
	switch strings.ToLower(effect) {
	case "noschedule", "no_schedule", "no-schedule":
		return string(corev1.TaintEffectNoSchedule)
	case "noexecute", "no_execute", "no-execute":
		return string(corev1.TaintEffectNoExecute)
	case "prefernoschedule", "prefer_no_schedule", "prefer-no-schedule":
		return string(corev1.TaintEffectPreferNoSchedule)
	default:
		return effect
	}
}

func EnsureLocalDiskLabels(labels map[string]*string, metaLabels map[string]string, localDiskSize uint32) {
	labels[consts.LabelKeyHasLocalDisk] = util.Pointer("true")
	labels[consts.LabelKeyLocalDiskSize] = util.Pointer(fmt.Sprint(localDiskSize))
	metaLabels[consts.LabelKeyHasLocalDisk] = "true"
	metaLabels[consts.LabelKeyLocalDiskSize] = fmt.Sprint(localDiskSize)
}

func EnsureLocalDiskLabelsString(labels map[string]string, metaLabels map[string]string, localDiskSize uint32) {
	labels[consts.LabelKeyHasLocalDisk] = "true"
	labels[consts.LabelKeyLocalDiskSize] = fmt.Sprint(localDiskSize)
	metaLabels[consts.LabelKeyHasLocalDisk] = "true"
	metaLabels[consts.LabelKeyLocalDiskSize] = fmt.Sprint(localDiskSize)
}

func EnsureNodeTypeLabel(taints []v1alpha1.NodePoolTaint, nodeType string) []v1alpha1.NodePoolTaint {
	if nodeType == "" {
		return taints
	}
	for _, t := range taints {
		if t.Key == consts.TaintKeyDedicated {
			return taints
		}
	}
	val := consts.TaintValueWorker
	if nodeType == "shared" {
		val = consts.TaintValueShared
	}
	return append(taints, v1alpha1.NodePoolTaint{
		Key:    consts.TaintKeyDedicated,
		Value:  val,
		Effect: string(corev1.TaintEffectNoSchedule),
	})
}

func EnsureLocalDiskTaint(taints []v1alpha1.NodePoolTaint) []v1alpha1.NodePoolTaint {
	for _, t := range taints {
		if t.Key == consts.TaintKeyHasLocalDisk {
			return taints
		}
	}
	return append(taints, v1alpha1.NodePoolTaint{
		Key:    consts.TaintKeyHasLocalDisk,
		Value:  consts.TaintValueTrue,
		Effect: string(corev1.TaintEffectNoSchedule),
	})
}

func ToMetaCondition(cond xpv1.Condition) metav1.Condition {
	// metav1.Condition requires Reason to be non-empty and LastTransitionTime to be set.
	// Crossplane conditions may have these fields unset when the condition hasn't been
	// populated yet (e.g. GetCondition returns a zero-value condition).
	reason := string(cond.Reason)
	if reason == "" {
		reason = "Unknown"
	}
	lastTransitionTime := cond.LastTransitionTime
	if lastTransitionTime.IsZero() {
		lastTransitionTime = metav1.Now()
	}
	return metav1.Condition{
		Type:               string(cond.Type),
		Status:             metav1.ConditionStatus(cond.Status),
		Reason:             reason,
		Message:            cond.Message,
		LastTransitionTime: lastTransitionTime,
	}
}
