package wav

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
}

// parseTestCase defines a table-driven test case for ParseFilenameTimestamps.
type parseTestCase struct {
	name     string
	files    []string
	expected map[int]expectedTS // index → expected timestamp
}

func runParseTestCase(t *testing.T, tc parseTestCase) {
	t.Helper()
	results, err := ParseFilenameTimestamps(tc.files)
	if err != nil {
		t.Fatalf("Failed to parse filenames: %v", err)
	}
	if len(results) != len(tc.files) {
		t.Fatalf("Expected %d results, got %d", len(tc.files), len(results))
	}
	for idx, want := range tc.expected {
		assertTimestamp(t, results[idx].Timestamp, want)
	}
}

func TestParseFilenameTimestamps(t *testing.T) {
	cases := []parseTestCase{
		{
			name:  "YYMMDD format (test case a)",
			files: []string{"201012_123456.wav", "201014_123456.WAV", "201217_123456.wav", "211122_123456.WAV"},
			expected: map[int]expectedTS{
				0: {2020, 10, 12, 12, 34, 56}, // Year 20 → 2020
				3: {2021, 11, 22, 12, 34, 56},
			},
		},
		{
			name:  "DDMMYY format (test case b)",
			files: []string{"121020_123456.WAV", "141020_123456.wav", "171220_123456.WAV", "221121_123456.wav"},
			expected: map[int]expectedTS{
				0: {2020, 10, 12, 12, 34, 56},
				2: {2020, 12, 17, 12, 34, 56},
			},
		},
		{
			name:  "YYYYMMDD format (test case c)",
			files: []string{"20230609_103000.WAV", "20241109_201504.wav"},
			expected: map[int]expectedTS{
				0: {2023, 6, 9, 10, 30, 0},
				1: {2024, 11, 9, 20, 15, 4},
			},
		},
		{
			name:  "6-digit with variance detection (test case d)",
			files: []string{"120119_003002.wav", "180120_231502.wav", "170122_010005.wav", "010419_234502.WAV", "310320_231502.wav", "220824_231502.WAV", "240123_231502.wav"},
			expected: map[int]expectedTS{
				0: {2019, 1, 12, 0, 30, 2}, // DDMMYY
				4: {2020, 3, 31, 23, 15, 2},
			},
		},
		{
			name:  "prefixes (test case e)",
			files: []string{"XYZ123_7689_20230609_103000.WAV", "string 20241109_201504.wav"},
			expected: map[int]expectedTS{
				0: {2023, 6, 9, 10, 30, 0},
				1: {2024, 11, 9, 20, 15, 4},
			},
		},
		{
			name:  "complex prefixes (test case f)",
			files: []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"},
			expected: map[int]expectedTS{
				0: {2020, 1, 18, 23, 15, 2},
				1: {2019, 1, 12, 0, 30, 2},
				4: {2020, 3, 31, 23, 15, 2},
			},
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			runParseTestCase(t, tc)
		})
	}
}

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})
	})
}