package scheduler

// wiring.go — construction helpers for starting the scheduler in the server
// monolith before tasks #5 (LifecycleService) and #14 (ProvisionService) are
// fully implemented.
//
// BuildStubConfig returns a ready-to-use Config whose lifecycle and provision
// dependencies are replaced with no-op stubs.  The scheduler's three loops
// still run and hold leader leases, but detect/orchestrate ticks are no-ops.
// Replace the stubs with real implementations once #5/#14 are ready.

import (
	"context"
	"database/sql"

	"github.com/redis/go-redis/v9"

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

// BuildRealConfig wires the scheduler against the real lifecycle (SQL-backed)
// and provision (#14) services, replacing the no-op stubs of BuildStubConfig.
//
//   - detect/orchestrate lifecycle → SQLLifecycle over the nodes table + LoadCache.
//   - provision               → provisionAdapter over the real provision.Service.
//
// Without vendor credentials the provision service's CreateNode fails (a pending
// replacement simply stays pending), but all lifecycle reads/transitions operate
// on real node rows. Capacity monitoring is left disabled (nil) as in the stub.
//
// e2e behaviour (auto-detect → drain → replace → provision) is verifiable only
// against a real node fleet with vendor provisioning configured.
func BuildRealConfig(
	rdb *redis.Client,
	probeStore *probe.Store,
	db *sql.DB,
	loads *nodes.LoadCache,
	provSvc provisionSvc,
	provStore provisionResolver,
) Config {
	sqlLC := NewSQLLifecycle(db, loads)
	detectLC := NewDetectLifecycle(sqlLC)
	orchLC := NewOrchestrateLifecycle(sqlLC)
	prov := NewProvisionAdapter(provSvc, provStore)

	streaks := detect.NewStreakStore(rdb)
	engine := detect.NewEngine(probeStore, detectLC, streaks, rdb, nil, nil)

	replacer := orchestrate.NewReplacer(orchestrate.Config{
		RDB:      rdb,
		Prov:     prov,
		LC:       orchLC,
		Snaps:    probeStore,
		Breaker:  nil, // StubBreaker default until 15F policy tuned
		Notifier: nil, // LogNotifier default
		Clock:    nil,
	})
	grayscale := orchestrate.NewGrayscale(rdb, orchLC, nil)

	return Config{
		RDB:       rdb,
		Engine:    engine,
		Replacer:  replacer,
		Grayscale: grayscale,
	}
}

// BuildStubConfig constructs a scheduler Config wired entirely with in-process
// stubs.  Useful for:
//   - Starting the scheduler before tasks #5 / #14 are delivered (no-op ticks).
//   - Integration tests that need a scheduler goroutine to be running.
//
// To swap in real implementations once they exist:
//
//	cfg := scheduler.BuildStubConfig(rdb, probeStore)
//	cfg.Engine = detect.NewEngine(realLC, ...)
//	cfg.Replacer = orchestrate.NewReplacer(orchestrate.Config{LC: realLC, Prov: realProv, ...})
//	cfg.Grayscale = orchestrate.NewGrayscale(rdb, realLC, nil)
//	sched := scheduler.New(cfg)
func BuildStubConfig(rdb *redis.Client, probeStore *probe.Store) Config {
	// Stub lifecycle for 15D — no nodes registered, so Tick is a safe no-op.
	stubDetectLC := detect.NewMockLifecycle(nil)

	// Stub lifecycle and provision for 15E.
	stubOrchLC := &stubOrchLC{}
	stubProv := &stubProvision{}

	streaks := detect.NewStreakStore(rdb)
	engine := detect.NewEngine(
		probeStore,   // 15A probe snapshot reader
		stubDetectLC, // 15D lifecycle (stub until #5)
		streaks,
		rdb,
		nil, // notifier — LogNotifier used by default
		nil, // config — DefaultConfig used
	)

	replacer := orchestrate.NewReplacer(orchestrate.Config{
		RDB:      rdb,
		Prov:     stubProv,   // provision stub until #14
		LC:       stubOrchLC, // lifecycle stub until #5
		Snaps:    probeStore,
		Breaker:  nil, // StubBreaker used by default
		Notifier: nil, // LogNotifier used by default
		Clock:    nil, // RealClock used by default
	})

	grayscale := orchestrate.NewGrayscale(rdb, stubOrchLC, nil)

	return Config{
		RDB:       rdb,
		Engine:    engine,
		Replacer:  replacer,
		Grayscale: grayscale,
		// Capacity, Notifier, ProbeStore, Prober, Targets: nil → stubs/disabled
	}
}

// ─────────────────────────────────────────────────────────────────────────────
// Stub implementations for orchestrate.LifecycleService and ProvisionService
//
// These stubs satisfy the interface contracts while doing nothing harmful.
// Replace with the real services (#5 / #14) when those tasks are complete.
// ─────────────────────────────────────────────────────────────────────────────

// stubOrchLC implements orchestrate.LifecycleService as a no-op.
// GetNode always returns nil (node not found); all mutating calls succeed
// silently.  This prevents any orchestration work from proceeding until a real
// lifecycle service is wired in.
type stubOrchLC struct{}

func (stubOrchLC) GetNode(_ context.Context, _ string) (*orchestrate.NodeInfo, error) {
	return nil, nil // "node not found" → orchestrate skips this replacement
}

func (stubOrchLC) TransitionStatus(_ context.Context, _, _, _ string, _ map[string]any) (int, error) {
	return 1, nil
}

func (stubOrchLC) SetWeight(_ context.Context, _ string, _ int) error       { return nil }
func (stubOrchLC) BumpVersion(_ context.Context) error                      { return nil }
func (stubOrchLC) WriteAuditLog(_ context.Context, _, _, _, _ string) error { return nil }

// stubProvision implements orchestrate.ProvisionService as a no-op.
// CreateNode always returns an error so pending replacements stay pending
// rather than silently creating phantom resources.
type stubProvision struct{}

func (stubProvision) CreateNode(_ context.Context, _ orchestrate.NodeSpec, _ string) (string, error) {
	return "", context.DeadlineExceeded // signal "not ready" without alarming
}

func (stubProvision) DestroyNode(_ context.Context, _ string) error { return nil }

func (stubProvision) RotateIP(_ context.Context, _ string) (string, error) { return "", nil }

func (stubProvision) ListProviders(_ context.Context, _, _ string) ([]orchestrate.ProviderInfo, error) {
	return nil, nil
}
