package scrape

import (
	"context"
	"sync"
	"time"

	"github.com/rs/zerolog"
)

// Reconciler is the contract a subsystem implements when it wants to
// register a periodic catch-up pass — events the webhook stream may have
// missed, state the cron didn't pick up.
//
// Phase 4 ships the loop and an empty registry. Phase 5+ (autobrr, qbit,
// crossseed) plug in concrete reconcilers that pull recent state from each
// tool's REST API and emit events for any state changes the webhook stream
// didn't deliver.
type Reconciler interface {
	// Name is the operator-facing identifier; appears in logs.
	Name() string

	// Reconcile runs one pass. Errors are logged but do not stop the loop.
	Reconcile(ctx context.Context) error
}

// ReconcileLoop is the periodic runner. One per process; subsystems
// register themselves via Add() in main.go.
type ReconcileLoop struct {
	interval time.Duration
	logger   zerolog.Logger

	mu          sync.Mutex
	reconcilers []Reconciler
	stop        context.CancelFunc
	wg          sync.WaitGroup
}

// NewReconcileLoop constructs a stopped loop. interval defaults to
// 15 minutes when zero — slow enough to be cheap, fast enough to catch a
// missed webhook within one cycle.
func NewReconcileLoop(interval time.Duration, logger zerolog.Logger) *ReconcileLoop {
	if interval <= 0 {
		interval = 15 * time.Minute
	}
	return &ReconcileLoop{
		interval: interval,
		logger:   logger.With().Str("component", "reconciler").Logger(),
	}
}

// Add registers a Reconciler. Safe to call before Start; calling after
// Start adds it to the next cycle.
func (l *ReconcileLoop) Add(r Reconciler) {
	l.mu.Lock()
	l.reconcilers = append(l.reconcilers, r)
	l.mu.Unlock()
}

// Start launches the loop. Returns immediately.
func (l *ReconcileLoop) Start(parent context.Context) {
	ctx, cancel := context.WithCancel(parent)
	l.mu.Lock()
	l.stop = cancel
	l.mu.Unlock()
	l.wg.Add(1)
	go l.run(ctx)
}

// Stop terminates the loop and waits for the current cycle (if any).
func (l *ReconcileLoop) Stop() {
	l.mu.Lock()
	if l.stop != nil {
		l.stop()
	}
	l.mu.Unlock()
	l.wg.Wait()
}

func (l *ReconcileLoop) run(ctx context.Context) {
	defer l.wg.Done()
	t := time.NewTicker(l.interval)
	defer t.Stop()
	for {
		select {
		case <-ctx.Done():
			return
		case <-t.C:
			l.runOnce(ctx)
		}
	}
}

func (l *ReconcileLoop) runOnce(ctx context.Context) {
	l.mu.Lock()
	rs := append([]Reconciler{}, l.reconcilers...)
	l.mu.Unlock()
	for _, r := range rs {
		start := time.Now()
		err := r.Reconcile(ctx)
		dur := time.Since(start)
		if err != nil {
			l.logger.Warn().Err(err).Str("reconciler", r.Name()).Dur("duration", dur).Msg("reconcile failed")
		} else {
			l.logger.Debug().Str("reconciler", r.Name()).Dur("duration", dur).Msg("reconcile ok")
		}
	}
}
