package loop

import (
	"context"
	"path/filepath"
	"testing"
	"time"

	"github.com/kami/maven/internal/calendar"
	"github.com/kami/maven/internal/store"
)

// An ambient meeting suppresses a nudge for its own span and no longer
// (Vikunja #513). The span is read back off the event fact, so there is no
// expiry to configure and no way for it to outlive the meeting.
func TestAmbientEventSuppressesNudgesForItsOwnSpan(t *testing.T) {
	ctx := context.Background()
	s, err := store.Open(ctx, filepath.Join(t.TempDir(), "ambient_busy.db"))
	if err != nil {
		t.Fatalf("Open: %v", err)
	}
	t.Cleanup(func() { _ = s.Close() })

	day := time.Date(2026, 8, 5, 0, 0, 0, 0, time.Local)
	ev := calendar.Event{
		Summary: "Встреча с Аней",
		Start:   day.Add(14 * time.Hour),
		End:     day.Add(15 * time.Hour),
	}
	if _, err := s.SetValue(ctx, store.KindEnv, calendar.FactKey(ev),
		calendar.SourceAmbient, calendar.FactValue(ev), day); err != nil {
		t.Fatalf("SetValue: %v", err)
	}

	g := NewGatherer(s, nil)
	for _, tc := range []struct {
		name string
		now  time.Time
		busy bool
	}{
		{"before it starts", day.Add(13*time.Hour + 59*time.Minute), false},
		{"at the first minute", day.Add(14 * time.Hour), true},
		{"in the middle", day.Add(14*time.Hour + 30*time.Minute), true},
		{"at the end instant", day.Add(15 * time.Hour), false},
		{"an hour after", day.Add(16 * time.Hour), false},
	} {
		t.Run(tc.name, func(t *testing.T) {
			st, _, err := g.GatherState(ctx, tc.now)
			if err != nil {
				t.Fatalf("GatherState: %v", err)
			}
			if st.CalendarBusy != tc.busy {
				t.Fatalf("CalendarBusy = %v at %s, want %v", st.CalendarBusy, tc.now.Format("15:04"), tc.busy)
			}
		})
	}
}

// A meeting on another day must not make today busy at the same clock reading.
// The day comes from the key, which is what makes this hold.
func TestAnEventOnAnotherDayDoesNotSuppress(t *testing.T) {
	ctx := context.Background()
	s, err := store.Open(ctx, filepath.Join(t.TempDir(), "ambient_busy_day.db"))
	if err != nil {
		t.Fatalf("Open: %v", err)
	}
	t.Cleanup(func() { _ = s.Close() })

	yesterday := time.Date(2026, 8, 4, 0, 0, 0, 0, time.Local)
	ev := calendar.Event{Summary: "Standup", Start: yesterday.Add(14 * time.Hour), End: yesterday.Add(15 * time.Hour)}
	if _, err := s.SetValue(ctx, store.KindEnv, calendar.FactKey(ev),
		calendar.SourceAmbient, calendar.FactValue(ev), yesterday); err != nil {
		t.Fatalf("SetValue: %v", err)
	}

	today := time.Date(2026, 8, 5, 14, 30, 0, 0, time.Local)
	st, _, err := NewGatherer(s, nil).GatherState(ctx, today)
	if err != nil {
		t.Fatalf("GatherState: %v", err)
	}
	if st.CalendarBusy {
		t.Fatal("yesterday's meeting suppressed a nudge today")
	}
}

func TestFactSpanReadsBackWhatFactValueWrote(t *testing.T) {
	day := time.Date(2026, 8, 5, 0, 0, 0, 0, time.Local)
	ev := calendar.Event{Summary: "Обед с мамой", Start: day.Add(13 * time.Hour), End: day.Add(13*time.Hour + 45*time.Minute)}
	start, end, ok := calendar.FactSpan(calendar.FactKey(ev), calendar.FactValue(ev), time.Local)
	if !ok {
		t.Fatal("FactSpan could not read its own encoding")
	}
	if !start.Equal(ev.Start) || !end.Equal(ev.End) {
		t.Fatalf("span = %s-%s, want %s-%s", start, end, ev.Start, ev.End)
	}
}

// An end at or before the start is a meeting crossing midnight, not a zero-length
// one. Reading it as zero-length would silently drop the suppression.
func TestFactSpanCrossesMidnight(t *testing.T) {
	start, end, ok := calendar.FactSpan("calendar_event_20260805_Night", "Night @ 23:30-00:15", time.Local)
	if !ok {
		t.Fatal("FactSpan rejected a midnight-crossing event")
	}
	if got := end.Sub(start); got != 45*time.Minute {
		t.Fatalf("span length = %s, want 45m", got)
	}
}

// A fact that does not parse says nothing about now. Guessing a span here is
// how one suppressed nudge becomes all of them.
func TestFactSpanRejectsWhatItCannotRead(t *testing.T) {
	for _, tc := range []struct{ key, value string }{
		{"other_key_20260805_x", "x @ 10:00-11:00"},
		{"calendar_event_20260805_x", "x"},
		{"calendar_event_notadate_x", "x @ 10:00-11:00"},
		{"calendar_event_20260805_x", "x @ 25:00-11:00"},
		{"calendar_event_20260805_x", "x @ 10:00"},
	} {
		if _, _, ok := calendar.FactSpan(tc.key, tc.value, time.Local); ok {
			t.Errorf("FactSpan(%q, %q) parsed, want rejected", tc.key, tc.value)
		}
	}
}
