package clusterconfig

import (
	"context"
	"time"

	"github.com/go-logr/logr"
	"k8s.io/apimachinery/pkg/fields"
	"k8s.io/apimachinery/pkg/util/wait"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
)

type GarbageCollector struct {
	Log logr.Logger
	client.Client
	period            time.Duration
	configRevisionTTL time.Duration
}

func NewGarbageCollector(mgr ctrl.Manager, logger logr.Logger, period time.Duration, ttl time.Duration) *GarbageCollector {
	return &GarbageCollector{
		Log:               logger,
		Client:            mgr.GetClient(),
		period:            period,
		configRevisionTTL: ttl,
	}
}

func (c *GarbageCollector) Start(ctx context.Context) error {
	wait.UntilWithContext(ctx, c.performGC, c.period)
	c.Log.Info("Shutdown signal received, stopping gc worker")
	return nil
}

func (c *GarbageCollector) NeedLeaderElection() bool {
	return true
}

func (c *GarbageCollector) SetupWithManager(mgr ctrl.Manager) error {
	return mgr.Add(c)
}

func (c *GarbageCollector) performGC(ctx context.Context) {
	c.Log.Info("Performing garbage collection")
	crs := &cloudv1.ConfigRevisionList{}
	err := c.Client.List(ctx, crs, &client.ListOptions{})
	if err != nil {
		c.Log.Error(err, "Failed to list ConfigRevisions")
		return
	}
	// TODO: maybe parallel
	for _, cr := range crs.Items {
		cs := &cloudv1.ClusterList{}
		if err = c.Client.List(context.TODO(), cs, &client.ListOptions{
			FieldSelector: fields.OneTermEqualSelector(ConfigRevisionRefField, cr.Name),
		}); err != nil {
			continue
		}
		activeAt, err := time.Parse(time.RFC3339, cr.Annotations[cloudv1.ConfigRevisionActiveAnnotation])
		if err != nil {
			c.Log.Info("Failed to parse active time in annotation", "error", err)
			// consider timeout if activeAt not set
			activeAt = time.Now().Add(-c.configRevisionTTL)
		}
		// if reference count is 0 and the time since last update is greater than the TTL, delete the CR
		if len(cs.Items) == 0 && activeAt.Add(c.configRevisionTTL).Before(time.Now()) {
			c.Log.Info("Deleting config revision", "name", cr.Name)
			if err := c.Client.Delete(context.TODO(), &cr); err != nil {
				c.Log.Error(err, "Failed to delete config revision", "name", cr.Name)
			}
		}
	}
}
