package wallpaper

import (
	"testing"
	"time"
)

func TestParseHHMM(t *testing.T) {
	cases := []struct {
		in     string
		hour   int
		minute int
		ok     bool
	}{
		{"06:00", 6, 0, true},
		{"23:59", 23, 59, true},
		{"00:00", 0, 0, true},
		{"24:00", 0, 0, false},
		{"6:5", 6, 5, true},
		{"bad", 0, 0, false},
		{"12", 0, 0, false},
		{"12:60", 0, 0, false},
	}
	for _, c := range cases {
		hour, minute, ok := parseHHMM(c.in)
		if ok != c.ok || (ok && (hour != c.hour || minute != c.minute)) {
			t.Errorf("parseHHMM(%q) = (%d, %d, %v), want (%d, %d, %v)", c.in, hour, minute, ok, c.hour, c.minute, c.ok)
		}
	}
}

func TestComputeNextInterval(t *testing.T) {
	now := time.Date(2026, 6, 30, 12, 0, 0, 0, time.UTC)
	got := computeNext(now, ScheduleConfig{Mode: "interval", IntervalSec: 300})
	if want := now.Add(300 * time.Second); !got.Equal(want) {
		t.Errorf("interval next = %v, want %v", got, want)
	}

	clamped := computeNext(now, ScheduleConfig{Mode: "interval", IntervalSec: 0})
	if want := now.Add(time.Second); !clamped.Equal(want) {
		t.Errorf("interval clamp = %v, want %v", clamped, want)
	}
}

func TestComputeNextTime(t *testing.T) {
	now := time.Date(2026, 6, 30, 12, 0, 0, 0, time.UTC)

	later := computeNext(now, ScheduleConfig{Mode: "time", Time: "18:00"})
	if want := time.Date(2026, 6, 30, 18, 0, 0, 0, time.UTC); !later.Equal(want) {
		t.Errorf("time next (today) = %v, want %v", later, want)
	}

	tomorrow := computeNext(now, ScheduleConfig{Mode: "time", Time: "06:00"})
	if want := time.Date(2026, 7, 1, 6, 0, 0, 0, time.UTC); !tomorrow.Equal(want) {
		t.Errorf("time next (tomorrow) = %v, want %v", tomorrow, want)
	}
}

func TestActiveSchedules(t *testing.T) {
	global := activeSchedules(Config{
		Global: ScheduleConfig{Enabled: true, Mode: "interval", IntervalSec: 60},
	})
	if len(global) != 1 {
		t.Fatalf("global active = %d, want 1", len(global))
	}
	if _, ok := global[""]; !ok {
		t.Errorf("global active missing global key")
	}

	perMonitor := activeSchedules(Config{
		PerMonitor: true,
		Global:     ScheduleConfig{Enabled: true},
		Monitors: map[string]ScheduleConfig{
			"DP-1": {Enabled: true, Mode: "interval", IntervalSec: 60},
			"DP-2": {Enabled: false},
		},
	})
	if len(perMonitor) != 1 {
		t.Fatalf("per-monitor active = %d, want 1", len(perMonitor))
	}
	if _, ok := perMonitor["DP-1"]; !ok {
		t.Errorf("per-monitor active missing DP-1")
	}
}

func TestPrevDailyTime(t *testing.T) {
	now := time.Date(2026, 6, 30, 12, 0, 0, 0, time.UTC)

	earlier, ok := prevDailyTime(now, "06:00")
	if !ok || !earlier.Equal(time.Date(2026, 6, 30, 6, 0, 0, 0, time.UTC)) {
		t.Errorf("prev (today) = %v, %v", earlier, ok)
	}

	later, ok := prevDailyTime(now, "18:00")
	if !ok || !later.Equal(time.Date(2026, 6, 29, 18, 0, 0, 0, time.UTC)) {
		t.Errorf("prev (yesterday) = %v, %v", later, ok)
	}

	if _, ok := prevDailyTime(now, "bad"); ok {
		t.Error("prev (invalid) should not be ok")
	}
}

func TestTimeScheduleCatchUpAfterRestart(t *testing.T) {
	origDelay := catchUpDelay
	catchUpDelay = 100 * time.Millisecond
	defer func() { catchUpDelay = origDelay }()

	now := time.Now()
	hhmm := now.Add(-2 * time.Hour).Format("15:04")
	seed := map[string]time.Time{"": now.Add(-26 * time.Hour)}

	m := newManager(seed, func(map[string]time.Time) {})
	defer m.Close()

	sub := m.Subscribe("test")
	defer m.Unsubscribe("test")

	m.SetConfig(Config{Global: ScheduleConfig{Enabled: true, Mode: "time", Time: hhmm}})

	deadline := time.After(3 * time.Second)
	for {
		select {
		case state := <-sub:
			if state.CycleSeq > 0 && state.Target == "" {
				return
			}
		case <-deadline:
			t.Fatal("missed time schedule did not catch up within 3s")
		}
	}
}

func TestTimeScheduleNoFireOnFirstEnable(t *testing.T) {
	now := time.Now()
	hhmm := now.Add(-2 * time.Hour).Format("15:04")

	m := newManager(map[string]time.Time{}, func(map[string]time.Time) {})
	defer m.Close()

	sub := m.Subscribe("test")
	defer m.Unsubscribe("test")

	m.SetConfig(Config{Global: ScheduleConfig{Enabled: true, Mode: "time", Time: hhmm}})

	deadline := time.After(1500 * time.Millisecond)
	for {
		select {
		case state := <-sub:
			if state.CycleSeq > 0 {
				t.Fatal("first enable must not fire immediately")
			}
		case <-deadline:
			return
		}
	}
}

func TestSchedulerEmitsCycle(t *testing.T) {
	m := newManager(map[string]time.Time{}, func(map[string]time.Time) {})
	defer m.Close()

	sub := m.Subscribe("test")
	defer m.Unsubscribe("test")

	m.SetConfig(Config{Global: ScheduleConfig{Enabled: true, Mode: "interval", IntervalSec: 1}})

	deadline := time.After(3 * time.Second)
	for {
		select {
		case state := <-sub:
			if state.CycleSeq > 0 && state.Target == "" {
				return
			}
		case <-deadline:
			t.Fatal("scheduler did not emit a cycle event within 3s")
		}
	}
}
