package orchestrate_test

import (
	"context"
	"os"
	"testing"

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

// ─────────────────────────────────────────────────────────────────────────────
// Test: Default values
// ─────────────────────────────────────────────────────────────────────────────

func TestDefaultSchedConfig(t *testing.T) {
	cfg := orchestrate.DefaultSchedConfig()

	// Spot-check a representative value from each section.
	if cfg.Detect.DomesticFailNumerator != 2 {
		t.Errorf("Detect.DomesticFailNumerator = %d; want 2", cfg.Detect.DomesticFailNumerator)
	}
	if cfg.Capacity.WatermarkThreshold != 0.70 {
		t.Errorf("Capacity.WatermarkThreshold = %f; want 0.70", cfg.Capacity.WatermarkThreshold)
	}
	if cfg.Capacity.BreakerMinN != 3 {
		t.Errorf("Capacity.BreakerMinN = %d; want 3", cfg.Capacity.BreakerMinN)
	}
	if cfg.Probe.GrayscaleIntervalHours != 6 {
		t.Errorf("Probe.GrayscaleIntervalHours = %d; want 6", cfg.Probe.GrayscaleIntervalHours)
	}
}

// ─────────────────────────────────────────────────────────────────────────────
// Test: YAML file overrides defaults
// ─────────────────────────────────────────────────────────────────────────────

func TestConfigManagerYAMLOverrides(t *testing.T) {
	yaml := `
detect:
  suspect_streak_min: 5
  confirmed_streak_min: 12
capacity:
  watermark_threshold: 0.60
  breaker_min_n: 5
  expected_probe_ids:
    - pb-1
    - pb-2
probe:
  grayscale_interval_hours: 8
  max_attempts: 4
`
	tmpFile, err := writeTempYAML(t, yaml)
	if err != nil {
		t.Fatalf("writeTempYAML: %v", err)
	}

	m := orchestrate.NewConfigManager(tmpFile, nil)
	if err := m.Load(); err != nil {
		t.Fatalf("Load: %v", err)
	}

	cfg := m.Current()

	if cfg.Detect.SuspectStreakMin != 5 {
		t.Errorf("Detect.SuspectStreakMin = %d; want 5", cfg.Detect.SuspectStreakMin)
	}
	if cfg.Detect.ConfirmedStreakMin != 12 {
		t.Errorf("Detect.ConfirmedStreakMin = %d; want 12", cfg.Detect.ConfirmedStreakMin)
	}
	if cfg.Capacity.WatermarkThreshold != 0.60 {
		t.Errorf("Capacity.WatermarkThreshold = %f; want 0.60", cfg.Capacity.WatermarkThreshold)
	}
	if cfg.Capacity.BreakerMinN != 5 {
		t.Errorf("Capacity.BreakerMinN = %d; want 5", cfg.Capacity.BreakerMinN)
	}
	if len(cfg.Capacity.ExpectedProbeIDs) != 2 {
		t.Errorf("ExpectedProbeIDs count = %d; want 2", len(cfg.Capacity.ExpectedProbeIDs))
	} else {
		if cfg.Capacity.ExpectedProbeIDs[0] != "pb-1" || cfg.Capacity.ExpectedProbeIDs[1] != "pb-2" {
			t.Errorf("ExpectedProbeIDs = %v; want [pb-1 pb-2]", cfg.Capacity.ExpectedProbeIDs)
		}
	}
	if cfg.Probe.GrayscaleIntervalHours != 8 {
		t.Errorf("Probe.GrayscaleIntervalHours = %d; want 8", cfg.Probe.GrayscaleIntervalHours)
	}
	if cfg.Probe.MaxAttempts != 4 {
		t.Errorf("Probe.MaxAttempts = %d; want 4", cfg.Probe.MaxAttempts)
	}

	// Unset fields must retain defaults.
	if cfg.Detect.DomesticFailNumerator != 2 {
		t.Errorf("Detect.DomesticFailNumerator should be default 2; got %d", cfg.Detect.DomesticFailNumerator)
	}
}

// ─────────────────────────────────────────────────────────────────────────────
// Test: Environment variable overrides YAML
// ─────────────────────────────────────────────────────────────────────────────

func TestConfigManagerEnvOverridesYAML(t *testing.T) {
	yaml := `
detect:
  suspect_streak_min: 5
capacity:
  breaker_min_n: 7
`
	tmpFile, err := writeTempYAML(t, yaml)
	if err != nil {
		t.Fatalf("writeTempYAML: %v", err)
	}

	// Set env vars AFTER writing YAML to ensure they override.
	t.Setenv("DETECT_SUSPECT_STREAK_MIN", "9")
	t.Setenv("CAP_BREAKER_MIN_N", "15")

	m := orchestrate.NewConfigManager(tmpFile, nil)
	if err := m.Load(); err != nil {
		t.Fatalf("Load: %v", err)
	}

	cfg := m.Current()

	// Env var should override YAML value.
	if cfg.Detect.SuspectStreakMin != 9 {
		t.Errorf("Detect.SuspectStreakMin = %d; want 9 (env override)", cfg.Detect.SuspectStreakMin)
	}
	if cfg.Capacity.BreakerMinN != 15 {
		t.Errorf("Capacity.BreakerMinN = %d; want 15 (env override)", cfg.Capacity.BreakerMinN)
	}
}

// ─────────────────────────────────────────────────────────────────────────────
// Test: SCHED_CONFIG_FILE env var overrides path argument
// ─────────────────────────────────────────────────────────────────────────────

func TestConfigManagerSCHED_CONFIG_FILE(t *testing.T) {
	yaml := `
capacity:
  breaker_window_min: 120
`
	tmpFile, err := writeTempYAML(t, yaml)
	if err != nil {
		t.Fatalf("writeTempYAML: %v", err)
	}

	t.Setenv("SCHED_CONFIG_FILE", tmpFile)

	// Pass a wrong path as arg; env var should win.
	m := orchestrate.NewConfigManager("/nonexistent/path.yaml", nil)
	if err := m.Load(); err != nil {
		t.Fatalf("Load: %v", err)
	}

	if cfg := m.Current(); cfg.Capacity.BreakerWindowMin != 120 {
		t.Errorf("BreakerWindowMin = %d; want 120 (read from SCHED_CONFIG_FILE)", cfg.Capacity.BreakerWindowMin)
	}
}

// ─────────────────────────────────────────────────────────────────────────────
// Test: Reload changes values and triggers audit callback with diff
// ─────────────────────────────────────────────────────────────────────────────

func TestConfigManagerReloadWithAudit(t *testing.T) {
	yaml1 := `
capacity:
  watermark_threshold: 0.70
  breaker_min_n: 3
`
	tmpFile, err := writeTempYAML(t, yaml1)
	if err != nil {
		t.Fatalf("writeTempYAML: %v", err)
	}

	var auditCalls []struct{ old, new string }
	auditFn := func(ctx context.Context, old, new string) {
		auditCalls = append(auditCalls, struct{ old, new string }{old, new})
	}

	m := orchestrate.NewConfigManager(tmpFile, orchestrate.AuditFn(auditFn))
	if err := m.Load(); err != nil {
		t.Fatalf("initial Load: %v", err)
	}

	initialThreshold := m.Current().Capacity.WatermarkThreshold

	// Rewrite the YAML with a changed value.
	yaml2 := `
capacity:
  watermark_threshold: 0.55
  breaker_min_n: 5
`
	if err := os.WriteFile(tmpFile, []byte(yaml2), 0o644); err != nil {
		t.Fatalf("rewrite YAML: %v", err)
	}

	// Reload should pick up the new values.
	if err := m.Reload(context.Background()); err != nil {
		t.Fatalf("Reload: %v", err)
	}

	newThreshold := m.Current().Capacity.WatermarkThreshold

	if initialThreshold == newThreshold {
		t.Errorf("WatermarkThreshold not updated after reload: both = %f", newThreshold)
	}
	if newThreshold != 0.55 {
		t.Errorf("WatermarkThreshold after reload = %f; want 0.55", newThreshold)
	}
	if m.Current().Capacity.BreakerMinN != 5 {
		t.Errorf("BreakerMinN after reload = %d; want 5", m.Current().Capacity.BreakerMinN)
	}

	// Audit callback must have been invoked once (old != new).
	if len(auditCalls) != 1 {
		t.Errorf("audit callback count = %d; want 1", len(auditCalls))
	} else {
		if auditCalls[0].old == auditCalls[0].new {
			t.Error("audit callback old == new; diff expected")
		}
	}
}

// ─────────────────────────────────────────────────────────────────────────────
// Test: Reload with no change → audit NOT called (diff is empty)
// ─────────────────────────────────────────────────────────────────────────────

func TestConfigManagerReloadNoChangeSkipsAudit(t *testing.T) {
	yaml := `
capacity:
  watermark_threshold: 0.75
`
	tmpFile, err := writeTempYAML(t, yaml)
	if err != nil {
		t.Fatalf("writeTempYAML: %v", err)
	}

	auditCount := 0
	auditFn := func(_ context.Context, _, _ string) { auditCount++ }

	m := orchestrate.NewConfigManager(tmpFile, orchestrate.AuditFn(auditFn))
	if err := m.Load(); err != nil {
		t.Fatalf("initial Load: %v", err)
	}

	// Reload without changing the YAML file.
	if err := m.Reload(context.Background()); err != nil {
		t.Fatalf("Reload: %v", err)
	}

	// No diff → audit should NOT be called on the second load.
	// (First Load always swaps from defaults to YAML; that may be different.)
	// What we really check: the reload didn't trigger another audit if nothing changed.
	// The auditFn may have been called 1 time (from Load) but NOT from Reload.
	//
	// Actually, Load also calls swap. If the defaults differ from the YAML,
	// the initial Load increments auditCount. Then Reload sees no diff → no extra call.
	countAfterReload := auditCount

	// Reload again — still unchanged → same count.
	if err := m.Reload(context.Background()); err != nil {
		t.Fatalf("second Reload: %v", err)
	}
	if auditCount != countAfterReload {
		t.Errorf("audit called %d times after second reload; expected 0 extra calls (no diff)", auditCount-countAfterReload)
	}
}

// ─────────────────────────────────────────────────────────────────────────────
// Test: comma-separated env var for ExpectedProbeIDs
// ─────────────────────────────────────────────────────────────────────────────

func TestConfigManagerProbeIDsFromEnv(t *testing.T) {
	t.Setenv("CAP_EXPECTED_PROBE_IDS", "alpha, beta , gamma")

	m := orchestrate.NewConfigManager("", nil)
	if err := m.Load(); err != nil {
		t.Fatalf("Load: %v", err)
	}
	ids := m.Current().Capacity.ExpectedProbeIDs
	if len(ids) != 3 {
		t.Fatalf("ExpectedProbeIDs = %v; want 3 entries", ids)
	}
	want := []string{"alpha", "beta", "gamma"}
	for i, w := range want {
		if ids[i] != w {
			t.Errorf("ids[%d] = %q; want %q", i, ids[i], w)
		}
	}
}
