package events

import (
	"fmt"
	"testing"
	"time"

	"launchpad.net/email-reminder/internal/util"
)

const defaultMaxRecipients = 10000

var defaultRecipient = util.EmailRecipient{Email: "default@example.com"}

func TestGetOccurrence(t *testing.T) {
	tests := []struct {
		event        Event
		now          time.Time
		wantValue    int
		wantOccurred bool
	}{
		{
			event: Event{
				Type:  "birthday",
				Year:  2000,
				Month: 5,
				Day:   EventDay(15),
			},
			now:          time.Date(2025, 5, 15, 10, 0, 0, 0, time.UTC),
			wantValue:    25,
			wantOccurred: true,
		},
		{
			event: Event{
				Type:  "birthday",
				Year:  2000,
				Month: 5,
				Day:   EventDay(15),
			},
			now:          time.Date(2025, 7, 20, 10, 0, 0, 0, time.UTC),
			wantValue:    25,
			wantOccurred: true,
		},
		{
			event: Event{
				Type:  "birthday",
				Year:  2000,
				Month: 5,
				Day:   EventDay(15),
			},
			now:          time.Date(2025, 5, 10, 10, 0, 0, 0, time.UTC),
			wantValue:    24,
			wantOccurred: true,
		},
		{
			event: Event{
				Type:  "birthday",
				Year:  2000,
				Month: 8,
				Day:   EventDay(15),
			},
			now:          time.Date(2025, 5, 20, 10, 0, 0, 0, time.UTC),
			wantValue:    24,
			wantOccurred: true,
		},
		{
			event: Event{
				Type:  "birthday",
				Year:  2000,
				Month: 2,
				Day:   EventDay(29),
			},
			now:          time.Date(2025, 2, 28, 10, 0, 0, 0, time.UTC), // 2025 is not a leap year
			wantValue:    25,
			wantOccurred: true,
		},
		{
			event: Event{
				Type:  "birthday",
				Year:  2000,
				Month: 2,
				Day:   EventDay(29),
			},
			now:          time.Date(2024, 2, 28, 10, 0, 0, 0, time.UTC), // 2024 is a leap year
			wantValue:    23,
			wantOccurred: true,
		},
		{
			event: Event{
				Type:  "birthday",
				Year:  2000,
				Month: 2,
				Day:   EventDay(29),
			},
			now:          time.Date(2024, 2, 29, 10, 0, 0, 0, time.UTC), // 2024 is a leap year
			wantValue:    24,
			wantOccurred: true,
		},
		{
			event: Event{
				Type:  "yearly",
				Year:  2020,
				Month: 5,
				Day:   EventDay(15),
			},
			now:          time.Date(2025, 5, 15, 10, 0, 0, 0, time.UTC),
			wantValue:    5,
			wantOccurred: true,
		},
		{
			event: Event{
				Type:  "birthday",
				Year:  2025,
				Month: 5,
				Day:   EventDay(15),
			},
			now:          time.Date(2025, 5, 15, 10, 0, 0, 0, time.UTC),
			wantValue:    0,
			wantOccurred: true,
		},
		{
			event: Event{
				Type:  "birthday",
				Year:  2025,
				Month: 8,
				Day:   EventDay(15),
			},
			now:          time.Date(2025, 5, 15, 10, 0, 0, 0, time.UTC),
			wantValue:    -1,
			wantOccurred: false, // unborn person
		},
		{
			event: Event{
				Type:  "birthday",
				Year:  2000,
				Month: 5,
				Day:   EventDay(15),
			},
			now:          time.Date(2025, 3, 20, 10, 0, 0, 0, time.UTC),
			wantValue:    24,
			wantOccurred: true,
		},
	}

	for i, tt := range tests {
		gotValue, gotOccurred := tt.event.GetOccurrence(tt.now)
		if gotValue != tt.wantValue {
			t.Errorf("Test %d: GetOccurrence() gotValue = %v, want %v", i, gotValue, tt.wantValue)
		}
		if gotOccurred != tt.wantOccurred {
			t.Errorf("Test %d: GetOccurrence() gotOccurred = %v, want %v", i, gotOccurred, tt.wantOccurred)
		}
	}
}

