package worker

import (
	"context"
	"sync"
	"time"

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

// SessionCleanupService defines the interface for session cleanup operations.
type SessionCleanupService interface {
	CleanupExpired(ctx context.Context) (int, error)
}

// SessionCleanup runs periodic cleanup of expired sessions.
// Expired sessions have their preview resources torn down and checkout tokens revoked.
type SessionCleanup struct {
	service         SessionCleanupService
	cleanupInterval time.Duration

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

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

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

// NewSessionCleanup creates a new session cleanup worker.
func NewSessionCleanup(service SessionCleanupService, cfg *SessionCleanupConfig) *SessionCleanup {
	if cfg == nil {
		cfg = DefaultSessionCleanupConfig()
	}

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

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

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

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

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

// cleanupLoop runs periodic cleanup.
func (c *SessionCleanup) 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 tears down expired sessions.
func (c *SessionCleanup) runCleanup() {
	ctx, cancel := context.WithTimeout(c.ctx, TimeoutMaintenance)
	defer cancel()

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

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

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