package plugin

import (
	"strings"
	"testing"
	"time"
)

func TestEasterDate(t *testing.T) {
	tests := []struct {
		year  int
		month time.Month
		day   int
	}{
		{2024, time.March, 31},
		{2025, time.April, 20},
		{2026, time.April, 5},
		{2027, time.March, 28},
		{2028, time.April, 16},
	}

	for _, tc := range tests {
		got := easterDate(tc.year)
		want := time.Date(tc.year, tc.month, tc.day, 0, 0, 0, 0, time.UTC)
		if !got.Equal(want) {
			t.Errorf("Easter %d: got %s, want %s", tc.year, got.Format("2006-01-02"), want.Format("2006-01-02"))
		}
	}
}

func TestNthMonday(t *testing.T) {
	tests := []struct {
		desc  string
		year  int
		month time.Month
		n     int
		want  string
	}{
		{"Family Day 2026", 2026, time.February, 3, "2026-02-16"},
		{"Labour Day 2026", 2026, time.September, 1, "2026-09-07"},
		{"Thanksgiving CA 2026", 2026, time.October, 2, "2026-10-12"},
		{"Family Day 2027", 2027, time.February, 3, "2027-02-15"},
	}

	for _, tc := range tests {
		got := nthMonday(tc.year, tc.month, tc.n)
		if got.Format("2006-01-02") != tc.want {
			t.Errorf("%s: got %s, want %s", tc.desc, got.Format("2006-01-02"), tc.want)
		}
	}
}

func TestMondayBeforeMay25(t *testing.T) {
	tests := []struct {
		year int
		want string
	}{
		{2026, "2026-05-18"},
		{2027, "2027-05-24"},
		{2028, "2028-05-22"},
	}

	for _, tc := range tests {
		got := mondayBeforeMay25(tc.year)
		if got.Format("2006-01-02") != tc.want {
			t.Errorf("Victoria Day %d: got %s, want %s", tc.year, got.Format("2006-01-02"), tc.want)
		}
	}
}

// ── Hebrew Calendar Tests ───────────────────────────────────────────────────

func TestHebrewRoshHashanah(t *testing.T) {
	// Verified against hebcal.com
	tests := []struct {
		year int
		want string
	}{
		{2023, "2023-09-16"}, // 5784
		{2024, "2024-10-03"}, // 5785
		{2025, "2025-09-23"}, // 5786
		{2026, "2026-09-12"}, // 5787
		{2027, "2027-10-02"}, // 5788
		{2028, "2028-09-21"}, // 5789
		{2030, "2030-09-28"}, // 5791
	}

	for _, tc := range tests {
		got := hebrewRoshHashanah(tc.year)
		if got.Format("2006-01-02") != tc.want {
			t.Errorf("Rosh Hashanah %d: got %s, want %s", tc.year, got.Format("2006-01-02"), tc.want)
		}
	}
}

func TestHebrewYomKippur(t *testing.T) {
	tests := []struct {
		year int
		want string
	}{
		{2026, "2026-09-21"},
		{2027, "2027-10-11"},
	}

	for _, tc := range tests {
		got := hebrewYomKippur(tc.year)
		if got.Format("2006-01-02") != tc.want {
			t.Errorf("Yom Kippur %d: got %s, want %s", tc.year, got.Format("2006-01-02"), tc.want)
		}
	}
}

func TestHebrewPassover(t *testing.T) {
	// 15 Nisan (first full day of Pesach)
	tests := []struct {
		year int
		want string
	}{
		{2024, "2024-04-23"},
		{2025, "2025-04-13"},
		{2026, "2026-04-02"},
		{2027, "2027-04-22"},
	}

	for _, tc := range tests {
		got := hebrewPassover(tc.year)
		if got.Format("2006-01-02") != tc.want {
			t.Errorf("Passover %d: got %s, want %s", tc.year, got.Format("2006-01-02"), tc.want)
		}
	}
}

func TestHebrewHanukkah(t *testing.T) {
	// 25 Kislev (first full day)
	tests := []struct {
		year int
		want string
	}{
		{2024, "2024-12-26"},
		{2025, "2025-12-15"},
		{2026, "2026-12-05"},
		{2027, "2027-12-25"},
	}

	for _, tc := range tests {
		got := hebrewHanukkah(tc.year)
		if got.Format("2006-01-02") != tc.want {
			t.Errorf("Hanukkah %d: got %s, want %s", tc.year, got.Format("2006-01-02"), tc.want)
		}
	}
}

// ── Tabular Islamic Calendar Tests ──────────────────────────────────────────

func TestIslamicEidAlFitr(t *testing.T) {
	// Tabular Islamic calendar — ±1 day from observed
	tests := []struct {
		year int
		want string
	}{
		{2026, "2026-03-20"},
		{2027, "2027-03-10"},
	}

	for _, tc := range tests {
		dates := tabularIslamicDates(tc.year, 10, 1)
		if len(dates) == 0 {
			t.Errorf("Eid al-Fitr %d: no dates returned", tc.year)
			continue
		}
		got := dates[0].Format("2006-01-02")
		if got != tc.want {
			t.Errorf("Eid al-Fitr %d: got %s, want %s", tc.year, got, tc.want)
		}
	}
}