func TestGetRecipients(t *testing.T) {
	tests := []struct {
		name  string
		event Event
		want  []util.EmailRecipient
	}{
		{
			name: "no recipients - use default email",
			event: Event{
				Name:       "Test Event",
				Recipients: []Recipient{},
			},
			want: []util.EmailRecipient{defaultRecipient},
		},
		{
			name: "single recipient with name and email",
			event: Event{
				Name: "Test Event",
				Recipients: []Recipient{
					{Name: "John Doe", Email: "john@example.com"},
				},
			},
			want: []util.EmailRecipient{{Email: "john@example.com", FirstName: "John", LastName: "Doe"}},
		},
		{
			name: "single recipient with email only",
			event: Event{
				Name: "Test Event",
				Recipients: []Recipient{
					{Email: "jane@example.com"},
				},
			},
			want: []util.EmailRecipient{{Email: "jane@example.com"}},
		},
		{
			name: "multiple recipients",
			event: Event{
				Name: "Test Event",
				Recipients: []Recipient{
					{Name: "John Doe", Email: "john@example.com"},
					{Email: "jane@example.com"},
					{Name: "Bob Smith", Email: "bob@example.com"},
				},
			},
			want: []util.EmailRecipient{
				{Email: "john@example.com", FirstName: "John", LastName: "Doe"},
				{Email: "jane@example.com"},
				{Email: "bob@example.com", FirstName: "Bob", LastName: "Smith"},
			},
		},
		{
			name: "recipient with empty email - should be ignored",
			event: Event{
				Name: "Test Event",
				Recipients: []Recipient{
					{Name: "John Doe Smith", Email: "john@example.com"},
					{Name: "Invalid", Email: ""},
					{Email: "jane@example.com"},
				},
			},
			want: []util.EmailRecipient{
				{Email: "john@example.com", FirstName: "John", LastName: "Doe Smith"},
				{Email: "jane@example.com"},
			},
		},
		{
			name: "all recipients invalid - use default email",
			event: Event{
				Name: "Test Event",
				Recipients: []Recipient{
					{Name: "Invalid1", Email: ""},
					{Name: "Invalid2", Email: "   "},
				},
			},
			want: []util.EmailRecipient{defaultRecipient},
		},
		{
			name: "recipient with whitespace in name and email",
			event: Event{
				Name: "Test Event",
				Recipients: []Recipient{
					{Name: "       John Doe    ", Email: "  john@example.com  "},
				},
			},
			want: []util.EmailRecipient{{Email: "john@example.com", FirstName: "John", LastName: "Doe"}},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := tt.event.GetRecipients(defaultRecipient, defaultMaxRecipients)
			if len(got) != len(tt.want) {
				t.Errorf("GetRecipients() length = %d, want %d", len(got), len(tt.want))
				return
			}
			for i, recipient := range got {
				if recipient.Email != tt.want[i].Email {
					t.Errorf("GetRecipients()[%d].Email = %s, want %s", i, recipient.Email, tt.want[i].Email)
				}
				if recipient.FirstName != tt.want[i].FirstName {
					t.Errorf("GetRecipients()[%d].FirstName = %s, want %s", i, recipient.FirstName, tt.want[i].FirstName)
				}
			}
		})
	}
}

func TestGetRecipientsMaxLimit(t *testing.T) {
	tests := []struct {
		name          string
		event         Event
		maxRecipients int
		wantCount     int
	}{
		{
			name: "within limit - 2 recipients with max 10",
			event: Event{
				Recipients: []Recipient{
					{Email: "recipient1@example.com"},
					{Email: "recipient2@example.com"},
				},
			},
			maxRecipients: 10,
			wantCount:     2,
		},
		{
			name: "at limit - 2 recipients with max 2",
			event: Event{
				Recipients: []Recipient{
					{Email: "recipient1@example.com"},
					{Email: "recipient2@example.com"},
				},
			},
			maxRecipients: 2,
			wantCount:     2,
		},
		{
			name: "exceeds limit - 5 recipients with max 3",
			event: Event{
				Name: "Test Event",
				Recipients: []Recipient{
					{Email: "recipient1@example.com"},
					{Email: "recipient2@example.com"},
					{Email: "recipient3@example.com"},
					{Email: "recipient4@example.com"},
					{Email: "recipient5@example.com"},
				},
			},
			maxRecipients: 3,
			wantCount:     3, // Only first 3 should be returned
		},
		{
			name:          "no recipients - fallback used",
			event:         Event{},
			maxRecipients: 10,
			wantCount:     1,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := tt.event.GetRecipients(defaultRecipient, tt.maxRecipients)
			if len(got) != tt.wantCount {
				t.Errorf("GetRecipients() returned %d recipients, want %d", len(got), tt.wantCount)
			}
		})
	}
}

func TestAnniversaryContact(t *testing.T) {
	tests := []struct {
		name  string
		event Event
		want  string
	}{
		{
			name: "both emails present",
			event: Event{
				Name:         "John",
				Email:        " john@example.com",
				PartnerName:  "Jane",
				PartnerEmail: "  jane@example.com  ",
			},
			want: "You can reach them at john@example.com and jane@example.com respectively.",
		},
		{
			name: "only event email present",
			event: Event{
				Name:         "John",
				Email:        "   john@example.com",
				PartnerName:  "Jane",
				PartnerEmail: "",
			},
			want: "You can reach John at john@example.com.",
		},
		{
			name: "only partner email present",
			event: Event{
				Name:         "John",
				Email:        "",
				PartnerName:  "Jane",
				PartnerEmail: "jane@example.com   ",
			},
			want: "You can reach Jane at jane@example.com.",
		},
		{
			name: "no emails present",
			event: Event{
				Name:         "John",
				Email:        "",
				PartnerName:  "Jane",
				PartnerEmail: "",
			},
			want: "",
		},
		{
			name: "empty strings for emails",
			event: Event{
				Name:         "John",
				Email:        "   ",
				PartnerName:  "Jane",
				PartnerEmail: "   ",
			},
			want: "",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := tt.event.AnniversaryContact()
			if got != tt.want {
				t.Errorf("AnniversaryContact() = %q, want %q", got, tt.want)
			}
		})
	}
}

