package mutatefuncs

import (
	"maps"
	"strings"

	computev1alpha1 "github.com/crossplane-contrib/provider-gcp/apis/compute/v1alpha1"
	"github.com/crossplane-contrib/provider-gcp/apis/container/v1beta1"
	"github.com/crossplane-contrib/provider-gcp/apis/container/v1beta2"
	vpcpeeringv1beta1 "github.com/crossplane/provider-gcp/apis/vpcpeering/v1beta1"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/consts"
	nodepoolutil "github.com/tidbcloud/infra-provider/pkg/nodepool"
)

func MutateGKE(current, expected *v1beta2.Cluster) error {
	current.Labels = expected.Labels
	current.Annotations = expected.Annotations

	current.Spec = expected.Spec

	return nil
}

func MutateGKENodePool(current, expected *v1beta1.NodePool) error {
	currentDesiredComputeClass := nodepoolutil.GKEDesiredComputeClassNameForNodePool(current)
	currentAppliedComputeClass := nodepoolutil.GKEAppliedComputeClassNameForNodePool(current)
	expectedDesiredComputeClass := gkeComputeClassDesiredAnnotationForNodePool(expected)
	preserveAppliedComputeClass := expectedDesiredComputeClass != "" &&
		currentDesiredComputeClass == expectedDesiredComputeClass &&
		currentAppliedComputeClass == expectedDesiredComputeClass

	mergedConfigLabels, hasConfigLabelMerge := mergeGKENodePoolConfigLabelsForComputeClass(current, expected)
	mergedConfigTaints, hasConfigTaintMerge := mergeGKENodePoolConfigTaintsForComputeClass(current, expected)

	current.Labels = expected.Labels
	current.Annotations = maps.Clone(expected.Annotations)
	if preserveAppliedComputeClass {
		nodepoolutil.SetGKEAppliedComputeClassForNodePool(current, expectedDesiredComputeClass)
	} else {
		nodepoolutil.ClearGKEAppliedComputeClassForNodePool(current)
	}

	current.Spec = expected.Spec
	if hasConfigLabelMerge {
		if current.Spec.ForProvider.Config == nil {
			current.Spec.ForProvider.Config = &v1beta1.NodeConfig{}
		}
		current.Spec.ForProvider.Config.Labels = mergedConfigLabels
	}
	if hasConfigTaintMerge {
		if current.Spec.ForProvider.Config == nil {
			current.Spec.ForProvider.Config = &v1beta1.NodeConfig{}
		}
		current.Spec.ForProvider.Config.Taints = mergedConfigTaints
	}

	return nil
}

func gkeComputeClassDesiredAnnotationForNodePool(np *v1beta1.NodePool) string {
	if np == nil || np.Annotations == nil {
		return ""
	}
	return strings.TrimSpace(np.Annotations[consts.AnnoKeyGKENodePoolComputeClass])
}

// mergeGKENodePoolConfigLabelsForComputeClass only handles heterogeneous
// NodePools. Ordinary NodePools without a desired compute-class annotation keep
// the normal full-apply label behavior. For existing heterogeneous NodePools,
// the compute-class config label is confirmed actual/applied metadata, so this
// merge preserves the current spec label instead of projecting new desired
// intent into spec.
func mergeGKENodePoolConfigLabelsForComputeClass(current, expected *v1beta1.NodePool) (map[string]string, bool) {
	currentLabels := map[string]string(nil)
	if current != nil && current.Spec.ForProvider.Config != nil {
		currentLabels = current.Spec.ForProvider.Config.Labels
	}
	expectedLabels := map[string]string(nil)
	if expected != nil && expected.Spec.ForProvider.Config != nil {
		expectedLabels = expected.Spec.ForProvider.Config.Labels
	}
	expectedComputeClass := gkeComputeClassDesiredAnnotationForNodePool(expected)
	if expectedComputeClass == "" {
		return nil, false
	}

	labels := make(map[string]string, len(currentLabels)+len(expectedLabels))
	for k, v := range currentLabels {
		if isManagedGKENodePoolLabelKey(k) {
			continue
		}
		labels[k] = v
	}
	for k, v := range expectedLabels {
		if k == consts.GKEComputeClassLabelKey {
			continue
		}
		labels[k] = v
	}
	if currentSpecComputeClass := nodepoolutil.GKEComputeClassSpecLabelForNodePool(current); currentSpecComputeClass != "" {
		labels[consts.GKEComputeClassLabelKey] = currentSpecComputeClass
	}
	return labels, true
}

func mergeGKENodePoolConfigTaintsForComputeClass(current, expected *v1beta1.NodePool) ([]*v1beta1.NodeTaint, bool) {
	expectedComputeClass := gkeComputeClassDesiredAnnotationForNodePool(expected)
	if expectedComputeClass == "" {
		return nil, false
	}
	expectedTaints := []*v1beta1.NodeTaint(nil)
	if expected != nil && expected.Spec.ForProvider.Config != nil {
		expectedTaints = expected.Spec.ForProvider.Config.Taints
	}
	taints := make([]*v1beta1.NodeTaint, 0, len(expectedTaints)+1)
	for _, taint := range expectedTaints {
		if taint == nil || taint.Key == consts.GKEComputeClassLabelKey {
			continue
		}
		copied := *taint
		taints = append(taints, &copied)
	}
	if currentSpecComputeClass := nodepoolutil.GKEComputeClassSpecTaintForNodePool(current); currentSpecComputeClass != "" {
		taints = append(taints, &v1beta1.NodeTaint{
			Key:    consts.GKEComputeClassLabelKey,
			Value:  currentSpecComputeClass,
			Effect: "NO_SCHEDULE",
		})
	}
	return taints, true
}

func isManagedGKENodePoolLabelKey(key string) bool {
	switch key {
	case consts.GKEComputeClassLabelKey,
		consts.LabelKeyNodeGroupType,
		consts.K8sVersionLabelKey,
		consts.LabelKeyFor3LayerArch,
		cloudv1.TagLabelUsedBy,
		cloudv1.TagLabelCluster,
		cloudv1.TagLabelServiceType,
		cloudv1.TagLabelProject,
		cloudv1.TagLabelTenant:
		return true
	default:
		return false
	}
}

func MutateFirewall(current, expected *computev1alpha1.Firewall) error {
	current.Labels = expected.Labels
	current.Annotations = expected.Annotations

	current.Spec.ResourceSpec = expected.Spec.ResourceSpec
	current.Spec.ForProvider = expected.Spec.ForProvider

	return nil
}

func MutateGCPNetworkPeering(current, expected *vpcpeeringv1beta1.Peering) error {
	current.Labels = expected.Labels
	current.Annotations = expected.Annotations

	current.Spec.ResourceSpec = expected.Spec.ResourceSpec
	current.Spec.ForProvider = expected.Spec.ForProvider

	return nil
}

func MutateRouter(current, expected *computev1alpha1.Router) error {
	current.Labels = expected.Labels
	current.Annotations = expected.Annotations

	current.Spec.ResourceSpec = expected.Spec.ResourceSpec
	current.Spec.ForProvider = expected.Spec.ForProvider
	return nil
}
