package conditions

import (
	"fmt"
	"slices"
	"strings"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	workapiv1 "open-cluster-management.io/api/work/v1"
)

const (
	// conditionRuleReasonPrefix is used to identify conditions that were generated by condition rules
	conditionRuleReasonPrefix = "ConditionRule"
)

// IsConditionGeneratedByConditionRule determines if a condition was created by condition rules
func IsConditionGeneratedByConditionRule(condition metav1.Condition) bool {
	return strings.HasPrefix(condition.Reason, conditionRuleReasonPrefix)
}

// PruneConditionsGeneratedByConditionRules removes old conditions that were generated by a ConditionRule
// but have not been updated since that latest ManifestWork generation. Removing a ConditionRule will
// always update the ManifestWork generation, so any conditions generated by conditions rules from a
// previous generation must have been from a deleted rule.
func PruneConditionsGeneratedByConditionRules(conditions *[]metav1.Condition, workGeneration int64) {
	*conditions = slices.DeleteFunc(*conditions, func(c metav1.Condition) bool {
		return IsConditionGeneratedByConditionRule(c) && c.ObservedGeneration != workGeneration
	})
}

// AggregateManifestConditions collects conditions from manifests and combines them into top level
// conditions for the ManifestWork. Only the Available condition and conditions generated by condition rules
// are aggregated.
func AggregateManifestConditions(generation int64, manifests []workapiv1.ManifestCondition) []metav1.Condition {
	aggregated := map[string]aggregateCondition{
		// Always set Available condition, even if there are no manifests
		workapiv1.ManifestAvailable: {},
	}

	// Collect conditions by type
	for _, manifest := range manifests {
		for _, condition := range manifest.Conditions {
			if condition.Type == workapiv1.ManifestAvailable || IsConditionGeneratedByConditionRule(condition) {
				aggCondition := aggregated[condition.Type]
				switch condition.Status {
				case metav1.ConditionFalse:
					aggCondition.numFalse++
				case metav1.ConditionTrue:
					aggCondition.numTrue++
				case metav1.ConditionUnknown:
					aggCondition.numUnknown++
				}

				if condition.Message != "" {
					aggCondition.message = condition.Message
				}

				aggregated[condition.Type] = aggCondition
			}
		}
	}

	// Create aggregate conditions
	conditions := make([]metav1.Condition, 0, len(aggregated))
	for conditionType, aggCondition := range aggregated {
		var condition metav1.Condition
		switch conditionType {
		case workapiv1.ManifestAvailable:
			condition = newAggregateAvailableCondition(aggCondition, generation, len(manifests))
		default:
			condition = newAggregateConditionFromRules(conditionType, generation, aggCondition)
		}

		conditions = append(conditions, condition)
	}

	// Ensure consistent ordering of aggregated conditions
	slices.SortFunc(conditions, func(a, b metav1.Condition) int {
		return strings.Compare(a.Type, b.Type)
	})
	return conditions
}

type aggregateCondition struct {
	numTrue, numFalse, numUnknown int
	message                       string
}

func newAggregateAvailableCondition(aggCondition aggregateCondition, generation int64, total int) metav1.Condition {
	switch {
	case aggCondition.numFalse > 0:
		return metav1.Condition{
			Type:               workapiv1.WorkAvailable,
			Status:             metav1.ConditionFalse,
			Reason:             "ResourcesNotAvailable",
			ObservedGeneration: generation,
			Message:            fmt.Sprintf("%d of %d resources are not available", aggCondition.numFalse, total),
		}
	case aggCondition.numUnknown > 0:
		return metav1.Condition{
			Type:               workapiv1.WorkAvailable,
			Status:             metav1.ConditionUnknown,
			Reason:             "ResourcesStatusUnknown",
			ObservedGeneration: generation,
			Message:            fmt.Sprintf("%d of %d resources have unknown status", aggCondition.numUnknown, total),
		}
	case aggCondition.numTrue == 0:
		return metav1.Condition{
			Type:               workapiv1.WorkAvailable,
			Status:             metav1.ConditionUnknown,
			Reason:             "ResourcesStatusUnknown",
			ObservedGeneration: generation,
			Message:            "cannot get any available resource ",
		}
	default:
		return metav1.Condition{
			Type:               workapiv1.WorkAvailable,
			Status:             metav1.ConditionTrue,
			Reason:             "ResourcesAvailable",
			ObservedGeneration: generation,
			Message:            "All resources are available",
		}
	}
}

func newAggregateConditionFromRules(conditionType string, generation int64, agg aggregateCondition) metav1.Condition {
	var status metav1.ConditionStatus

	// Progressing uses opposite logic: True if ANY resource is progressing
	if conditionType == workapiv1.WorkProgressing {
		switch {
		case agg.numTrue > 0:
			status = metav1.ConditionTrue
		case agg.numUnknown > 0:
			status = metav1.ConditionUnknown
		default:
			status = metav1.ConditionFalse
		}
	} else {
		// Default logic: False wins, then Unknown, then True
		switch {
		case agg.numFalse > 0:
			status = metav1.ConditionFalse
		case agg.numUnknown > 0 || agg.numTrue == 0:
			status = metav1.ConditionUnknown
		default:
			status = metav1.ConditionTrue
		}
	}

	// Use manifest condition message if there is only one, otherwise use generic message
	numTotal := agg.numFalse + agg.numTrue + agg.numUnknown
	var message string
	if numTotal == 1 && agg.message != "" {
		message = agg.message
	} else {
		message = fmt.Sprintf("Aggregated from %d manifest conditions", numTotal)
	}

	return metav1.Condition{
		Type:               conditionType,
		Status:             status,
		Reason:             "ConditionRulesAggregated",
		ObservedGeneration: generation,
		Message:            message,
	}
}