func TestEventValidate(t *testing.T) {
	tests := []struct {
		name      string
		event     Event
		wantValid bool
	}{
		// Valid events
		{
			name: "valid anniversary",
			event: Event{
				Type:        "anniversary",
				Name:        "John",
				PartnerName: "Jane",
				Day:         EventDay(15),
				Month:       6,
				Year:        2020,
			},
			wantValid: true,
		},
		{
			name: "valid birthday",
			event: Event{
				Type:  "birthday",
				Name:  "Alice",
				Day:   EventDay(1),
				Month: 1,
			},
			wantValid: true,
		},
		{
			name: "valid yearly",
			event: Event{
				Type:  "yearly",
				Name:  "Company Anniversary",
				Day:   EventDay(31),
				Month: 12,
			},
			wantValid: true,
		},
		{
			name: "valid monthly",
			event: Event{
				Type: "monthly",
				Name: "Monthly Review",
				Day:  EventDay(15),
			},
			wantValid: true,
		},
		{
			name: "valid weekly on Sunday",
			event: Event{
				Type: "weekly",
				Name: "Weekly Meeting",
				Day:  EventDay(0),
			},
			wantValid: true,
		},
		{
			name: "valid weekly on Saturday",
			event: Event{
				Type: "weekly",
				Name: "Weekly Meeting",
				Day:  EventDay(6),
			},
			wantValid: true,
		},

		// Invalid events - empty name
		{
			name: "empty name",
			event: Event{
				Type:  "birthday",
				Name:  "",
				Day:   EventDay(15),
				Month: 6,
			},
			wantValid: false,
		},
		{
			name: "whitespace only name",
			event: Event{
				Type:  "birthday",
				Name:  "   \t\n   ",
				Day:   EventDay(15),
				Month: 6,
			},
			wantValid: false,
		},

		// Invalid events - anniversary specific validation
		{
			name: "anniversary with empty partner name",
			event: Event{
				Type:        "anniversary",
				Name:        "John",
				PartnerName: "",
				Day:         EventDay(15),
				Month:       6,
			},
			wantValid: false,
		},
		{
			name: "anniversary with whitespace only partner name",
			event: Event{
				Type:        "anniversary",
				Name:        "John",
				PartnerName: "   \t\n   ",
				Day:         EventDay(15),
				Month:       6,
			},
			wantValid: false,
		},

		// Invalid events - bad day values for anniversary/birthday/yearly
		{
			name: "anniversary with day 0",
			event: Event{
				Type:        "anniversary",
				Name:        "Test",
				PartnerName: "Partner",
				Day:         EventDay(0),
				Month:       6,
			},
			wantValid: false,
		},
		{
			name: "birthday with day 32",
			event: Event{
				Type:  "birthday",
				Name:  "Test",
				Day:   EventDay(32),
				Month: 6,
			},
			wantValid: false,
		},

		// Invalid events - bad month values for anniversary/birthday/yearly
		{
			name: "anniversary with month 0",
			event: Event{
				Type:        "anniversary",
				Name:        "Test",
				PartnerName: "Partner",
				Day:         EventDay(15),
				Month:       0,
			},
			wantValid: false,
		},
		{
			name: "yearly with month 13",
			event: Event{
				Type:  "yearly",
				Name:  "Test",
				Day:   EventDay(15),
				Month: 13,
			},
			wantValid: false,
		},

		// Invalid events - bad day values for monthly
		{
			name: "monthly with day 0",
			event: Event{
				Type: "monthly",
				Name: "Test",
				Day:  EventDay(0),
			},
			wantValid: false,
		},
		{
			name: "monthly with day 32",
			event: Event{
				Type: "monthly",
				Name: "Test",
				Day:  EventDay(32),
			},
			wantValid: false,
		},

		// Invalid events - unknown type
		{
			name: "unknown event type",
			event: Event{
				Type: "unknown",
				Name: "Test",
				Day:  EventDay(15),
			},
			wantValid: false,
		},

		// Invalid events - negative year
		{
			name: "birthday with large negative year",
			event: Event{
				Type:  "birthday",
				Name:  "Test",
				Day:   EventDay(15),
				Month: 6,
				Year:  -9999,
			},
			wantValid: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := tt.event.Validate()

			if tt.wantValid {
				if err != nil {
					t.Errorf("Validate() unexpected error = %v", err)
				}
			} else {
				if err == nil {
					t.Errorf("Validate() error = nil, want non-empty error")
				} else if err.Error() == "" {
					t.Errorf("Validate() error message is empty, want non-empty error message")
				}
			}
		})
	}
}

