package utils

import (
	"testing"
	"time"
)

type expectedTS struct {
	Year, Month, Day, Hour, Minute, Second int
}

func assertTimestamp(t *testing.T, got time.Time, want expectedTS) {
	t.Helper()
	t.Helper()
	if got.Year() != want.Year {
		t.Errorf("Year: got %d, want %d", got.Year(), want.Year)
	}
	if got.Month() != time.Month(want.Month) {
		t.Errorf("Month: got %d, want %d", got.Month(), want.Month)
	}
	if got.Day() != want.Day {
		t.Errorf("Day: got %d, want %d", got.Day(), want.Day)
	}
	if got.Hour() != want.Hour {
		t.Errorf("Hour: got %d, want %d", got.Hour(), want.Hour)
	}
	if got.Minute() != want.Minute {
		t.Errorf("Minute: got %d, want %d", got.Minute(), want.Minute)
	}
	if got.Second() != want.Second {
		t.Errorf("Second: got %d, want %d", got.Second(), want.Second)
	}
}

func assertOffset(t *testing.T, got time.Time, wantSeconds int) {
	t.Helper()
	_, offset := got.Zone()
	if offset != wantSeconds {
		t.Errorf("Offset: got %d seconds, want %d seconds", offset, wantSeconds)
	}
}

// parseAndApply is a test helper that parses filenames and applies a timezone offset.
func parseAndApply(t *testing.T, filenames []string, tz string) []time.Time {
	t.Helper()
	parsed, err := ParseFilenameTimestamps(filenames)
	if err != nil {
		t.Fatalf("Failed to parse filenames: %v", err)
	}
	results, err := ApplyTimezoneOffset(parsed, tz)
	if err != nil {
		t.Fatalf("Failed to apply timezone: %v", err)
	}
	return results
}

