// Copyright 2021-2024 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package conditionsutil

import (
	"slices"
	"sort"

	apiequality "k8s.io/apimachinery/pkg/api/equality"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"go.pinniped.dev/internal/plog"
)

// Some common reasons and messages shared by conditions of various resources.
const (
	ReasonSuccess          = "Success"
	ReasonNotReady         = "NotReady"
	ReasonUnableToValidate = "UnableToValidate"
	ReasonInvalidIssuerURL = "InvalidIssuerURL"

	MessageUnableToValidate = "unable to validate; see other conditions for details"
)

// MergeConditions merges newConditions into existingConditionsToUpdate.
// Note that lastTransitionTime refers to the time when the status changed,
// but observedGeneration should be the current generation for all conditions,
// since Pinniped should always check every condition.
// It returns true if any resulting condition has non-true status.
func MergeConditions(
	newConditions []*metav1.Condition,
	existingConditionsToUpdate *[]metav1.Condition,
	observedGeneration int64,
	lastTransitionTime metav1.Time,
	log plog.MinLogger,
) bool {
	for i := range newConditions {
		newCondition := newConditions[i].DeepCopy()
		newCondition.LastTransitionTime = lastTransitionTime
		newCondition.ObservedGeneration = observedGeneration
		if mergeCondition(existingConditionsToUpdate, newCondition) {
			log.Info("updated condition",
				"type", newCondition.Type,
				"status", newCondition.Status,
				"reason", newCondition.Reason,
				"message", newCondition.Message)
		}
	}
	sort.SliceStable(*existingConditionsToUpdate, func(i, j int) bool {
		return (*existingConditionsToUpdate)[i].Type < (*existingConditionsToUpdate)[j].Type
	})
	return HadErrorCondition(newConditions)
}

// mergeCondition merges a new metav1.Condition into a slice of existing conditions. It returns true
// if something other than the LastTransitionTime has been updated.
func mergeCondition(existingConditionsToUpdate *[]metav1.Condition, newCondition *metav1.Condition) bool {
	// Find any existing condition with a matching type.
	index := slices.IndexFunc(*existingConditionsToUpdate, func(condition metav1.Condition) bool {
		return newCondition.Type == condition.Type
	})

	var existingCondition *metav1.Condition
	if index < 0 {
		// If there is no existing condition of this type, append this one and we're done.
		*existingConditionsToUpdate = append(*existingConditionsToUpdate, *newCondition)
		return true
	}

	// Get a pointer to the existing condition
	existingCondition = &(*existingConditionsToUpdate)[index]

	// If the status has not changed, preserve the original lastTransitionTime
	if newCondition.Status == existingCondition.Status {
		newCondition.LastTransitionTime = existingCondition.LastTransitionTime
	}

	changed := !apiequality.Semantic.DeepEqual(existingCondition, newCondition)
	*existingCondition = *newCondition
	return changed
}

func HadErrorCondition(conditions []*metav1.Condition) bool {
	return slices.ContainsFunc(conditions, func(condition *metav1.Condition) bool {
		return condition.Status != metav1.ConditionTrue
	})
}