func TestIsOccurring(t *testing.T) {
	tests := []struct {
		name          string
		event         Event
		now           time.Time
		reminder      Reminder
		wantOccurring bool
		wantWhen      string
		wantError     bool
	}{
		// Same day and days before occurring tests
		{
			name: "anniversary same day",
			event: Event{
				Type:        "anniversary",
				Name:        "Alice",
				PartnerName: "Bob",
				Day:         EventDay(15),
				Month:       6,
			},
			now: time.Date(2025, 6, 15, 10, 0, 0, 0, time.UTC),
			reminder: Reminder{
				Type: "same day",
			},
			wantOccurring: true,
			wantWhen:      "today",
			wantError:     false,
		},
		{
			name: "birthday 1 day before",
			event: Event{
				Type:  "birthday",
				Name:  "John",
				Day:   EventDay(25),
				Month: 12,
			},
			now: time.Date(2025, 12, 24, 10, 0, 0, 0, time.UTC),
			reminder: Reminder{
				Type: "days before",
				Days: 1,
			},
			wantOccurring: true,
			wantWhen:      "tomorrow (Thu Dec 25)",
			wantError:     false,
		},
		{
			name: "birthday 42 days before (should fire)",
			event: Event{
				Type:  "birthday",
				Name:  "Test Person",
				Day:   EventDay(14),
				Month: 5,
				Year:  1948,
			},
			now: time.Date(2011, 4, 2, 10, 0, 0, 0, time.UTC),
			reminder: Reminder{
				Type: "days before",
				Days: 42,
			},
			wantOccurring: true,
			wantWhen:      "in 42 days (Sat May 14)",
			wantError:     false,
		},
		{
			name: "birthday 42 days before (should not fire)",
			event: Event{
				Type:  "birthday",
				Name:  "Test Person",
				Day:   EventDay(14),
				Month: 5,
				Year:  1948,
			},
			now: time.Date(2011, 4, 1, 10, 0, 0, 0, time.UTC),
			reminder: Reminder{
				Type: "days before",
				Days: 42,
			},
			wantOccurring: false,
			wantWhen:      "in 42 days (Fri May 13)",
			wantError:     false,
		},
		{
			name: "yearly same day",
			event: Event{
				Type:  "yearly",
				Name:  "Annual Meeting",
				Day:   EventDay(10),
				Month: 3,
			},
			now: time.Date(2025, 3, 10, 10, 0, 0, 0, time.UTC),
			reminder: Reminder{
				Type: "same day",
			},
			wantOccurring: true,
			wantWhen:      "today",
			wantError:     false,
		},
		{
			name: "monthly 2 days before",
			event: Event{
				Type: "monthly",
				Name: "Monthly Report",
				Day:  EventDay(15),
			},
			now: time.Date(2025, 9, 13, 10, 0, 0, 0, time.UTC),
			reminder: Reminder{
				Type: "days before",
				Days: 2,
			},
			wantOccurring: true,
			wantWhen:      "in 2 days (Mon Sep 15)",
			wantError:     false,
		},
		{
			name: "weekly 1 day before",
			event: Event{
				Type: "weekly",
				Name: "Team Meeting",
				Day:  EventDay(1), // Monday
			},
			now: time.Date(2025, 9, 14, 10, 0, 0, 0, time.UTC), // Sunday
			reminder: Reminder{
				Type: "days before",
				Days: 1,
			},
			wantOccurring: true,
			wantWhen:      "tomorrow (Mon Sep 15)",
			wantError:     false,
		},
		{
			name: "weekly same day - Sunday",
			event: Event{
				Type: "weekly",
				Name: "Sunday Service",
				Day:  EventDay(0), // Sunday
			},
			now: time.Date(2025, 9, 14, 10, 0, 0, 0, time.UTC), // Sunday
			reminder: Reminder{
				Type: "same day",
			},
			wantOccurring: true,
			wantWhen:      "today",
			wantError:     false,
		},

		// Non-occurring tests
		{
			name: "birthday different day",
			event: Event{
				Type:  "birthday",
				Name:  "Jane",
				Day:   EventDay(25),
				Month: 12,
			},
			now: time.Date(2025, 12, 20, 10, 0, 0, 0, time.UTC),
			reminder: Reminder{
				Type: "same day",
			},
			wantOccurring: false,
			wantWhen:      "today",
			wantError:     false,
		},
		{
			name: "monthly different day",
			event: Event{
				Type: "monthly",
				Name: "TPS Report",
				Day:  EventDay(15),
			},
			now: time.Date(2025, 9, 10, 10, 0, 0, 0, time.UTC),
			reminder: Reminder{
				Type: "same day",
			},
			wantOccurring: false,
			wantWhen:      "today",
			wantError:     false,
		},
		{
			name: "weekly different day",
			event: Event{
				Type: "weekly",
				Name: "Garbage",
				Day:  EventDay(1), // Monday
			},
			now: time.Date(2025, 9, 16, 10, 0, 0, 0, time.UTC), // Tuesday
			reminder: Reminder{
				Type: "same day",
			},
			wantOccurring: false,
			wantWhen:      "today",
			wantError:     false,
		},

		// Edge cases
		{
			name: "monthly last day of February non-leap year",
			event: Event{
				Type: "monthly",
				Name: "End of Month Report",
				Day:  EventDay(31), // Requested on 31st but February only has 28 days
			},
			now: time.Date(2025, 2, 28, 10, 0, 0, 0, time.UTC), // Last day of February in non-leap year
			reminder: Reminder{
				Type: "same day",
			},
			wantOccurring: true,
			wantWhen:      "today",
			wantError:     false,
		},
		{
			name: "leap year February 29th birthday on Feb 28th",
			event: Event{
				Type:  "birthday",
				Name:  "Leap Year Baby",
				Day:   EventDay(29),
				Month: 2,
				Year:  2024,
			},
			now: time.Date(2025, 2, 28, 10, 0, 0, 0, time.UTC), // 2025 is not a leap year
			reminder: Reminder{
				Type: "same day",
			},
			wantOccurring: true,
			wantWhen:      "today",
			wantError:     false,
		},
		{
			name: "leap year February 29th birthday on actual Feb 29th",
			event: Event{
				Type:  "birthday",
				Name:  "Leap Year Baby",
				Day:   EventDay(29),
				Month: 2,
				Year:  2024,
			},
			now: time.Date(2028, 2, 29, 10, 0, 0, 0, time.UTC), // 2028 is a leap year
			reminder: Reminder{
				Type: "same day",
			},
			wantOccurring: true,
			wantWhen:      "today",
			wantError:     false,
		},
		{
			name: "anniversary Feb 29 celebrated on Feb 28 in non-leap year",
			event: Event{
				Type:        "anniversary",
				Name:        "Alice",
				PartnerName: "Bob",
				Day:         EventDay(29),
				Month:       2,
				Year:        2000, // leap year base
			},
			now:           time.Date(2025, 2, 28, 10, 0, 0, 0, time.UTC), // Non-leap year Feb 28
			reminder:      Reminder{Type: "same day"},
			wantOccurring: true,
			wantWhen:      "today",
			wantError:     false,
		},

		// Error cases
		{
			name: "invalid reminder type",
			event: Event{
				Type:  "birthday",
				Name:  "Robert",
				Day:   EventDay(25),
				Month: 12,
			},
			now: time.Date(2025, 12, 25, 10, 0, 0, 0, time.UTC),
			reminder: Reminder{
				Type: "invalid",
			},
			wantOccurring: false,
			wantWhen:      "",
			wantError:     true,
		},
		{
			name: "invalid event type",
			event: Event{
				Type: "invalid",
				Name: "Invalid Event",
				Day:  EventDay(25),
			},
			now: time.Date(2025, 12, 25, 10, 0, 0, 0, time.UTC),
			reminder: Reminder{
				Type: "same day",
			},
			wantOccurring: false,
			wantWhen:      "today",
			wantError:     true,
		},
		{
			name: "days before with zero days",
			event: Event{
				Type:  "birthday",
				Name:  "Alice",
				Day:   EventDay(25),
				Month: 12,
			},
			now: time.Date(2025, 12, 25, 10, 0, 0, 0, time.UTC),
			reminder: Reminder{
				Type: "days before",
				Days: 0,
			},
			wantOccurring: false,
			wantWhen:      "",
			wantError:     true,
		},
		// Additional weekly edge/fallback cases
		{
			name: "weekly day 7 treated as Sunday (same day reminder)",
			event: Event{
				Type: "weekly",
				Name: "Overflow Weekly",
				Day:  EventDay(7), // out of normal range; should default to Sunday
			},
			now: time.Date(2025, 9, 14, 10, 0, 0, 0, time.UTC), // Sunday
			reminder: Reminder{
				Type: "same day",
			},
			wantOccurring: true,
			wantWhen:      "today",
			wantError:     false,
		},
		{
			name: "weekly negative day treated as Sunday (days before reminder)",
			event: Event{
				Type: "weekly",
				Name: "Negative Weekly",
				Day:  EventDay(-1), // negative; should default to Sunday
			},
			now: time.Date(2025, 9, 13, 10, 0, 0, 0, time.UTC), // Saturday
			reminder: Reminder{
				Type: "days before",
				Days: 1,
			},
			wantOccurring: true, // Saturday + 1 day = Sunday (defaulted)
			wantWhen:      "tomorrow (Sun Sep 14)",
			wantError:     false,
		},
		{
			name: "birthday with days before reminder crossing year and month boundaries in leap and non-leap years",
			event: Event{
				Type:  "birthday",
				Name:  "John",
				Day:   EventDay(2),
				Month: 3,
				Year:  2000,
			},
			now: time.Date(2023, 12, 29, 10, 0, 0, 0, time.UTC), // 2023 is a non-leap year, 2024 is a leap year
			reminder: Reminder{
				Type: "days before",
				Days: 64, // 33 days before Jan 31st, 29 more days before Feb 29th, 2 more days before March 2nd
			},
			wantOccurring: true,
			wantWhen:      "in 64 days (Sat Mar 2)",
			wantError:     false,
		},
		{
			name: "monthly event on 31st crossing a month boundary",
			event: Event{
				Type: "monthly",
				Name: "Monthly 31st Event",
				Day:  EventDay(31),
			},
			now: time.Date(2025, 2, 25, 10, 0, 0, 0, time.UTC), // non-leap year
			reminder: Reminder{
				Type: "days before",
				Days: 34, // 3 days before Feb 28th, 31 more days before March 31st
			},
			wantOccurring: true,
			wantWhen:      "in 34 days (Mon Mar 31)",
			wantError:     false,
		},
		{
			name: "yearly event with days before reminder crossing a month boundary on a leap year",
			event: Event{
				Type:  "yearly",
				Name:  "Test Event",
				Day:   EventDay(31),
				Month: 3, // March
				Year:  2020,
			},
			now: time.Date(2024, 2, 28, 10, 0, 0, 0, time.UTC), // leap year
			reminder: Reminder{
				Type: "days before",
				Days: 31,
			},
			wantOccurring: false, // Feb 28 + 31 days = March 30th due to leap year
			wantWhen:      "in 31 days (Sat Mar 30)",
			wantError:     false,
		},
		{
			name: "monthly event on 31st with days before instead of same-day",
			event: Event{
				Type: "monthly",
				Name: "Monthly Report",
				Day:  EventDay(31),
			},
			now: time.Date(2025, 4, 30, 10, 0, 0, 0, time.UTC), // last day of the month
			reminder: Reminder{
				Type: "days before",
				Days: 1,
			},
			wantOccurring: false,
			wantWhen:      "tomorrow (Thu May 1)",
			wantError:     false,
		},
		{
			name: "monthly event on 31st with days before on leap year",
			event: Event{
				Type: "monthly",
				Name: "End of Month Report",
				Day:  EventDay(31),
			},
			now: time.Date(2024, 1, 29, 10, 0, 0, 0, time.UTC), // leap year
			reminder: Reminder{
				Type: "days before",
				Days: 31, // 2 days before Jan 31st, 29 more days before Feb 29th
			},
			wantOccurring: true, // Feb 29th is the last day of the month
			wantWhen:      "in 31 days (Thu Feb 29)",
			wantError:     false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			occurring, when, err := tt.event.IsOccurring(tt.now, tt.reminder)

			if occurring != tt.wantOccurring {
				t.Errorf("IsOccurring() occurring = %v, want %v", occurring, tt.wantOccurring)
			}

			if when != tt.wantWhen {
				t.Errorf("IsOccurring() when = %q, want %q", when, tt.wantWhen)
			}

			if (err != nil) != tt.wantError {
				t.Errorf("IsOccurring() error = %v, wantError %v", err, tt.wantError)
			}
		})
	}
}