func TestParseFilenameTimestamps(t *testing.T) {
	t.Run("should parse YYMMDD format (test case a)", func(t *testing.T) {
		filenames := []string{
			"201012_123456.wav",
			"201014_123456.WAV",
			"201217_123456.wav",
			"211122_123456.WAV",
		}

		results, err := ParseFilenameTimestamps(filenames)
		if err != nil {
			t.Fatalf("Failed to parse filenames: %v", err)
		}

		if len(results) != 4 {
			t.Fatalf("Expected 4 results, got %d", len(results))
		}

		// Year 20 should be interpreted as 2020 (less variance than days)
		assertTimestamp(t, results[0].Timestamp, expectedTS{2020, 10, 12, 12, 34, 56})
		assertTimestamp(t, results[3].Timestamp, expectedTS{2021, 11, 22, 12, 34, 56})
	})

	t.Run("should parse DDMMYY format (test case b)", func(t *testing.T) {
		filenames := []string{
			"121020_123456.WAV",
			"141020_123456.wav",
			"171220_123456.WAV",
			"221121_123456.wav",
		}

		results, err := ParseFilenameTimestamps(filenames)
		if err != nil {
			t.Fatalf("Failed to parse filenames: %v", err)
		}

		if len(results) != 4 {
			t.Fatalf("Expected 4 results, got %d", len(results))
		}

		// More variance in first two digits (12,14,17,22) than last two (20,20,20,21)
		// So DDMMYY format: day=first, month=middle, year=last+2000
		assertTimestamp(t, results[0].Timestamp, expectedTS{2020, 10, 12, 12, 34, 56})
		assertTimestamp(t, results[2].Timestamp, expectedTS{2020, 12, 17, 12, 34, 56})
	})

	t.Run("should parse YYYYMMDD format (test case c)", func(t *testing.T) {
		filenames := []string{
			"20230609_103000.WAV",
			"20241109_201504.wav",
		}

		results, err := ParseFilenameTimestamps(filenames)
		if err != nil {
			t.Fatalf("Failed to parse filenames: %v", err)
		}

		if len(results) != 2 {
			t.Fatalf("Expected 2 results, got %d", len(results))
		}

		assertTimestamp(t, results[0].Timestamp, expectedTS{2023, 6, 9, 10, 30, 0})
		assertTimestamp(t, results[1].Timestamp, expectedTS{2024, 11, 9, 20, 15, 4})
	})

	t.Run("should parse mixed 6-digit dates with variance detection (test case d)", func(t *testing.T) {
		filenames := []string{
			"120119_003002.wav",
			"180120_231502.wav",
			"170122_010005.wav",
			"010419_234502.WAV",
			"310320_231502.wav",
			"220824_231502.WAV",
			"240123_231502.wav",
		}

		results, err := ParseFilenameTimestamps(filenames)
		if err != nil {
			t.Fatalf("Failed to parse filenames: %v", err)
		}

		if len(results) != 7 {
			t.Fatalf("Expected 7 results, got %d", len(results))
		}

		// First two digits: 12,18,17,01,31,22,24 (variance = high)
		// Last two digits: 19,20,22,19,20,24,23 (variance = lower)
		// Should be DDMMYY format
		assertTimestamp(t, results[0].Timestamp, expectedTS{2019, 1, 12, 0, 30, 2})
		assertTimestamp(t, results[4].Timestamp, expectedTS{2020, 3, 31, 23, 15, 2})
	})

	t.Run("should parse filenames with prefixes (test case e)", func(t *testing.T) {
		filenames := []string{
			"XYZ123_7689_20230609_103000.WAV",
			"string 20241109_201504.wav",
		}

		results, err := ParseFilenameTimestamps(filenames)
		if err != nil {
			t.Fatalf("Failed to parse filenames: %v", err)
		}

		if len(results) != 2 {
			t.Fatalf("Expected 2 results, got %d", len(results))
		}

		assertTimestamp(t, results[0].Timestamp, expectedTS{2023, 6, 9, 10, 30, 0})
		assertTimestamp(t, results[1].Timestamp, expectedTS{2024, 11, 9, 20, 15, 4})
	})

	t.Run("should parse filenames with complex prefixes (test case f)", func(t *testing.T) {
		filenames := []string{
			"abcdefg__1234_180120_231502.wav",
			"string 120119_003002.wav",
			"ABCD EFG___170122_010005.wav",
			"BHD_1234 010419_234502.WAV",
			"cill xyz 310320_231502.wav",
			"220824_231502.WAV",
			"240123_231502.wav",
		}

		results, err := ParseFilenameTimestamps(filenames)
		if err != nil {
			t.Fatalf("Failed to parse filenames: %v", err)
		}

		if len(results) != 7 {
			t.Fatalf("Expected 7 results, got %d", len(results))
		}

		// Same pattern as test case d - should be DDMMYY
		assertTimestamp(t, results[0].Timestamp, expectedTS{2020, 1, 18, 23, 15, 2})
		assertTimestamp(t, results[1].Timestamp, expectedTS{2019, 1, 12, 0, 30, 2})
		assertTimestamp(t, results[4].Timestamp, expectedTS{2020, 3, 31, 23, 15, 2})
	})
}

func TestParseFilenameTimestampsErrors(t *testing.T) {
	t.Run("should throw error for empty filename array", func(t *testing.T) {
		_, err := ParseFilenameTimestamps([]string{})
		if err == nil {
			t.Error("Expected error for empty filename array")
		}
		if err != nil && err.Error() != "no filenames provided" {
			t.Logf("Error message: %v", err)
		}
	})

	t.Run("should throw error for filenames without date patterns", func(t *testing.T) {
		_, err := ParseFilenameTimestamps([]string{"invalid_filename.wav"})
		if err == nil {
			t.Error("Expected error for filenames without date patterns")
		}
	})

	t.Run("should throw error for mixed date formats", func(t *testing.T) {
		mixedFormats := []string{"201012_123456.wav", "20231012_123456.wav"} // 6-digit vs 8-digit
		_, err := ParseFilenameTimestamps(mixedFormats)
		if err == nil {
			t.Error("Expected error for mixed date formats")
		}
	})

	t.Run("should throw error for wrong length patterns", func(t *testing.T) {
		wrongLength := []string{"2010_123456.wav"} // 4 digits instead of 6 or 8
		_, err := ParseFilenameTimestamps(wrongLength)
		if err == nil {
			t.Error("Expected error for wrong length patterns")
		}
	})

	t.Run("should throw error when not enough files for 6-digit disambiguation", func(t *testing.T) {
		singleFile := []string{"120119_003002.wav"}
		_, err := ParseFilenameTimestamps(singleFile)
		if err == nil {
			t.Error("Expected error when not enough files for 6-digit disambiguation")
		}
	})
}

