package utils

import (
	"skraak_mcp/db"
	"strings"
	"testing"
	"time"
)

func TestIsAudioMoth(t *testing.T) {
	t.Run("should identify AudioMoth files by artist field", func(t *testing.T) {
		if !IsAudioMoth("", "AudioMoth") {
			t.Error("Should identify AudioMoth by artist field")
		}
		if !IsAudioMoth("", "AudioMoth 123456") {
			t.Error("Should identify AudioMoth with ID in artist field")
		}
		if IsAudioMoth("", "Other Artist") {
			t.Error("Should not identify non-AudioMoth artist")
		}
	})

	t.Run("should identify AudioMoth files by comment field", func(t *testing.T) {
		if !IsAudioMoth("Recorded by AudioMoth...", "") {
			t.Error("Should identify AudioMoth by comment field")
		}
		if IsAudioMoth("Regular recording comment", "") {
			t.Error("Should not identify non-AudioMoth comment")
		}
	})

	t.Run("should handle missing metadata", func(t *testing.T) {
		if IsAudioMoth("", "") {
			t.Error("Should not identify empty strings as AudioMoth")
		}
	})

	t.Run("should be case insensitive", func(t *testing.T) {
		if !IsAudioMoth("", "audiomoth") {
			t.Error("Should be case insensitive")
		}
		if !IsAudioMoth("", "AUDIOMOTH") {
			t.Error("Should be case insensitive")
		}
	})
}

func TestParseAudioMothComment(t *testing.T) {
	t.Run("should parse a valid structured AudioMoth comment", func(t *testing.T) {
		comment := "Recorded at 21:00:00 24/02/2025 (UTC+13) by AudioMoth 248AB50153AB0549 at medium gain while battery was 4.3V and temperature was 15.8C."

		result, err := ParseAudioMothComment(comment)
		if err != nil {
			t.Fatalf("Failed to parse comment: %v", err)
		}

		// Check timestamp (should be in UTC+13)
		expected := time.Date(2025, 2, 24, 21, 0, 0, 0, time.FixedZone("UTC+13", 13*3600))
		if !result.Timestamp.Equal(expected) {
			t.Errorf("Timestamp incorrect: got %v, want %v", result.Timestamp, expected)
		}

		// Convert to UTC and verify
		utc := result.Timestamp.UTC()
		expectedUTC := time.Date(2025, 2, 24, 8, 0, 0, 0, time.UTC)
		if !utc.Equal(expectedUTC) {
			t.Errorf("UTC timestamp incorrect: got %v, want %v", utc, expectedUTC)
		}

		if result.RecorderID != "248AB50153AB0549" {
			t.Errorf("RecorderID incorrect: got %s, want 248AB50153AB0549", result.RecorderID)
		}

		if result.Gain != db.GainMedium {
			t.Errorf("Gain incorrect: got %s, want %s", result.Gain, db.GainMedium)
		}

		if result.BatteryV != 4.3 {
			t.Errorf("BatteryV incorrect: got %f, want 4.3", result.BatteryV)
		}

		if result.TempC != 15.8 {
			t.Errorf("TempC incorrect: got %f, want 15.8", result.TempC)
		}
	})

	t.Run("should return error for invalid comments", func(t *testing.T) {
		invalidComments := []string{
			"Not an AudioMoth comment",
			"Recorded at invalid time format",
			"Short comment",
			"",
			"AudioMoth without proper format",
		}

		for _, comment := range invalidComments {
			_, err := ParseAudioMothComment(comment)
			if err == nil {
				t.Errorf("Expected error for invalid comment: %s", comment)
			}
		}
	})

	t.Run("should handle different timezone formats", func(t *testing.T) {
		commentUTCMinus := "Recorded at 10:30:45 15/06/2024 (UTC-5) by AudioMoth 123456789ABCDEF0 at high gain while battery was 3.9V and temperature was 22.1C."

		result, err := ParseAudioMothComment(commentUTCMinus)
		if err != nil {
			t.Fatalf("Failed to parse comment: %v", err)
		}

		// Check timestamp is in UTC-5
		expected := time.Date(2024, 6, 15, 10, 30, 45, 0, time.FixedZone("UTC-5", -5*3600))
		if !result.Timestamp.Equal(expected) {
			t.Errorf("Timestamp incorrect: got %v, want %v", result.Timestamp, expected)
		}

		if result.Gain != db.GainHigh {
			t.Errorf("Gain incorrect: got %s, want %s", result.Gain, db.GainHigh)
		}

		if result.BatteryV != 3.9 {
			t.Errorf("BatteryV incorrect: got %f, want 3.9", result.BatteryV)
		}

		if result.TempC != 22.1 {
			t.Errorf("TempC incorrect: got %f, want 22.1", result.TempC)
		}
	})

	t.Run("should parse all gain levels", func(t *testing.T) {
		testCases := []struct {
			gainStr  string
			expected db.GainLevel
		}{
			{"low", db.GainLow},
			{"low-medium", db.GainLowMedium},
			{"medium", db.GainMedium},
			{"medium-high", db.GainMediumHigh},
			{"high", db.GainHigh},
		}

		for _, tc := range testCases {
			comment := "Recorded at 21:00:00 24/02/2025 (UTC+13) by AudioMoth 248AB50153AB0549 at " + tc.gainStr + " gain while battery was 4.3V and temperature was 15.8C."
			result, err := ParseAudioMothComment(comment)
			if err != nil {
				t.Errorf("Failed to parse comment with gain %s: %v", tc.gainStr, err)
				continue
			}

			if result.Gain != tc.expected {
				t.Errorf("Gain incorrect for %s: got %s, want %s", tc.gainStr, result.Gain, tc.expected)
			}
		}
	})

	t.Run("should handle negative temperatures", func(t *testing.T) {
		comment := "Recorded at 21:00:00 24/02/2025 (UTC+13) by AudioMoth 248AB50153AB0549 at medium gain while battery was 4.3V and temperature was -5.2C."

		result, err := ParseAudioMothComment(comment)
		if err != nil {
			t.Fatalf("Failed to parse comment: %v", err)
		}

		if result.TempC != -5.2 {
			t.Errorf("TempC incorrect: got %f, want -5.2", result.TempC)
		}
	})

	t.Run("should fallback to legacy parsing", func(t *testing.T) {
		// Legacy format might not match structured regex but should be parseable
		// Test with a legacy-style comment
		comment := "Recorded at 21:00:00 24/02/2025 (UTC+13) by AudioMoth 248AB50153AB0549 at medium gain while battery was 4.3V and temperature was 15.8C"

		// Note: The legacy parser expects the exact structure, so this might fail
		// if the comment doesn't match. Adjust test as needed based on actual legacy format.
		result, err := ParseAudioMothComment(comment)

		// Either succeeds or fails gracefully
		if err == nil {
			// If it succeeds, verify basic fields
			if result.RecorderID == "" {
				t.Error("RecorderID should not be empty")
			}
		}
	})
}