func TestReminderMessage(t *testing.T) {
	defaultNow := time.Date(2025, 4, 15, 10, 0, 0, 0, time.UTC)

	tests := []struct {
		name          string
		event         Event
		occurringWhen string
		now           time.Time // Zero value means use defaultNow
		wantMessage   string
		wantSubject   string
		wantError     bool
	}{
		{
			name: "anniversary comprehensive - occurrence, special name, both contacts",
			event: Event{
				Type:         "anniversary",
				Name:         "  Alice  ", // Test whitespace trimming
				PartnerName:  "Bob",
				Email:        "alice@test.com",
				PartnerEmail: "bob@test.com",
				Year:         2000,
				Month:        4,
				Day:          EventDay(13),
			},
			occurringWhen: "today",
			wantMessage:   "I just want to remind you that the 25th anniversary (Silver) of Alice and Bob is today.\n\nYou can reach them at alice@test.com and bob@test.com respectively.",
			wantSubject:   "25th anniversary of Alice and Bob",
			wantError:     false,
		},
		{
			name: "anniversary without occurrence and partial contact info",
			event: Event{
				Type:        "anniversary",
				Name:        "Charlie",
				PartnerName: "Diana",
				Email:       "charlie@test.com", // Only one contact
			},
			occurringWhen: "tomorrow (Wed Apr 16)",
			wantMessage:   "I just want to remind you that the anniversary of Charlie and Diana is tomorrow (Wed Apr 16).\n\nYou can reach Charlie at charlie@test.com.",
			wantSubject:   "Anniversary of Charlie and Diana",
			wantError:     false,
		},
		{
			name: "birthday comprehensive - age calculation before birthday in year, email, today timing",
			event: Event{
				Type:  "birthday",
				Name:  "John  ",
				Email: "john@test.com",
				Year:  2000,
				Month: 9,
				Day:   EventDay(13),
			},
			occurringWhen: "today",
			// defaultNow is April 15 2025, which is before the Sept 13 birthday, so John is not 25 yet
			wantMessage: "I just want to remind you that John (john@test.com) is turning 24 today.",
			wantSubject: "John is now 24",
			wantError:   false,
		},
		{
			name: "birthday after birthday in year with today timing uses new age in subject",
			event: Event{
				Type:  "birthday",
				Name:  "John",
				Email: "john@test.com",
				Year:  2000,
				Month: 9,
				Day:   EventDay(13),
			},
			occurringWhen: "today",
			now:           time.Date(2025, 9, 14, 10, 0, 0, 0, time.UTC), // After/birthday date in the same year
			wantMessage:   "I just want to remind you that John (john@test.com) is turning 25 today.",
			wantSubject:   "John is now 25",
			wantError:     false,
		},
		{
			name: "birthday without age and non-today timing",
			event: Event{
				Type: "birthday",
				Name: "Jane",
			},
			occurringWhen: "in 2 days (Thu Apr 17)",
			wantMessage:   "I just want to remind you that Jane is getting one year older in 2 days (Thu Apr 17).",
			wantSubject:   "Jane's birthday",
			wantError:     false,
		},
		{
			name: "yearly comprehensive - occurrence calculation",
			event: Event{
				Type:  "yearly",
				Name:  "Company Founded",
				Year:  2020,
				Month: 4,
				Day:   EventDay(13),
			},
			occurringWhen: "today",
			wantMessage:   "I just want to remind you of the following event today:\n\n6th Company Founded",
			wantSubject:   "Company Founded",
			wantError:     false,
		},
		{
			name: "yearly without occurrence",
			event: Event{
				Type:  "yearly",
				Name:  "Memorial Day",
				Month: 5,
				Day:   EventDay(30),
			},
			occurringWhen: "tomorrow (Wed Apr 16)",
			wantMessage:   "I just want to remind you of the following event tomorrow (Wed Apr 16):\n\nMemorial Day",
			wantSubject:   "Memorial Day",
			wantError:     false,
		},
		{
			name: "monthly event",
			event: Event{
				Type: "monthly",
				Name: "   Team Meeting",
				Day:  EventDay(15),
			},
			occurringWhen: "today",
			wantMessage:   "I just want to remind you of the following event today:\n\nTeam Meeting",
			wantSubject:   "Team Meeting",
			wantError:     false,
		},
		{
			name: "weekly event",
			event: Event{
				Type: "weekly",
				Name: "Garbage Collection     ",
				Day:  EventDay(1),
			},
			occurringWhen: "tomorrow (Wed Apr 16)",
			wantMessage:   "I just want to remind you of the following event tomorrow (Wed Apr 16):\n\nGarbage Collection",
			wantSubject:   "Garbage Collection",
			wantError:     false,
		},

		// Edge cases for date handling
		{
			name: "birthday Feb 29th in non-leap year",
			event: Event{
				Type:  "birthday",
				Name:  "Leap Year Baby",
				Year:  2000, // Born in leap year
				Month: 2,
				Day:   EventDay(29),
			},
			occurringWhen: "today",
			now:           time.Date(2025, 2, 28, 10, 0, 0, 0, time.UTC),                        // Feb 28th in non-leap year 2025
			wantMessage:   "I just want to remind you that Leap Year Baby is turning 25 today.", // 2025 is non-leap year, so Feb 29th becomes Feb 28th
			wantSubject:   "Leap Year Baby is now 25",
			wantError:     false,
		},
		{
			name: "monthly event 31st in 30-day month",
			event: Event{
				Type: "monthly",
				Name: "End of Month Report",
				Day:  EventDay(31), // Set to 31st but testing in a 30-day month
			},
			occurringWhen: "today",
			now:           time.Date(2025, 9, 30, 10, 0, 0, 0, time.UTC), // Sept 30th (last day of 30-day month)
			wantMessage:   "I just want to remind you of the following event today:\n\nEnd of Month Report",
			wantSubject:   "End of Month Report",
			wantError:     false,
		},

		// Error case
		{
			name: "unknown event type",
			event: Event{
				Type: "invalid",
				Name: "Test Event",
			},
			occurringWhen: "today",
			wantMessage:   "",
			wantSubject:   "",
			wantError:     true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			testNow := defaultNow
			if !tt.now.IsZero() {
				testNow = tt.now
			}

			gotMessage, gotSubject, err := tt.event.ReminderMessage(testNow, tt.occurringWhen)

			if (err != nil) != tt.wantError {
				t.Errorf("ReminderMessage() error = %v, wantError %v", err, tt.wantError)
				return
			}

			if err != nil {
				return // Don't check message/subject if we expected an error
			}

			if gotMessage != tt.wantMessage {
				t.Errorf("ReminderMessage() gotMessage = %q, want %q", gotMessage, tt.wantMessage)
			}
			if gotSubject != tt.wantSubject {
				t.Errorf("ReminderMessage() gotSubject = %q, want %q", gotSubject, tt.wantSubject)
			}
		})
	}
}

