package services

import (
	"testing"
	"time"
)

// TestComputeDeadlineBucketBounds checks the 5-bucket pivots for every
// weekday — Heute / Diese Woche / Nächste Woche must be disjoint and
// every day from today through next Sunday must land in exactly one
// bucket (overdue stays before today; everything past next Sunday is
// "later" and not visible in the four pending cards).
func TestComputeDeadlineBucketBounds(t *testing.T) {
	type expectation struct {
		name        string
		now         time.Time
		wantSunday  string // upcoming Sunday including today, YYYY-MM-DD
		wantNextMon string // Monday of next week
	}
	cases := []expectation{
		{"Mon 2026-05-04", time.Date(2026, 5, 4, 12, 0, 0, 0, time.UTC), "2026-05-10", "2026-05-11"},
		{"Tue 2026-05-05", time.Date(2026, 5, 5, 12, 0, 0, 0, time.UTC), "2026-05-10", "2026-05-11"},
		{"Wed 2026-05-06", time.Date(2026, 5, 6, 12, 0, 0, 0, time.UTC), "2026-05-10", "2026-05-11"},
		{"Thu 2026-05-07", time.Date(2026, 5, 7, 12, 0, 0, 0, time.UTC), "2026-05-10", "2026-05-11"},
		{"Fri 2026-05-08", time.Date(2026, 5, 8, 12, 0, 0, 0, time.UTC), "2026-05-10", "2026-05-11"},
		{"Sat 2026-05-09", time.Date(2026, 5, 9, 12, 0, 0, 0, time.UTC), "2026-05-10", "2026-05-11"},
		// Sunday: spec says "Diese Woche" inclusive of today's Sunday
		// itself ⇒ Sunday-of-this-week == today, so Diese Woche has
		// no future-eligible entries beyond Heute.
		{"Sun 2026-05-10", time.Date(2026, 5, 10, 12, 0, 0, 0, time.UTC), "2026-05-10", "2026-05-11"},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			b := computeDeadlineBucketBounds(tc.now)
			if got := b.today.Format("2006-01-02"); got != tc.now.UTC().Format("2006-01-02") {
				t.Errorf("today = %s, want %s", got, tc.now.Format("2006-01-02"))
			}
			if got := b.tomorrow.Format("2006-01-02"); got != tc.now.AddDate(0, 0, 1).UTC().Format("2006-01-02") {
				t.Errorf("tomorrow = %s, want %s", got, tc.now.AddDate(0, 0, 1).Format("2006-01-02"))
			}
			sunday := b.nextMonday.AddDate(0, 0, -1).Format("2006-01-02")
			if sunday != tc.wantSunday {
				t.Errorf("Sunday-of-this-week = %s, want %s", sunday, tc.wantSunday)
			}
			if got := b.nextMonday.Format("2006-01-02"); got != tc.wantNextMon {
				t.Errorf("nextMonday = %s, want %s", got, tc.wantNextMon)
			}
			if got := b.weekAfter.Sub(b.nextMonday); got != 7*24*time.Hour {
				t.Errorf("weekAfter - nextMonday = %v, want 7d", got)
			}
		})
	}
}

// TestBucketsAreDisjoint walks 21 consecutive days from a known Monday
// and asserts that every day falls into at most one of the three
// pending-future buckets (today / this_week / next_week). Days past
// next Sunday must fall into none of them.
func TestBucketsAreDisjoint(t *testing.T) {
	now := time.Date(2026, 5, 4, 12, 0, 0, 0, time.UTC) // Monday
	b := computeDeadlineBucketBounds(now)
	for i := range 21 {
		day := b.today.AddDate(0, 0, i)
		isToday := day.Equal(b.today)
		isThisWeek := day.After(b.today) && day.Before(b.nextMonday)
		isNextWeek := !day.Before(b.nextMonday) && day.Before(b.weekAfter)
		hits := 0
		if isToday {
			hits++
		}
		if isThisWeek {
			hits++
		}
		if isNextWeek {
			hits++
		}
		// First 14 days from "today" must each hit exactly one bucket.
		// Days 14..20 (the third week and beyond) must hit none.
		want := 1
		if i >= 14 {
			want = 0
		}
		if hits != want {
			t.Errorf("offset +%dd (%s): %d bucket hits, want %d (today=%v thisWeek=%v nextWeek=%v)",
				i, day.Format("2006-01-02"), hits, want, isToday, isThisWeek, isNextWeek)
		}
	}
}
