package timeparsing

import (
	"testing"
	"time"
)

// TestParseNaturalLanguage tests the NLP parser wrapper.
func TestParseNaturalLanguage(t *testing.T) {
	// Fixed reference time: Wednesday, January 15, 2025, 10:00:00 AM
	now := time.Date(2025, 1, 15, 10, 0, 0, 0, time.Local)

	tests := []struct {
		name      string
		input     string
		wantYear  int
		wantMonth time.Month
		wantDay   int
		wantHour  int // -1 means don't check hour
		wantErr   bool
	}{
		// Relative days
		{
			name:      "tomorrow",
			input:     "tomorrow",
			wantYear:  2025,
			wantMonth: time.January,
			wantDay:   16,
			wantHour:  -1,
			wantErr:   false,
		},
		{
			name:      "yesterday",
			input:     "yesterday",
			wantYear:  2025,
			wantMonth: time.January,
			wantDay:   14,
			wantHour:  -1,
			wantErr:   false,
		},

		// Next weekday (reference is Wednesday Jan 15)
		{
			name:      "next monday",
			input:     "next monday",
			wantYear:  2025,
			wantMonth: time.January,
			wantDay:   20, // Next Monday after Jan 15
			wantHour:  -1,
			wantErr:   false,
		},
		{
			name:      "next friday",
			input:     "next friday",
			wantYear:  2025,
			wantMonth: time.January,
			wantDay:   17, // Friday Jan 17 (same week)
			wantHour:  -1,
			wantErr:   false,
		},

		// With time
		{
			name:      "tomorrow at 9am",
			input:     "tomorrow at 9am",
			wantYear:  2025,
			wantMonth: time.January,
			wantDay:   16,
			wantHour:  9,
			wantErr:   false,
		},
		{
			name:      "next monday at 2pm",
			input:     "next monday at 2pm",
			wantYear:  2025,
			wantMonth: time.January,
			wantDay:   20,
			wantHour:  14,
			wantErr:   false,
		},

		// Relative durations (NLP style)
		{
			name:      "in 3 days",
			input:     "in 3 days",
			wantYear:  2025,
			wantMonth: time.January,
			wantDay:   18,
			wantHour:  -1,
			wantErr:   false,
		},
		{
			name:      "in 1 week",
			input:     "in 1 week",
			wantYear:  2025,
			wantMonth: time.January,
			wantDay:   22,
			wantHour:  -1,
			wantErr:   false,
		},

		// Past relative
		{
			name:      "3 days ago",
			input:     "3 days ago",
			wantYear:  2025,
			wantMonth: time.January,
			wantDay:   12,
			wantHour:  -1,
			wantErr:   false,
		},

		// Invalid inputs
		{
			name:    "random text",
			input:   "not a date at all",
			wantErr: true,
		},
		{
			name:    "empty string",
			input:   "",
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := ParseNaturalLanguage(tt.input, now)
			if (err != nil) != tt.wantErr {
				t.Errorf("ParseNaturalLanguage(%q) error = %v, wantErr %v", tt.input, err, tt.wantErr)
				return
			}
			if tt.wantErr {
				return
			}

			if got.Year() != tt.wantYear {
				t.Errorf("ParseNaturalLanguage(%q) year = %d, want %d", tt.input, got.Year(), tt.wantYear)
			}
			if got.Month() != tt.wantMonth {
				t.Errorf("ParseNaturalLanguage(%q) month = %v, want %v", tt.input, got.Month(), tt.wantMonth)
			}
			if got.Day() != tt.wantDay {
				t.Errorf("ParseNaturalLanguage(%q) day = %d, want %d", tt.input, got.Day(), tt.wantDay)
			}
			if tt.wantHour >= 0 && got.Hour() != tt.wantHour {
				t.Errorf("ParseNaturalLanguage(%q) hour = %d, want %d", tt.input, got.Hour(), tt.wantHour)
			}
		})
	}
}