func TestEventDayUnmarshalText(t *testing.T) {
	tests := []struct {
		input string
		want  EventDay
	}{
		{"0", EventDay(0)},
		{"1", EventDay(1)},
		{"15", EventDay(15)},
		{"32", EventDay(32)},
		{"100", EventDay(100)},

		// Full day names
		{"Sunday", EventDay(0)},
		{"monday", EventDay(1)},
		{"Tuesday   ", EventDay(2)},
		{"Wednesday", EventDay(3)},
		{" Thursday  ", EventDay(4)},
		{"Friday", EventDay(5)},
		{"SATURDAY", EventDay(6)},

		// Common abbreviations
		{"Sun", EventDay(0)},
		{"Mon", EventDay(1)},
		{"Tue", EventDay(2)},
		{"  Wed  ", EventDay(3)},
		{"Thu", EventDay(4)},
		{"Fri", EventDay(5)},
		{"Sat", EventDay(6)},

		// Invalid inputs
		{"invalid", EventDay(-1)},
		{"-1", EventDay(-1)},
		{"", EventDay(-1)},
	}

	for _, tt := range tests {
		t.Run(tt.input, func(t *testing.T) {
			var ed EventDay
			err := ed.UnmarshalText([]byte(tt.input))
			if err != nil && (ed != -1 || tt.want != -1) {
				t.Errorf("EventDay.UnmarshalText(%q) unexpected error: %v", tt.input, err)
			}
			if ed != tt.want {
				t.Errorf("EventDay.UnmarshalText(%q) = %d, want %d", tt.input, ed, tt.want)
			}
		})
	}
}