func TestIslamicEidAlAdha(t *testing.T) {
	tests := []struct {
		year int
		want string
	}{
		{2026, "2026-05-27"},
		{2027, "2027-05-17"}, // Tabular Islamic calendar ±1 day from observed
	}

	for _, tc := range tests {
		dates := tabularIslamicDates(tc.year, 12, 10)
		if len(dates) == 0 {
			t.Errorf("Eid al-Adha %d: no dates returned", tc.year)
			continue
		}
		got := dates[0].Format("2006-01-02")
		if got != tc.want {
			t.Errorf("Eid al-Adha %d: got %s, want %s", tc.year, got, tc.want)
		}
	}
}

// ── Integration Tests ───────────────────────────────────────────────────────

func TestFloatingHolidays_AllComputed(t *testing.T) {
	for year := 2024; year <= 2035; year++ {
		holidays := floatingHolidays(year)
		if len(holidays) < 14 { // at least: 4 Easter + Nowruz + 4 Canadian + 4 Hebrew + 2 Islamic
			t.Errorf("year %d: only %d floating holidays, expected at least 14", year, len(holidays))
		}

		// Verify no zero-time dates (would indicate a computation failure)
		for _, h := range holidays {
			if h.Date.IsZero() {
				t.Errorf("year %d: holiday %q has zero date", year, h.Name)
			}
			if h.Date.Year() != year {
				t.Errorf("year %d: holiday %q falls in %d", year, h.Name, h.Date.Year())
			}
		}
	}
}

func TestFloatingHolidays_DateCollisions(t *testing.T) {
	holidays := floatingHolidays(2026)
	seen := make(map[string][]string)
	for _, h := range holidays {
		key := h.Date.Format("2006-01-02")
		seen[key] = append(seen[key], h.Name)
	}
	for date, names := range seen {
		if len(names) > 1 {
			t.Logf("NOTE: %d holidays on %s: %s", len(names), date, strings.Join(names, ", "))
		}
	}
}

func TestFixedHolidays_Christmas(t *testing.T) {
	found := false
	for _, h := range majorHolidays {
		if h.Month == 12 && h.Day == 25 {
			found = true
			break
		}
	}
	if !found {
		t.Error("Christmas not in majorHolidays")
	}
}

func TestFixedHolidays_NoDuplicateMonthDay(t *testing.T) {
	seen := make(map[[2]int]string)
	for _, h := range majorHolidays {
		key := [2]int{h.Month, h.Day}
		if prev, ok := seen[key]; ok {
			t.Errorf("duplicate fixed holiday on %d/%d: %s and %s", h.Month, h.Day, prev, h.Name)
		}
		seen[key] = h.Name
	}
}

func TestDailySummary_HolidayBlock(t *testing.T) {
	players := []AdvPlayerDaySummary{
		{DisplayName: "Alice", Activity: "dungeon", Location: "Cellar", Outcome: "success", LootValue: 500},
		{DisplayName: "Bob", Activity: "mine", Location: "Quarry", Outcome: "success", LootValue: 300},
		{DisplayName: "Carol", IsDead: true, DeadUntil: "14:00 UTC", Activity: "dungeon", Location: "Cave", Outcome: "death"},
	}

	text := renderAdvDailySummary("2026-12-25", nil, TwinBeeRewardSummary{}, players, "Christmas")

	if !strings.Contains(text, "Christmas") {
		t.Error("summary should contain holiday name")
	}
	// Adv 2.0 — holiday recap now advertises the +5 mood / free pack /
	// +1 yield perks instead of the retired double-action mechanic.
	if !strings.Contains(text, "TwinBee's blessing") {
		t.Error("summary should mention TwinBee's blessing on holidays")
	}
	if !strings.Contains(text, "free standard pack") {
		t.Error("summary should mention the complimentary standard pack")
	}

	// Without holiday
	textNoHol := renderAdvDailySummary("2026-12-26", nil, TwinBeeRewardSummary{}, players, "")
	if strings.Contains(textNoHol, "TwinBee's blessing") {
		t.Error("non-holiday summary should NOT mention TwinBee's blessing")
	}
}

// ── JDN Roundtrip Test ──────────────────────────────────────────────────────

func TestGregorianFromJDN_Roundtrip(t *testing.T) {
	// Verify known JDN ↔ Gregorian conversions
	tests := []struct {
		jdn  int
		date string
	}{
		{2451545, "2000-01-01"}, // J2000.0
		{2440588, "1970-01-01"}, // Unix epoch
		{2299161, "1582-10-15"}, // Gregorian calendar adoption
	}

	for _, tc := range tests {
		got := gregorianFromJDN(tc.jdn)
		if got.Format("2006-01-02") != tc.date {
			t.Errorf("JDN %d: got %s, want %s", tc.jdn, got.Format("2006-01-02"), tc.date)
		}
	}
}