func TestParseGainLevel(t *testing.T) {
	testCases := []struct {
		input    string
		expected db.GainLevel
		wantErr  bool
	}{
		{"low", db.GainLow, false},
		{"LOW", db.GainLow, false},
		{"  low  ", db.GainLow, false},
		{"low-medium", db.GainLowMedium, false},
		{"medium", db.GainMedium, false},
		{"medium-high", db.GainMediumHigh, false},
		{"high", db.GainHigh, false},
		{"invalid", "", true},
		{"", "", true},
		{"ultra", "", true},
	}

	for _, tc := range testCases {
		t.Run(tc.input, func(t *testing.T) {
			result, err := parseGainLevel(tc.input)

			if tc.wantErr {
				if err == nil {
					t.Errorf("Expected error for input %q, got nil", tc.input)
				}
			} else {
				if err != nil {
					t.Errorf("Unexpected error for input %q: %v", tc.input, err)
				}
				if result != tc.expected {
					t.Errorf("Result incorrect for %q: got %s, want %s", tc.input, result, tc.expected)
				}
			}
		})
	}
}

func TestParseAudioMothTimestamp(t *testing.T) {
	t.Run("should parse standard timestamp format", func(t *testing.T) {
		result, err := parseAudioMothTimestamp("21:00:00", "24/02/2025", "UTC+13")
		if err != nil {
			t.Fatalf("Failed to parse timestamp: %v", err)
		}

		expected := time.Date(2025, 2, 24, 21, 0, 0, 0, time.FixedZone("UTC+13", 13*3600))
		if !result.Equal(expected) {
			t.Errorf("Timestamp incorrect: got %v, want %v", result, expected)
		}
	})

	t.Run("should parse timestamp with +HH format", func(t *testing.T) {
		result, err := parseAudioMothTimestamp("10:30:45", "15/06/2024", "+13")
		if err != nil {
			t.Fatalf("Failed to parse timestamp: %v", err)
		}

		expected := time.Date(2024, 6, 15, 10, 30, 45, 0, time.FixedZone("UTC+13", 13*3600))
		if !result.Equal(expected) {
			t.Errorf("Timestamp incorrect: got %v, want %v", result, expected)
		}
	})

	t.Run("should parse negative timezone offset", func(t *testing.T) {
		result, err := parseAudioMothTimestamp("10:30:45", "15/06/2024", "UTC-5")
		if err != nil {
			t.Fatalf("Failed to parse timestamp: %v", err)
		}

		expected := time.Date(2024, 6, 15, 10, 30, 45, 0, time.FixedZone("UTC-5", -5*3600))
		if !result.Equal(expected) {
			t.Errorf("Timestamp incorrect: got %v, want %v", result, expected)
		}
	})

	t.Run("should handle invalid time format", func(t *testing.T) {
		_, err := parseAudioMothTimestamp("25:00:00", "15/06/2024", "UTC+13")
		// Note: Go's time.Date will normalize invalid times, so this might not error
		// The error would be caught if the format doesn't match
		_ = err
	})

	t.Run("should handle invalid date format", func(t *testing.T) {
		_, err := parseAudioMothTimestamp("10:30:45", "32/13/2024", "UTC+13")
		// Note: Go's time.Date will normalize invalid dates
		_ = err
	})
}