func TestIsSameDay(t *testing.T) {
	tests := []struct {
		name          string
		now           time.Time
		originalDay   EventDay
		originalMonth time.Month
		want          bool
	}{
		{
			name:          "exact match - regular date",
			now:           time.Date(2023, time.March, 15, 10, 30, 0, 0, time.UTC),
			originalDay:   EventDay(15),
			originalMonth: time.March,
			want:          true,
		},
		{
			name:          "different day same month",
			now:           time.Date(2023, time.March, 16, 10, 30, 0, 0, time.UTC),
			originalDay:   EventDay(15),
			originalMonth: time.March,
			want:          false,
		},
		{
			name:          "same day different month",
			now:           time.Date(2023, time.April, 15, 10, 30, 0, 0, time.UTC),
			originalDay:   EventDay(15),
			originalMonth: time.March,
			want:          false,
		},
		{
			name:          "Feb 29 birthday on Feb 28 in non-leap year",
			now:           time.Date(2023, time.February, 28, 10, 30, 0, 0, time.UTC),
			originalDay:   EventDay(29),
			originalMonth: time.February,
			want:          true,
		},
		{
			name:          "Feb 29 birthday on Feb 29 in leap year",
			now:           time.Date(2024, time.February, 29, 10, 30, 0, 0, time.UTC),
			originalDay:   EventDay(29),
			originalMonth: time.February,
			want:          true,
		},
		{
			name:          "Feb 29 birthday on Feb 28 in leap year (should not match)",
			now:           time.Date(2024, time.February, 28, 10, 30, 0, 0, time.UTC),
			originalDay:   EventDay(29),
			originalMonth: time.February,
			want:          false,
		},
		{
			name:          "regular Feb 28 birthday on Feb 28 in non-leap year",
			now:           time.Date(2023, time.February, 28, 10, 30, 0, 0, time.UTC),
			originalDay:   EventDay(28),
			originalMonth: time.February,
			want:          true,
		},
		{
			name:          "regular Feb 28 birthday on Feb 28 in leap year",
			now:           time.Date(2024, time.February, 28, 10, 30, 0, 0, time.UTC),
			originalDay:   EventDay(28),
			originalMonth: time.February,
			want:          true,
		},
		{
			name:          "Feb 27 should not match Feb 29 in non-leap year",
			now:           time.Date(2023, time.February, 27, 10, 30, 0, 0, time.UTC),
			originalDay:   EventDay(29),
			originalMonth: time.February,
			want:          false,
		},
		{
			name:          "March 1 should not match Feb 29 in non-leap year",
			now:           time.Date(2023, time.March, 1, 10, 30, 0, 0, time.UTC),
			originalDay:   EventDay(29),
			originalMonth: time.February,
			want:          false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			event := Event{
				Day:   tt.originalDay,
				Month: tt.originalMonth,
			}
			result := event.IsSameDay(tt.now)
			if result != tt.want {
				t.Errorf("Event{Day: %d, Month: %v}.IsSameDay(%v) = %v, want %v",
					tt.originalDay, tt.originalMonth, tt.now, result, tt.want)
			}
		})
	}
}

