package worker

import (
	"context"
	"sync"
	"time"

	"github.com/orchard9/rdev/internal/logging"
)

// CheckoutCleanupService defines the interface for checkout cleanup operations.
// This allows the worker to depend on the service interface rather than the concrete type.
type CheckoutCleanupService interface {
	CleanupExpired(ctx context.Context) (int, error)
}

// CheckoutCleanup runs periodic cleanup of expired checkouts.
// Expired checkouts have their Gitea tokens revoked and status updated.
type CheckoutCleanup struct {
	service         CheckoutCleanupService
	cleanupInterval time.Duration

	ctx    context.Context
	cancel context.CancelFunc
	wg     sync.WaitGroup
}

// CheckoutCleanupConfig holds configuration for checkout cleanup.
type CheckoutCleanupConfig struct {
	// CleanupInterval is how often to run cleanup.
	// Default: 5 minutes.
	CleanupInterval time.Duration
}

// DefaultCheckoutCleanupConfig returns sensible defaults.
func DefaultCheckoutCleanupConfig() *CheckoutCleanupConfig {
	return &CheckoutCleanupConfig{
		CleanupInterval: 5 * time.Minute,
	}
}

// NewCheckoutCleanup creates a new checkout cleanup worker.
func NewCheckoutCleanup(service CheckoutCleanupService, cfg *CheckoutCleanupConfig) *CheckoutCleanup {
	if cfg == nil {
		cfg = DefaultCheckoutCleanupConfig()
	}

	ctx, cancel := context.WithCancel(context.Background())

	return &CheckoutCleanup{
		service:         service,
		cleanupInterval: cfg.CleanupInterval,
		ctx:             ctx,
		cancel:          cancel,
	}
}

// Start begins the cleanup loop.
func (c *CheckoutCleanup) Start() {
	log := logging.FromContext(c.ctx).WithWorker("checkout-cleanup")
	log.Info("checkout cleanup started",
		"cleanup_interval", c.cleanupInterval,
	)

	c.wg.Add(1)
	go c.cleanupLoop()
}

// Stop gracefully shuts down the cleanup worker.
func (c *CheckoutCleanup) Stop() {
	log := logging.FromContext(c.ctx).WithWorker("checkout-cleanup")
	log.Info("checkout cleanup stopping")
	c.cancel()
	c.wg.Wait()
	log.Info("checkout cleanup stopped")
}

// cleanupLoop runs periodic cleanup.
func (c *CheckoutCleanup) cleanupLoop() {
	defer c.wg.Done()

	// Run immediately on start
	c.runCleanup()

	ticker := time.NewTicker(c.cleanupInterval)
	defer ticker.Stop()

	for {
		select {
		case <-c.ctx.Done():
			return
		case <-ticker.C:
			c.runCleanup()
		}
	}
}

// runCleanup marks expired checkouts and revokes their tokens.
func (c *CheckoutCleanup) runCleanup() {
	ctx, cancel := context.WithTimeout(c.ctx, TimeoutMaintenance)
	defer cancel()

	log := logging.FromContext(ctx).WithWorker("checkout-cleanup")

	cleaned, err := c.service.CleanupExpired(ctx)
	if err != nil {
		log.Error("failed to cleanup expired checkouts",
			logging.FieldError, err,
		)
		return
	}

	if cleaned > 0 {
		log.Info("cleaned up expired checkouts",
			"count", cleaned,
		)
	}
}