func TestStructuredVsLegacyParsing(t *testing.T) {
	t.Run("should prefer structured parsing", func(t *testing.T) {
		comment := "Recorded at 21:00:00 24/02/2025 (UTC+13) by AudioMoth 248AB50153AB0549 at medium gain while battery was 4.3V and temperature was 15.8C."

		result, err := ParseAudioMothComment(comment)
		if err != nil {
			t.Fatalf("Failed to parse comment: %v", err)
		}

		// Verify it parsed correctly
		if result.RecorderID != "248AB50153AB0549" {
			t.Errorf("RecorderID incorrect: got %s, want 248AB50153AB0549", result.RecorderID)
		}
	})

	t.Run("should handle legacy format", func(t *testing.T) {
		// Create a comment that matches legacy space-separated format
		comment := "Recorded at 21:00:00 24/02/2025 (UTC+13) by AudioMoth 248AB50153AB0549 at medium gain while battery was 4.3V and temperature was 15.8C."

		// The structured parser should handle this
		result, err := ParseAudioMothComment(comment)
		if err != nil {
			// If structured fails, legacy should catch it
			// (though for this format, structured should work)
			t.Logf("Note: Structured parsing failed, expected legacy to handle: %v", err)
		} else {
			if result.RecorderID == "" {
				t.Error("RecorderID should not be empty")
			}
		}
	})
}

func TestAudioMothCommentEdgeCases(t *testing.T) {
	t.Run("should handle extra whitespace", func(t *testing.T) {
		comment := "Recorded at  21:00:00  24/02/2025  (UTC+13)  by  AudioMoth  248AB50153AB0549  at  medium  gain  while  battery  was  4.3V  and  temperature  was  15.8C."

		// Depending on implementation, this might or might not parse
		_, err := ParseAudioMothComment(comment)
		if err != nil {
			// Expected - structured regex is strict
			t.Logf("Extra whitespace causes parsing to fail (expected): %v", err)
		}
	})

	t.Run("should handle different case in gain", func(t *testing.T) {
		comment := "Recorded at 21:00:00 24/02/2025 (UTC+13) by AudioMoth 248AB50153AB0549 at MEDIUM gain while battery was 4.3V and temperature was 15.8C."

		result, err := ParseAudioMothComment(comment)
		if err == nil {
			if result.Gain != db.GainMedium {
				t.Errorf("Gain should be normalized: got %s, want %s", result.Gain, db.GainMedium)
			}
		}
	})

	t.Run("should handle non-hex recorder ID via legacy parser", func(t *testing.T) {
		// Structured regex expects [A-F0-9]+ hex format and will not match
		// Legacy parser will catch this and parse it (more lenient)
		comment := "Recorded at 21:00:00 24/02/2025 (UTC+13) by AudioMoth GGGGGGGGGGGGGGGG at medium gain while battery was 4.3V and temperature was 15.8C."

		result, err := ParseAudioMothComment(comment)
		// Legacy parser is lenient and accepts any recorder ID
		if err != nil {
			t.Fatalf("Legacy parser should handle non-hex recorder ID: %v", err)
		}

		// Verify it parsed the recorder ID (even though it's not valid hex)
		if result.RecorderID != "GGGGGGGGGGGGGGGG" {
			t.Errorf("RecorderID incorrect: got %s, want GGGGGGGGGGGGGGGG", result.RecorderID)
		}
	})

	t.Run("should handle recorder ID of different lengths", func(t *testing.T) {
		// Short ID
		comment := "Recorded at 21:00:00 24/02/2025 (UTC+13) by AudioMoth ABCD at medium gain while battery was 4.3V and temperature was 15.8C."

		result, err := ParseAudioMothComment(comment)
		if err != nil {
			t.Fatalf("Failed to parse comment with short ID: %v", err)
		}

		if !strings.Contains(result.RecorderID, "ABCD") {
			t.Errorf("RecorderID should contain ABCD, got %s", result.RecorderID)
		}
	})
}