package v1

import (
	"time"

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

// IsStatusConditionTrue returns true when the conditionType is present and set to `metav1.ConditionTrue`
func IsStatusConditionTrue(conditions []ClusterCondition, conditionType ClusterConditionType) bool {
	return IsStatusConditionPresentAndEqual(conditions, conditionType, ConditionTrue)
}

// IsStatusConditionPresentAndEqual returns true when conditionType is present and equal to status.
func IsStatusConditionPresentAndEqual(conditions []ClusterCondition, conditionType ClusterConditionType, status ConditionStatus) bool {
	for _, condition := range conditions {
		if condition.Type == conditionType {
			return condition.Status == status
		}
	}
	return false
}

// FindStatusCondition finds the conditionType in conditions.
func FindStatusCondition(conditions []ClusterCondition, conditionType ClusterConditionType) *ClusterCondition {
	for i := range conditions {
		if conditions[i].Type == conditionType {
			return &conditions[i]
		}
	}

	return nil
}

// SetStatusCondition sets the corresponding condition in conditions to newCondition.
// conditions must be non-nil.
// 1. if the condition of the specified type already exists (all fields of the existing condition are updated to
//    newCondition, LastTransitionTime is set to now if the new status differs from the old status)
// 2. if a condition of the specified type does not exist (LastTransitionTime is set to now() if unset, and newCondition is appended)
func SetStatusCondition(conditions *[]ClusterCondition, newCondition ClusterCondition) {
	if conditions == nil {
		return
	}
	existingCondition := FindStatusCondition(*conditions, newCondition.Type)
	if existingCondition == nil {
		if newCondition.LastTransitionTime.IsZero() {
			newCondition.LastTransitionTime = metav1.NewTime(time.Now())
		}
		if newCondition.LastProbeTime.IsZero() {
			newCondition.LastProbeTime = metav1.NewTime(time.Now())
		}
		*conditions = append(*conditions, newCondition)
		return
	}

	if existingCondition.Status != newCondition.Status {
		existingCondition.Status = newCondition.Status
		if !newCondition.LastTransitionTime.IsZero() {
			existingCondition.LastTransitionTime = newCondition.LastTransitionTime
		} else {
			existingCondition.LastTransitionTime = metav1.NewTime(time.Now())
		}
	}

	existingCondition.Reason = newCondition.Reason
	existingCondition.Message = newCondition.Message
}

// GetClusterCondition extracts the provided condition from the given status and returns that.
// Returns nil and -1 if the condition is not present, and the index of the located condition.
func GetClusterCondition(status *ClusterStatus, conditionType ClusterConditionType) (int, *ClusterCondition) {
	if status == nil {
		return -1, nil
	}
	return GetClusterConditionFromList(status.Conditions, conditionType)
}

// GetClusterConditionFromList extracts the provided condition from the given list of condition and
// returns the index of the condition and the condition. Returns -1 and nil if the condition is not present.
func GetClusterConditionFromList(conditions []ClusterCondition, conditionType ClusterConditionType) (int, *ClusterCondition) {
	if conditions == nil {
		return -1, nil
	}
	for i := range conditions {
		if conditions[i].Type == conditionType {
			return i, &conditions[i]
		}
	}
	return -1, nil
}

// UpdateClusterCondition updates existing cluster condition or creates a new one. Sets LastTransitionTime to now if the
// status has changed.
// Returns true if cluster condition has changed or has been added.
func UpdateClusterCondition(status *ClusterStatus, condition *ClusterCondition) bool {
	condition.LastTransitionTime = metav1.Now()
	// Try to find this cluster condition.
	conditionIndex, oldCondition := GetClusterCondition(status, condition.Type)

	if oldCondition == nil {
		// We are adding new cluster condition.
		status.Conditions = append(status.Conditions, *condition)
		return true
	}
	// We are updating an existing condition, so we need to check if it has changed.
	if condition.Status == oldCondition.Status {
		condition.LastTransitionTime = oldCondition.LastTransitionTime
	}

	isEqual := condition.Status == oldCondition.Status &&
		condition.Reason == oldCondition.Reason &&
		condition.Message == oldCondition.Message &&
		condition.LastProbeTime.Equal(&oldCondition.LastProbeTime) &&
		condition.LastTransitionTime.Equal(&oldCondition.LastTransitionTime)

	status.Conditions[conditionIndex] = *condition
	// Return true if one of the fields have changed.
	return !isEqual
}
