// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0

package meta

import (
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

const (
	// ReadyCondition indicates the resource is ready and fully reconciled.
	ReadyCondition       string = "Ready"
	CordonedCondition    string = "Cordoned"
	TerminatingCondition string = "Terminating"
	NotReadyCondition    string = "NotReady"

	AssignedCondition  string = "Assigned"
	BoundCondition     string = "Bound"
	ExhaustedCondition string = "Exhausted"

	// FailedReason indicates a condition or event observed a failure (Claim Rejected).
	SucceededReason               string = "Succeeded"
	FailedReason                  string = "Failed"
	ActiveReason                  string = "Active"
	CordonedReason                string = "Cordoned"
	TerminatingReason             string = "Terminating"
	ReconcilingReason             string = "Reconciling"
	PoolExhaustedReason           string = "PoolExhausted"
	QueueExhaustedReason          string = "QueueExhausted"
	NamespaceExhaustedReason      string = "NamespaceExhausted"
	NoExhaustionsReason           string = "NoExhaustions"
	InUseReason                   string = "InUse"
	UnusedReason                  string = "Unused"
	PendingUnmanagedContentReason string = "PendingUnmanagedContent"
)

func IsStatusConditionTrue(conditions ConditionList, conditionType string) bool {
	cond := conditions.GetConditionByType(conditionType)
	if cond == nil {
		return false
	}

	return cond.Status == metav1.ConditionTrue
}

// +kubebuilder:object:generate=true

type ConditionList []Condition

// Adds a condition by type.
func (c *ConditionList) GetConditionByType(conditionType string) *Condition {
	for i := range *c {
		if (*c)[i].Type == conditionType {
			return &(*c)[i]
		}
	}

	return nil
}

// Adds a condition by type.
func (c *ConditionList) UpdateConditionByType(condition Condition) {
	for i, cond := range *c {
		if cond.Type == condition.Type {
			(*c)[i].UpdateCondition(condition)

			return
		}
	}

	*c = append(*c, condition)
}

func (c *ConditionList) UpdateConditionByTypeWithStatus(condition Condition) (updated bool) {
	for i, cond := range *c {
		if cond.Type == condition.Type {
			return (*c)[i].UpdateCondition(condition)
		}
	}

	*c = append(*c, condition)

	return true
}

// Removes a condition by type.
func (c *ConditionList) RemoveConditionByType(condition string) {
	if c == nil {
		return
	}

	filtered := make(ConditionList, 0, len(*c))

	for _, cond := range *c {
		if cond.Type != condition {
			filtered = append(filtered, cond)
		}
	}

	*c = filtered
}

// +kubebuilder:object:generate=true
type Condition metav1.Condition

func NewReadyCondition(obj client.Object) Condition {
	return Condition{
		Type:               ReadyCondition,
		Status:             metav1.ConditionTrue,
		Reason:             SucceededReason,
		Message:            "reconciled",
		LastTransitionTime: metav1.Now(),
	}
}

func NewCordonedCondition(obj client.Object) Condition {
	return Condition{
		Type:               CordonedCondition,
		Status:             metav1.ConditionFalse,
		Reason:             ActiveReason,
		Message:            "not cordoned",
		LastTransitionTime: metav1.Now(),
	}
}

func NewExhaustedCondition(obj client.Object) Condition {
	return Condition{
		Type:               ExhaustedCondition,
		ObservedGeneration: obj.GetGeneration(),
		LastTransitionTime: metav1.Now(),
		Reason:             NoExhaustionsReason,
		Message:            "no exhaustions",
		Status:             metav1.ConditionFalse,
	}
}

func NewBoundCondition(obj client.Object) Condition {
	return Condition{
		Type:               BoundCondition,
		ObservedGeneration: obj.GetGeneration(),
		LastTransitionTime: metav1.Now(),
		Message:            "bound by pool",
	}
}

func NewAssignedCondition(obj client.Object) Condition {
	return Condition{
		Type:               AssignedCondition,
		ObservedGeneration: obj.GetGeneration(),
		LastTransitionTime: metav1.Now(),
		Message:            "assigned to pool",
	}
}

func NewReadyConditionReconcilingReason(obj client.Object) Condition {
	return Condition{
		Type:               ReadyCondition,
		Status:             metav1.ConditionUnknown,
		Reason:             ReconcilingReason,
		Message:            "processing",
		LastTransitionTime: metav1.Now(),
	}
}

func NewTerminatingConditionReason(obj client.Object) Condition {
	return Condition{
		Type:               TerminatingCondition,
		Status:             metav1.ConditionTrue,
		Reason:             SucceededReason,
		Message:            "cleaning up",
		LastTransitionTime: metav1.Now(),
	}
}

// Disregards fields like LastTransitionTime and Version, which are not relevant for the API.
func (c *Condition) UpdateCondition(condition Condition) (updated bool) {
	if condition.Type == c.Type &&
		condition.Status == c.Status &&
		condition.Reason == c.Reason &&
		condition.Message == c.Message &&
		condition.ObservedGeneration == c.ObservedGeneration {
		return false
	}

	if condition.Status != c.Status {
		c.LastTransitionTime = metav1.Now()
	}

	c.Type = condition.Type
	c.Status = condition.Status
	c.Reason = condition.Reason
	c.Message = condition.Message
	c.ObservedGeneration = condition.ObservedGeneration
	c.LastTransitionTime = condition.LastTransitionTime

	return true
}
