package nodepoolgc

import (
	"time"

	"github.com/pingcap/errors"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"github.com/tidbcloud/infra-provider/pkg/consts"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/config"
)

// updateGCMetadata advances the annotation-backed state machine in place.
// The caller patches the object once after this function returns, including
// on validation errors so gc-last-scan-at remains observable.
func updateGCMetadata(now time.Time, obj client.Object, cfg config.NodePoolGCConfig) (_ Status, _ error) {
	nowText := now.Format(time.RFC3339)
	annotations := obj.GetAnnotations()
	if annotations == nil {
		annotations = map[string]string{}
	}
	defer obj.SetAnnotations(annotations)
	annotations[consts.AnnoKeyNodePoolGCLastScanAt] = nowText

	labels := obj.GetLabels()
	if labels == nil {
		labels = map[string]string{}
	}
	defer obj.SetLabels(labels)
	if val, ok := labels[consts.LabelKeyNodePoolGCManaged]; !ok {
		labels[consts.LabelKeyNodePoolGCManaged] = consts.AnnoValTrue
		// If the label was missing, we mark it as active and count from now.
		annotations[consts.AnnoKeyNodePoolLastUsedTime] = nowText
		return StatusNotExpired, nil
	} else if val != consts.AnnoValTrue {
		// If the label is present but not true, we treat it as unmanaged and skip GC.
		return StatusNotExpired, nil
	}
	if annotations[consts.AnnoKeyNodePoolLastUsedTime] == "" {
		annotations[consts.AnnoKeyNodePoolLastUsedTime] = nowText
		return StatusNotExpired, nil
	}

	lastUsed, err := requiredTime(annotations, consts.AnnoKeyNodePoolLastUsedTime)
	if err != nil {
		return StatusFailed, err
	}
	// clear expiredAt, approvedAt since the pool is now active again
	if now.Before(lastUsed.Add(cfg.ExpireAfter.Duration)) {
		delete(annotations, consts.AnnoKeyNodePoolGCExpiredAt)
		delete(annotations, consts.AnnoKeyNodePoolGCApprovedAt)
		return StatusNotExpired, nil
	}

	_, hasExpiredAt, err := optionalTime(annotations, consts.AnnoKeyNodePoolGCExpiredAt)
	if err != nil {
		return StatusFailed, err
	}
	if !hasExpiredAt {
		annotations[consts.AnnoKeyNodePoolGCExpiredAt] = nowText
	}

	_, hasApprovedAt, err := optionalTime(annotations, consts.AnnoKeyNodePoolGCApprovedAt)
	if err != nil {
		return StatusFailed, err
	}
	if hasApprovedAt {
		return StatusDeletionApproved, nil
	}

	if !cfg.AutoApproveDeletion {
		return StatusExpired, nil
	}
	annotations[consts.AnnoKeyNodePoolGCApprovedAt] = nowText
	return StatusDeletionApproved, nil
}

func requiredTime(annotations map[string]string, key string) (time.Time, error) {
	value := annotations[key]
	parsed, err := time.Parse(time.RFC3339, value)
	if err != nil {
		return time.Time{}, errors.Wrapf(err, "invalid %s", key)
	}
	return parsed, nil
}

func optionalTime(annotations map[string]string, key string) (time.Time, bool, error) {
	value := annotations[key]
	if value == "" {
		return time.Time{}, false, nil
	}
	parsed, err := requiredTime(annotations, key)
	return parsed, true, err
}