func TestApplyTimezoneOffset(t *testing.T) {
	t.Run("should apply UTC timezone correctly", func(t *testing.T) {
		results := parseAndApply(t, []string{"201012_123456.wav", "201014_123456.WAV"}, "UTC")
		if len(results) != 2 {
			t.Fatalf("Expected 2 results, got %d", len(results))
		}
		assertOffset(t, results[0], 0)
	})

	t.Run("should use fixed offset for entire cluster spanning DST transition", func(t *testing.T) {
		// Auckland DST ended April 4, 2021 (UTC+13 -> UTC+12)
		results := parseAndApply(t, []string{
			"20210401_120000.wav", // April 1st - DST active (UTC+13)
			"20210410_120000.wav", // April 10th - DST ended (would be UTC+12 if DST applied)
			"20210420_120000.wav", // April 20th - Standard time
		}, "Pacific/Auckland")

		if len(results) != 3 {
			t.Fatalf("Expected 3 results, got %d", len(results))
		}

		// All files should use UTC+13 offset (from earliest file: April 1st)
		for _, r := range results {
			assertOffset(t, r, 13*3600)
		}

		// All at 12:00 local - 13h = 23:00 UTC previous day
		assertTimestamp(t, results[0].UTC(), expectedTS{2021, 3, 31, 23, 0, 0})
		assertTimestamp(t, results[1].UTC(), expectedTS{2021, 4, 9, 23, 0, 0})
		assertTimestamp(t, results[2].UTC(), expectedTS{2021, 4, 19, 23, 0, 0})
	})

	t.Run("should handle out-of-order filenames correctly", func(t *testing.T) {
		results := parseAndApply(t, []string{
			"20210410_120000.wav", // April 10th (later)
			"20210401_120000.wav", // April 1st (earliest - determines offset)
			"20210405_120000.wav", // April 5th (middle)
		}, "Pacific/Auckland")

		// All files use UTC+13 (from April 1st, the earliest)
		for _, r := range results {
			assertOffset(t, r, 13*3600)
		}

		// Results maintain original filename order
		assertTimestamp(t, results[0], expectedTS{2021, 4, 10, 12, 0, 0})
		assertTimestamp(t, results[1], expectedTS{2021, 4, 1, 12, 0, 0})
		assertTimestamp(t, results[2], expectedTS{2021, 4, 5, 12, 0, 0})
	})

	t.Run("should apply fixed offset consistently across large time spans", func(t *testing.T) {
		results := parseAndApply(t, []string{
			"20210215_120000.wav", // February (summer, UTC+13)
			"20210615_120000.wav", // June (winter, would be UTC+12 if DST applied)
			"20210815_120000.wav", // August (winter)
		}, "Pacific/Auckland")

		// All files use offset from earliest (February): UTC+13
		for _, r := range results {
			assertOffset(t, r, 13*3600)
		}

		// 12:00 local - 13h = 23:00 UTC previous day
		assertTimestamp(t, results[0].UTC(), expectedTS{2021, 2, 14, 23, 0, 0})
		assertTimestamp(t, results[1].UTC(), expectedTS{2021, 6, 14, 23, 0, 0})
		assertTimestamp(t, results[2].UTC(), expectedTS{2021, 8, 14, 23, 0, 0})
	})

	t.Run("should handle US DST transitions with fixed offset", func(t *testing.T) {
		results := parseAndApply(t, []string{
			"20210310_120000.wav", // March 10th - before DST (UTC-5)
			"20210320_120000.wav", // March 20th - after DST (would be UTC-4)
		}, "America/New_York")

		// All files use offset from earliest (March 10th): UTC-5
		for _, r := range results {
			assertOffset(t, r, -5*3600)
		}

		// 12:00 local + 5h = 17:00 UTC
		assertTimestamp(t, results[0].UTC(), expectedTS{2021, 3, 10, 17, 0, 0})
		assertTimestamp(t, results[1].UTC(), expectedTS{2021, 3, 20, 17, 0, 0})
	})

	t.Run("should handle empty timestamps array", func(t *testing.T) {
		_, err := ApplyTimezoneOffset([]FilenameTimestamp{}, "UTC")
		if err == nil {
			t.Error("Expected error for empty timestamps array")
		}
	})

	t.Run("should handle invalid timezone", func(t *testing.T) {
		filenames := []string{"20210401_120000.wav"}
		parsed, err := ParseFilenameTimestamps(filenames)
		if err != nil {
			t.Fatalf("Failed to parse filenames: %v", err)
		}

		_, err = ApplyTimezoneOffset(parsed, "Invalid/Timezone")
		if err == nil {
			t.Error("Expected error for invalid timezone")
		}
	})
}