// TestParseRelativeTime tests the layered parsing function.
func TestParseRelativeTime(t *testing.T) {
	// Fixed reference time: Wednesday, January 15, 2025, 10:00:00 AM
	now := time.Date(2025, 1, 15, 10, 0, 0, 0, time.Local)

	tests := []struct {
		name      string
		input     string
		wantYear  int
		wantMonth time.Month
		wantDay   int
		wantHour  int // -1 means don't check hour
		wantErr   bool
	}{
		// Layer 1: Compact duration (should be tried first)
		{
			name:      "compact +1d",
			input:     "+1d",
			wantYear:  2025,
			wantMonth: time.January,
			wantDay:   16,
			wantHour:  10, // Same hour as now
			wantErr:   false,
		},
		{
			name:      "compact +6h",
			input:     "+6h",
			wantYear:  2025,
			wantMonth: time.January,
			wantDay:   15,
			wantHour:  16, // 10 + 6 = 16
			wantErr:   false,
		},

		// Layer 2: NLP
		{
			name:      "NLP tomorrow",
			input:     "tomorrow",
			wantYear:  2025,
			wantMonth: time.January,
			wantDay:   16,
			wantHour:  -1,
			wantErr:   false,
		},
		{
			name:      "NLP next monday",
			input:     "next monday",
			wantYear:  2025,
			wantMonth: time.January,
			wantDay:   20,
			wantHour:  -1,
			wantErr:   false,
		},

		// Layer 3: Date-only
		{
			name:      "date-only",
			input:     "2025-02-01",
			wantYear:  2025,
			wantMonth: time.February,
			wantDay:   1,
			wantHour:  0,
			wantErr:   false,
		},

		// Layer 4: RFC3339
		{
			name:      "RFC3339",
			input:     "2025-03-15T14:30:00Z",
			wantYear:  2025,
			wantMonth: time.March,
			wantDay:   15,
			wantHour:  14,
			wantErr:   false,
		},

		// Invalid
		{
			name:    "invalid expression",
			input:   "not-a-date",
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := ParseRelativeTime(tt.input, now)
			if (err != nil) != tt.wantErr {
				t.Errorf("ParseRelativeTime(%q) error = %v, wantErr %v", tt.input, err, tt.wantErr)
				return
			}
			if tt.wantErr {
				return
			}

			if got.Year() != tt.wantYear {
				t.Errorf("ParseRelativeTime(%q) year = %d, want %d", tt.input, got.Year(), tt.wantYear)
			}
			if got.Month() != tt.wantMonth {
				t.Errorf("ParseRelativeTime(%q) month = %v, want %v", tt.input, got.Month(), tt.wantMonth)
			}
			if got.Day() != tt.wantDay {
				t.Errorf("ParseRelativeTime(%q) day = %d, want %d", tt.input, got.Day(), tt.wantDay)
			}
			if tt.wantHour >= 0 && got.Hour() != tt.wantHour {
				t.Errorf("ParseRelativeTime(%q) hour = %d, want %d", tt.input, got.Hour(), tt.wantHour)
			}
		})
	}
}

// TestParseRelativeTime_LayerPrecedence verifies that layers are tried in order.
func TestParseRelativeTime_LayerPrecedence(t *testing.T) {
	now := time.Date(2025, 1, 15, 10, 0, 0, 0, time.Local)

	// "+1d" is valid compact duration, should NOT be parsed as NLP
	t1, err := ParseRelativeTime("+1d", now)
	if err != nil {
		t.Fatalf("ParseRelativeTime(\"+1d\") failed: %v", err)
	}
	// Compact duration adds exactly 1 day, preserving time
	expected := now.AddDate(0, 0, 1)
	if !t1.Equal(expected) {
		t.Errorf("ParseRelativeTime(\"+1d\") = %v, want %v (compact duration should take precedence)", t1, expected)
	}

	// "2025-01-20" should parse as date-only, not NLP
	t2, err := ParseRelativeTime("2025-01-20", now)
	if err != nil {
		t.Fatalf("ParseRelativeTime(\"2025-01-20\") failed: %v", err)
	}
	if t2.Day() != 20 || t2.Month() != time.January || t2.Year() != 2025 {
		t.Errorf("ParseRelativeTime(\"2025-01-20\") = %v, want Jan 20, 2025", t2)
	}
}