func TestEventDayDayOfTheWeek(t *testing.T) {
	tests := []struct {
		day  EventDay
		want time.Weekday
	}{
		{EventDay(1), time.Monday},
		{EventDay(2), time.Tuesday},
		{EventDay(6), time.Saturday},

		// Sunday cases (0 and 7 should both become Sunday)
		{EventDay(0), time.Sunday}, // already 0 in Go
		{EventDay(7), time.Sunday}, // cron-style

		// Out of range values should default to Sunday
		{EventDay(-1), time.Sunday},
		{EventDay(8), time.Sunday},
	}

	for _, tt := range tests {
		t.Run(fmt.Sprintf("day_%d", tt.day), func(t *testing.T) {
			got := tt.day.DayOfTheWeek()
			if got != tt.want {
				t.Errorf("EventDay(%d).DayOfTheWeek() = %v, want %v", tt.day, got, tt.want)
			}
		})
	}
}

func TestEventTypeUnmarshalText(t *testing.T) {
	tests := []struct {
		input string
		want  EventType
	}{
		{"anniversary", EventTypeAnniversary},
		{"birthday", EventTypeBirthday},
		{"yearly", EventTypeYearly},
		{"monthly", EventTypeMonthly},
		{"weekly", EventTypeWeekly},
		{"BIRTHDAY", EventTypeBirthday},
		{"  Anniversary  ", EventTypeAnniversary},
		{"unknown", EventType("unknown")},
		{"", EventType("")},
	}

	for _, tt := range tests {
		t.Run(tt.input, func(t *testing.T) {
			var et EventType
			err := et.UnmarshalText([]byte(tt.input))
			if err != nil {
				t.Errorf("EventType.UnmarshalText(%q) unexpected error: %v", tt.input, err)
			}
			if et != tt.want {
				t.Errorf("EventType.UnmarshalText(%q) = %q, want %q", tt.input, et, tt.want)
			}
		})
	}
}

func TestAnniversaryName(t *testing.T) {
	tests := []struct {
		occurrence int
		want       string
	}{
		// Test a few representative values
		{1, "Paper"},
		{25, "Silver"},
		{50, "Gold"},
		{70, "Platinum"},

		// Test degenerate cases
		{0, ""},
		{-1, ""},
		{16, ""},  // year without special name
		{100, ""}, // year without special name
	}

	for _, tt := range tests {
		got := anniversaryName(tt.occurrence)
		if got != tt.want {
			t.Errorf("anniversaryName(%d) = %q, want %q", tt.occurrence, got, tt.want)
		}
	}
}

func TestFormatOrdinal(t *testing.T) {
	tests := []struct {
		input int
		want  string
	}{
		// Test the special cases for 11th, 12th, 13th
		{11, "11th"},
		{12, "12th"},
		{13, "13th"},
		{111, "111th"},
		{112, "112th"},
		{113, "113th"},

		// Test 1st endings
		{1, "1st"},
		{21, "21st"},
		{31, "31st"},
		{101, "101st"},
		{121, "121st"},

		// Test 2nd endings
		{2, "2nd"},
		{22, "22nd"},
		{32, "32nd"},
		{102, "102nd"},
		{122, "122nd"},

		// Test 3rd endings
		{3, "3rd"},
		{23, "23rd"},
		{33, "33rd"},
		{103, "103rd"},
		{123, "123rd"},

		// Test th endings for other numbers
		{0, "0th"},
		{4, "4th"},
		{5, "5th"},
		{6, "6th"},
		{7, "7th"},
		{8, "8th"},
		{9, "9th"},
		{10, "10th"},
		{14, "14th"},
		{20, "20th"},
		{28, "28th"},
		{100, "100th"},
		{109, "109th"},
		{1000, "1000th"},
	}

	for _, tt := range tests {
		result := formatOrdinal(tt.input)
		if result != tt.want {
			t.Errorf("formatOrdinal(%d) = %s; want %s", tt.input, result, tt.want)
		}
	}
}