func TestHasTimestampFilename(t *testing.T) {
	testCases := []struct {
		filename string
		expected bool
	}{
		{"201012_123456.wav", true},
		{"20230609_103000.WAV", true},
		{"invalid_filename.wav", false},
		{"201012_123456.txt", false},
		{"201012.wav", false},
		{"_123456.wav", false},
		{"", false},
	}

	for _, tc := range testCases {
		t.Run(tc.filename, func(t *testing.T) {
			result := HasTimestampFilename(tc.filename)
			if result != tc.expected {
				t.Errorf("HasTimestampFilename(%q) = %v, want %v", tc.filename, result, tc.expected)
			}
		})
	}
}

func TestFilenameParserEdgeCases(t *testing.T) {
	t.Run("should handle case-insensitive file extensions", func(t *testing.T) {
		filenames := []string{
			"201012_123456.wav",
			"201014_123456.WAV",
			"201217_123456.Wav",
		}

		results, err := ParseFilenameTimestamps(filenames)
		if err != nil {
			t.Fatalf("Failed to parse filenames: %v", err)
		}

		if len(results) != 3 {
			t.Errorf("Expected 3 results, got %d", len(results))
		}
	})

	t.Run("should validate invalid dates", func(t *testing.T) {
		// 32nd day doesn't exist - should be caught by validation
		filenames := []string{"20240132_120000.wav"}

		_, err := ParseFilenameTimestamps(filenames)
		if err == nil {
			t.Error("Expected error for invalid date (day 32)")
		}
	})

	t.Run("should validate invalid months", func(t *testing.T) {
		// 13th month doesn't exist
		filenames := []string{"20241301_120000.wav"}

		_, err := ParseFilenameTimestamps(filenames)
		if err == nil {
			t.Error("Expected error for invalid month (13)")
		}
	})

	t.Run("should handle February 29th in leap year", func(t *testing.T) {
		filenames := []string{"20240229_120000.wav"} // 2024 is a leap year

		results, err := ParseFilenameTimestamps(filenames)
		if err != nil {
			t.Fatalf("Failed to parse leap year date: %v", err)
		}

		if results[0].Timestamp.Day() != 29 {
			t.Errorf("Expected day 29, got %d", results[0].Timestamp.Day())
		}
	})

	t.Run("should reject February 29th in non-leap year", func(t *testing.T) {
		filenames := []string{"20230229_120000.wav"} // 2023 is not a leap year

		_, err := ParseFilenameTimestamps(filenames)
		if err == nil {
			t.Error("Expected error for Feb 29th in non-leap year")
		}
	})
}

func TestUTCConversionCorrectness(t *testing.T) {
	t.Run("should convert Pacific/Auckland night recordings correctly to UTC", func(t *testing.T) {
		// 21:00 Pacific/Auckland (May = UTC+12) → 09:00 UTC same day
		results := parseAndApply(t, []string{"20210505_210000.wav"}, "Pacific/Auckland")
		assertTimestamp(t, results[0].UTC(), expectedTS{2021, 5, 5, 9, 0, 0})
	})

	t.Run("should convert day recordings correctly to UTC", func(t *testing.T) {
		// 12:00 Pacific/Auckland (May = UTC+12) → 00:00 UTC same day
		results := parseAndApply(t, []string{"20210505_120000.wav"}, "Pacific/Auckland")
		assertTimestamp(t, results[0].UTC(), expectedTS{2021, 5, 5, 0, 0, 0})
	})

	t.Run("should handle date rollover correctly", func(t *testing.T) {
		// 02:00 Pacific/Auckland (May = UTC+12) → 14:00 UTC previous day
		results := parseAndApply(t, []string{"20210505_020000.wav"}, "Pacific/Auckland")
		assertTimestamp(t, results[0].UTC(), expectedTS{2021, 5, 4, 14, 0, 0})
	})

	t.Run("should convert correctly for negative offset timezone", func(t *testing.T) {
		// 15:00 New York (June = UTC-4 during DST) → 19:00 UTC same day
		results := parseAndApply(t, []string{"20210615_150000.wav"}, "America/New_York")
		assertTimestamp(t, results[0].UTC(), expectedTS{2021, 6, 15, 19, 0, 0})
	})
}