// Package scheduler wires together the 15D detection engine, 15E orchestration
// engine, 15F capacity service, 15A probe store, and 15G notifier into three
// leader-elected goroutine loops (DetectLoop / OrchestrateLoop / CapacityLoop),
// plus an optional unguarded 15C third-party prober goroutine.
//
// This file declares the narrow dependency interfaces used by Scheduler.  Real
// implementations satisfy these interfaces; test mocks do too.
package scheduler

import (
	"context"
	"log/slog"

	"github.com/wangjia/pangolin/server/internal/scheduler/probe"
)

// ─────────────────────────────────────────────────────────────────────────────
// 15D – detection engine
// ─────────────────────────────────────────────────────────────────────────────

// DetectEngine is the 15D interface.  *detect.Engine satisfies it.
// Tick is called by DetectLoop on every 5-minute cycle.
type DetectEngine interface {
	Tick(ctx context.Context) error
}

// ─────────────────────────────────────────────────────────────────────────────
// 15E – orchestration engine
// ─────────────────────────────────────────────────────────────────────────────

// OrchestrateReplacer is the 15E replacement-orchestration interface.
// *orchestrate.Replacer satisfies it.
// Tick is called by OrchestrateLoop every 30 seconds.
type OrchestrateReplacer interface {
	Tick(ctx context.Context) error
}

// OrchestrateGrayscale is the 15E grayscale warm-up interface.
// *orchestrate.Grayscale satisfies it.
// Advance is called by CapacityLoop every 1–2 minutes.
type OrchestrateGrayscale interface {
	Advance(ctx context.Context) error
}

// ─────────────────────────────────────────────────────────────────────────────
// 15F – capacity service (stub until task 15F is delivered)
// ─────────────────────────────────────────────────────────────────────────────

// CapacityService is the 15F interface for watermark/quota checking and
// circuit-breaker time-decay.  Replace StubCapacityService with the real
// 15F implementation once that task is complete.
type CapacityService interface {
	// Check verifies that the node-pool watermark and per-region quotas are
	// within acceptable bounds.  Non-fatal: errors are logged, not returned.
	Check(ctx context.Context) error

	// Decay applies time-based decay to circuit-breaker event counters so that
	// temporary bursts do not permanently block replacements.
	Decay(ctx context.Context) error
}

// StubCapacityService is a no-op CapacityService used until task 15F is ready.
type StubCapacityService struct{}

// Check implements CapacityService (always succeeds).
func (StubCapacityService) Check(_ context.Context) error { return nil }

// Decay implements CapacityService (always succeeds).
func (StubCapacityService) Decay(_ context.Context) error { return nil }

// ─────────────────────────────────────────────────────────────────────────────
// 15A – probe state reader
// ─────────────────────────────────────────────────────────────────────────────

// ProbeStateReader reads the heartbeat liveness of probe agents from the 15A
// Redis store.  *probe.Store satisfies it (via AliveProbes).
type ProbeStateReader interface {
	// AliveProbes returns the IDs of probe agents that have sent a heartbeat
	// within the last heartbeatTTL window (15 min).  An empty result means
	// "no recent heartbeats", not "all probes are down".
	AliveProbes(ctx context.Context) ([]string, error)
}

// ─────────────────────────────────────────────────────────────────────────────
// 15C – third-party prober
// ─────────────────────────────────────────────────────────────────────────────

// ThirdPartyProber is the 15C interface.
// *probe.AliyunSyntheticAgent satisfies it.
type ThirdPartyProber interface {
	// RunOnce issues a full multi-ISP probe cycle for all targets and writes
	// results to Redis via the 15A probe store.
	RunOnce(ctx context.Context, targets []probe.ProbeTarget) error
}

// TargetProvider supplies the current list of active nodes to be probed via
// the 15C third-party service.  Typically backed by a DB query; a static
// implementation is available as StaticTargetProvider.
type TargetProvider interface {
	ListProbeTargets(ctx context.Context) ([]probe.ProbeTarget, error)
}

// StaticTargetProvider returns a fixed, pre-configured list of probe targets.
// Useful for initial deployment and integration tests before a DB-backed
// provider is available.
type StaticTargetProvider struct {
	Targets []probe.ProbeTarget
}

// ListProbeTargets implements TargetProvider.
func (p StaticTargetProvider) ListProbeTargets(_ context.Context) ([]probe.ProbeTarget, error) {
	return p.Targets, nil
}

// ─────────────────────────────────────────────────────────────────────────────
// 15G – notifier
// ─────────────────────────────────────────────────────────────────────────────

// Notifier is the 15G alerting interface.  The real implementation sends a
// Telegram (or similar) alert; LogNotifier is the stub.
type Notifier interface {
	NotifyFault(ctx context.Context, nodeID, reason string) error
}

// LogNotifier is a Notifier stub that writes to slog.
// Used when no real 15G notifier is wired.
type LogNotifier struct{}

// NotifyFault implements Notifier.
func (LogNotifier) NotifyFault(_ context.Context, nodeID, reason string) error {
	slog.Warn("scheduler: node fault — manual review required",
		"node_id", nodeID,
		"reason", reason,
	)
	return nil
}
