// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause

//go:build !plan9

package kube

import (
	"slices"
	"time"

	"go.uber.org/zap"
	xslices "golang.org/x/exp/slices"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	tsapi "tailscale.com/k8s-operator/apis/v1alpha1"
	"tailscale.com/tstime"
)

// SetConnectorCondition ensures that Connector status has a condition with the
// given attributes. LastTransitionTime gets set every time condition's status
// changes.
func SetConnectorCondition(cn *tsapi.Connector, conditionType tsapi.ConnectorConditionType, status metav1.ConditionStatus, reason, message string, gen int64, clock tstime.Clock, logger *zap.SugaredLogger) {
	conds := updateCondition(cn.Status.Conditions, conditionType, status, reason, message, gen, clock, logger)
	cn.Status.Conditions = conds
}

// RemoveConnectorCondition will remove condition of the given type if it exists.
func RemoveConnectorCondition(conn *tsapi.Connector, conditionType tsapi.ConnectorConditionType) {
	conn.Status.Conditions = slices.DeleteFunc(conn.Status.Conditions, func(cond tsapi.ConnectorCondition) bool {
		return cond.Type == conditionType
	})
}

// SetProxyClassCondition ensures that ProxyClass status has a condition with the
// given attributes. LastTransitionTime gets set every time condition's status
// changes.
func SetProxyClassCondition(pc *tsapi.ProxyClass, conditionType tsapi.ConnectorConditionType, status metav1.ConditionStatus, reason, message string, gen int64, clock tstime.Clock, logger *zap.SugaredLogger) {
	conds := updateCondition(pc.Status.Conditions, conditionType, status, reason, message, gen, clock, logger)
	pc.Status.Conditions = conds
}

// SetDNSConfigCondition ensures that DNSConfig status has a condition with the
// given attributes. LastTransitionTime gets set every time condition's status
// changes
func SetDNSConfigCondition(dnsCfg *tsapi.DNSConfig, conditionType tsapi.ConnectorConditionType, status metav1.ConditionStatus, reason, message string, gen int64, clock tstime.Clock, logger *zap.SugaredLogger) {
	conds := updateCondition(dnsCfg.Status.Conditions, conditionType, status, reason, message, gen, clock, logger)
	dnsCfg.Status.Conditions = conds
}

func updateCondition(conds []tsapi.ConnectorCondition, conditionType tsapi.ConnectorConditionType, status metav1.ConditionStatus, reason, message string, gen int64, clock tstime.Clock, logger *zap.SugaredLogger) []tsapi.ConnectorCondition {
	newCondition := tsapi.ConnectorCondition{
		Type:               conditionType,
		Status:             status,
		Reason:             reason,
		Message:            message,
		ObservedGeneration: gen,
	}

	nowTime := metav1.NewTime(clock.Now().Truncate(time.Second))
	newCondition.LastTransitionTime = &nowTime

	idx := xslices.IndexFunc(conds, func(cond tsapi.ConnectorCondition) bool {
		return cond.Type == conditionType
	})

	if idx == -1 {
		conds = append(conds, newCondition)
		return conds
	}

	cond := conds[idx] // update the existing condition

	// If this update doesn't contain a state transition, don't update last
	// transition time.
	if cond.Status == status {
		newCondition.LastTransitionTime = cond.LastTransitionTime
	} else {
		logger.Infof("Status change for condition %s from %s to %s", conditionType, cond.Status, status)
	}
	conds[idx] = newCondition
	return conds
}

func ProxyClassIsReady(pc *tsapi.ProxyClass) bool {
	idx := xslices.IndexFunc(pc.Status.Conditions, func(cond tsapi.ConnectorCondition) bool {
		return cond.Type == tsapi.ProxyClassready
	})
	if idx == -1 {
		return false
	}
	cond := pc.Status.Conditions[idx]
	return cond.Status == metav1.ConditionTrue && cond.ObservedGeneration == pc.Generation
}

func DNSCfgIsReady(cfg *tsapi.DNSConfig) bool {
	idx := xslices.IndexFunc(cfg.Status.Conditions, func(cond tsapi.ConnectorCondition) bool {
		return cond.Type == tsapi.NameserverReady
	})
	if idx == -1 {
		return false
	}
	cond := cfg.Status.Conditions[idx]
	return cond.Status == metav1.ConditionTrue && cond.ObservedGeneration == cfg.Generation
}
