package timestamptz

import (
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

// TestValidateAndNormalizeTimestampTz tests the function for string validation and normalization.
func TestValidateAndNormalizeTimestampTz(t *testing.T) {
	testCases := []struct {
		name             string
		inputStr         string
		defaultTZ        string
		expectedOutput   string
		expectError      bool
		errorContainsMsg string
	}{
		{
			name:           "Case 1: TZ-aware (UTC)",
			inputStr:       "2024-10-23T15:30:00Z",
			defaultTZ:      "Asia/Tokyo",
			expectedOutput: "2024-10-23T15:30:00Z",
			expectError:    false,
		},
		{
			name:           "Case 2: TZ-aware (Offset)",
			inputStr:       "2024-10-23T15:30:00+05:30",
			defaultTZ:      "UTC",
			expectedOutput: "2024-10-23T15:30:00+05:30",
			expectError:    false,
		},
		{
			name:           "Case 3: Naive (Apply Default TZ Shanghai)",
			inputStr:       "2024-10-23 15:30:00",
			defaultTZ:      "Asia/Shanghai", // Shanghai is UTC+08:00
			expectedOutput: "2024-10-23T15:30:00+08:00",
			expectError:    false,
		},
		{
			name:           "Case 4: Naive (Apply Default TZ LA)",
			inputStr:       "2024-10-23 15:30:00.123456",
			defaultTZ:      "America/Los_Angeles", // LA is UTC-07:00 (PDT for Oct)
			expectedOutput: "2024-10-23T15:30:00.123456-07:00",
			expectError:    false,
		},
		{
			name:             "Case 5: Invalid Format",
			inputStr:         "23-10-2024 15:30",
			defaultTZ:        "UTC",
			expectedOutput:   "",
			expectError:      true,
			errorContainsMsg: "invalid timestamp string",
		},
		{
			name:             "Case 6: Invalid Default Timezone",
			inputStr:         "2024-10-23T15:30:00Z",
			defaultTZ:        "Invalid/TZ",
			expectedOutput:   "2024-10-23T15:30:00Z",
			expectError:      false,
			errorContainsMsg: "",
		},
		{
			name:             "Case 7: Invalid Default Timezone",
			inputStr:         "2024-10-23T15:30:00",
			defaultTZ:        "Invalid/TZ",
			expectedOutput:   "",
			expectError:      true,
			errorContainsMsg: "invalid default timezone string",
		},
		{
			name:             "Case 8: Offset Too High (+15:00)",
			inputStr:         "2024-10-23T15:30:00+15:00",
			defaultTZ:        "UTC",
			expectedOutput:   "",
			expectError:      true,
			errorContainsMsg: "UTC offset hour 15 is out of the valid range",
		},
		{
			name:             "Case 9: Offset Too Low (-13:00)",
			inputStr:         "2024-10-23T15:30:00-13:00",
			defaultTZ:        "UTC",
			expectedOutput:   "",
			expectError:      true,
			errorContainsMsg: "UTC offset hour -13 is out of the valid range",
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			result, err := ValidateAndNormalizeTimestampTz(tc.inputStr, tc.defaultTZ)

			if tc.expectError {
				assert.Error(t, err)
				if err != nil {
					assert.Contains(t, err.Error(), tc.errorContainsMsg)
				}
			} else {
				assert.NoError(t, err)
				// Truncate expected output to microsecond for safe comparison
				// as time.Time can contain up to nanoseconds, but we test to microsecond precision.
				tParsed, _ := time.Parse(time.RFC3339Nano, tc.expectedOutput)
				expectedFormatted := tParsed.Truncate(time.Microsecond).Format(time.RFC3339Nano)

				assert.Equal(t, expectedFormatted, result)
			}
		})
	}
}

// TestValidateAndReturnUnixMicroTz tests the function for Unix microsecond conversion.
func TestValidateAndReturnUnixMicroTz(t *testing.T) {
	// Base time for comparison: 2024-10-23T15:30:00.123456Z (UTC)
	// UnixMicro should be the same regardless of input format/timezone if moment is the same.
	expectedMicro := int64(1729697400123456)

	testCases := []struct {
		name          string
		inputStr      string
		defaultTZ     string
		expectedMicro int64
		expectError   bool
	}{
		{
			name:          "Case 1: TZ-aware (UTC)",
			inputStr:      "2024-10-23T15:30:00.123456Z",
			defaultTZ:     "Asia/Tokyo",
			expectedMicro: expectedMicro,
			expectError:   false,
		},
		{
			name:          "Case 2: TZ-aware (Offset)",
			inputStr:      "2024-10-23T23:30:00.123456+08:00", // +8h is 15:30 UTC
			defaultTZ:     "UTC",
			expectedMicro: expectedMicro,
			expectError:   false,
		},
		{
			name:          "Case 3: Naive (Apply Default TZ Shanghai)",
			inputStr:      "2024-10-23 23:30:00.123456", // 23:30 Shanghai (UTC+08:00) is 15:30 UTC
			defaultTZ:     "Asia/Shanghai",
			expectedMicro: expectedMicro,
			expectError:   false,
		},
		{
			name:          "Case 4: Naive (Apply Default TZ LA)",
			inputStr:      "2024-10-23 08:30:00.123456", // 08:30 LA (PDT, UTC-07:00) is 15:30 UTC
			defaultTZ:     "America/Los_Angeles",
			expectedMicro: expectedMicro,
			expectError:   false,
		},
		{
			name:          "Case 5: Invalid Offset",
			inputStr:      "2024-10-23T15:30:00+15:00",
			defaultTZ:     "UTC",
			expectedMicro: 0,
			expectError:   true,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			result, err := ValidateAndReturnUnixMicroTz(tc.inputStr, tc.defaultTZ)

			if tc.expectError {
				assert.Error(t, err)
				assert.Equal(t, int64(0), result)
			} else {
				assert.NoError(t, err)
				assert.Equal(t, tc.expectedMicro, result)
			}
		})
	}
}

// TestCompareUnixMicroTz tests the comparison function.
func TestCompareUnixMicroTz(t *testing.T) {
	// Base time for comparison: 2025-10-23T15:30:00Z (Using the current date for realism)

	// These strings all represent the moment 2025-10-23T15:30:00.000000Z
	sameTime1 := "2025-10-23T15:30:00Z"
	sameTime2 := "2025-10-23T23:30:00+08:00" // Shanghai: 23:30 local -> 15:30 UTC
	sameTime3 := "2025-10-23 15:30:00"       // Naive time

	// A slightly different moment (1 microsecond later)
	differentTime := "2025-10-23T15:30:00.000001Z"

	testCases := []struct {
		name        string
		ts1         string
		ts2         string
		defaultTZ   string
		expectedCmp bool
		expectError bool
	}{
		{
			name:        "Case 1: Same moment, different explicit TZ format",
			ts1:         sameTime1, // 15:30 UTC
			ts2:         sameTime2, // 15:30 UTC
			defaultTZ:   "UTC",
			expectedCmp: true,
			expectError: false,
		},
		{
			name:        "Case 2: Same moment, one naive, UTC default TZ needed",
			ts1:         sameTime1, // 15:30 UTC
			ts2:         sameTime3, // 15:30 assigned UTC -> 15:30 UTC
			defaultTZ:   "UTC",
			expectedCmp: true,
			expectError: false,
		},
		{
			// FIX FOR DST: On 2025-10-23, New York is in EDT (UTC-04:00).
			// To match 15:30 UTC, the naive time (ts2) must be 15:30 + 04:00 = 19:30 local time.
			name:        "Case 3: Same moment, DST-aware comparison for naive string",
			ts1:         "2025-10-23T19:30:00-04:00", // 19:30 EDT -> 15:30 UTC
			ts2:         "2025-10-23 19:30:00",       // 19:30 assigned New York (EDT) -> 15:30 UTC
			defaultTZ:   "America/New_York",
			expectedCmp: true,
			expectError: false,
		},
		{
			name:        "Case 4: Different moment (1 microsecond difference)",
			ts1:         sameTime1,
			ts2:         differentTime,
			defaultTZ:   "UTC",
			expectedCmp: false,
			expectError: false,
		},
		// Revised Case 5
		{
			name:        "Case 5: Different naive times under the same default TZ",
			ts1:         "2025-10-23 10:00:00", // 10:00 assigned LA -> 03:00 UTC
			ts2:         "2025-10-23 11:00:00", // 11:00 assigned LA -> 04:00 UTC
			defaultTZ:   "America/Los_Angeles",
			expectedCmp: false, // 03:00 UTC != 04:00 UTC
			expectError: false,
		},
		// New Case 5B: Identical naive strings MUST be equal.
		{
			name:        "Case 5B: Identical naive strings must be equal",
			ts1:         "2025-10-23 10:00:00", // 10:00 assigned LA -> 03:00 UTC
			ts2:         "2025-10-23 10:00:00", // 10:00 assigned LA -> 03:00 UTC
			defaultTZ:   "America/Los_Angeles",
			expectedCmp: true,
			expectError: false,
		},
		{
			name:        "Case 6: Invalid TS1 (Offset Too High)",
			ts1:         "2025-10-23T15:30:00+15:00", // Should fail offset check
			ts2:         sameTime1,
			defaultTZ:   "UTC",
			expectedCmp: false,
			expectError: true,
		},
		{
			name:        "Case 7: Invalid TS2 (Bad Format)",
			ts1:         sameTime1,
			ts2:         "not a timestamp",
			defaultTZ:   "UTC",
			expectedCmp: false,
			expectError: true,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			result, err := CompareUnixMicroTz(tc.ts1, tc.ts2, tc.defaultTZ)

			if tc.expectError {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
				assert.Equal(t, tc.expectedCmp, result)
			}
		})
	}
}

// TestValidateTimestampTz tests the simple validation function.
func TestValidateTimestampTz(t *testing.T) {
	testCases := []struct {
		name        string
		inputStr    string
		defaultTZ   string
		expectError bool
	}{
		{
			name:        "Case 1: Valid TZ-aware",
			inputStr:    "2024-10-23T15:30:00Z",
			defaultTZ:   "UTC",
			expectError: false,
		},
		{
			name:        "Case 2: Valid Naive",
			inputStr:    "2024-10-23 15:30:00",
			defaultTZ:   "Asia/Shanghai",
			expectError: false,
		},
		{
			name:        "Case 3: Invalid Format",
			inputStr:    "Invalid",
			defaultTZ:   "UTC",
			expectError: true,
		},
		{
			name:        "Case 4: Invalid Offset",
			inputStr:    "2024-10-23T15:30:00+15:00",
			defaultTZ:   "UTC",
			expectError: true,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			err := ValidateTimestampTz(tc.inputStr, tc.defaultTZ)

			if tc.expectError {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

// TestConvertUnixMicroToTimezoneString tests the conversion of Unix Microsecond
// to a TZ-aware RFC3339Nano string.
func TestConvertUnixMicroToTimezoneString2(t *testing.T) {
	// Base time for all tests: 2025-03-20T10:30:00.123456Z (UTC)
	// This date is chosen to test standard time outside of DST changes.
	baseTimeUTC := time.Date(2025, time.March, 20, 10, 30, 0, 123456000, time.UTC)
	baseUnixMicro := baseTimeUTC.UnixMicro() // 1710921000123456 (Actual value calculated by Go)

	// DST test date: 2025-10-23T15:30:00.000000Z (UTC)
	// New York is in EDT (UTC-04:00) on this date.
	dstTimeUTC := time.Date(2025, time.October, 23, 15, 30, 0, 0, time.UTC)
	dstUnixMicro := dstTimeUTC.UnixMicro() // 1729697400000000

	testCases := []struct {
		name             string
		unixMicro        int64
		targetTZ         string
		expectedOutput   string
		expectError      bool
		errorContainsMsg string
	}{
		// --- Basic Functionality & Precision ---
		{
			name:           "Case 1: Standard conversion to UTC",
			unixMicro:      baseUnixMicro,
			targetTZ:       "UTC",
			expectedOutput: "2025-03-20T10:30:00.123456Z",
			expectError:    false,
		},
		{
			name:           "Case 2: Conversion to Asia/Shanghai (+08:00)",
			unixMicro:      baseUnixMicro,
			targetTZ:       "Asia/Shanghai", // UTC+08:00
			expectedOutput: "2025-03-20T18:30:00.123456+08:00",
			expectError:    false,
		},
		{
			name:      "Case 3: Conversion to America/Los_Angeles (PDT on Mar 20)",
			unixMicro: baseUnixMicro,
			targetTZ:  "America/Los_Angeles",
			// Corrected Output: 10:30 UTC - 7 hours (PDT) = 03:30 local time
			expectedOutput: "2025-03-20T03:30:00.123456-07:00",
			expectError:    false,
		},

		// --- DST Handling (2025-10-23) ---
		{
			name:           "Case 4: DST active (America/New_York) - UTC-04:00",
			unixMicro:      dstUnixMicro,
			targetTZ:       "America/New_York",          // EDT = UTC-04:00 on this date
			expectedOutput: "2025-10-23T11:30:00-04:00", // 15:30 UTC -> 11:30 local time
			expectError:    false,
		},

		// --- Error Handling ---
		{
			name:             "Case 5: Invalid Timezone String",
			unixMicro:        baseUnixMicro,
			targetTZ:         "Invalid/TZ_Name",
			expectedOutput:   "",
			expectError:      true,
			errorContainsMsg: "invalid target timezone string",
		},
		{
			name:           "Case 6: UnixMicro 0 (Epoch)",
			unixMicro:      0,
			targetTZ:       "UTC",
			expectedOutput: "1970-01-01T00:00:00Z",
			expectError:    false,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			result, err := ConvertUnixMicroToTimezoneString(tc.unixMicro, tc.targetTZ)

			if tc.expectError {
				assert.Error(t, err)
				if err != nil {
					assert.Contains(t, err.Error(), tc.errorContainsMsg)
				}
			} else {
				assert.NoError(t, err)

				// Assert the resulting string can be parsed back and retains microsecond precision
				tParsed, parseErr := time.Parse(time.RFC3339Nano, result)
				assert.NoError(t, parseErr)

				// Standardize the expected output to ensure nanosecond precision padding is correct
				expectedFormatted := tc.expectedOutput

				assert.Equal(t, expectedFormatted, result)

				// Ensure the microsecond value is maintained (conversion consistency check)
				assert.Equal(t, tc.unixMicro, tParsed.UnixMicro())
			}
		})
	}
}

// TestFormatTimeMicroWithoutTrailingZerosREVISED tests the custom formatting function
// to ensure it correctly truncates to microsecond and removes trailing zeros.
func TestFormatTimeMicroWithoutTrailingZeros(t *testing.T) {
	testCases := []struct {
		name           string
		nanoSeconds    int    // Nanoseconds part
		tzOffsetHours  int    // TZ Offset in hours
		expectedOutput string // Expected final string after cleaning
	}{
		{
			name:           "Case 1: Full Microsecond Precision (No trailing zeros to remove)",
			nanoSeconds:    123456000, // .123456
			tzOffsetHours:  8,
			expectedOutput: "2025-10-23T00:00:00.123456+08:00",
		},
		{
			name:           "Case 2: Millisecond Precision (Remove 6 trailing zeros)",
			nanoSeconds:    123000000, // .123
			tzOffsetHours:  8,
			expectedOutput: "2025-10-23T00:00:00.123+08:00",
		},
		{
			name:           "Case 3: Second Precision (Remove all 9 fractional zeros)",
			nanoSeconds:    0, // .000000
			tzOffsetHours:  8,
			expectedOutput: "2025-10-23T00:00:00+08:00", // Note: The dot is removed.
		},
		{
			name:           "Case 4: Single-digit precision (Remove 8 trailing zeros)",
			nanoSeconds:    100000000, // .1
			tzOffsetHours:  0,
			expectedOutput: "2025-10-23T00:00:00.1Z",
		},
		{
			name:           "Case 5: Precision beyond microsecond (Truncate first, then remove zeros)",
			nanoSeconds:    123456789, // .123456789 -> truncates to .123456
			tzOffsetHours:  -7,
			expectedOutput: "2025-10-23T00:00:00.123456-07:00",
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			// Create a time.Time object for a fixed date/time
			tz := time.FixedZone("TEST_TZ", tc.tzOffsetHours*3600)
			tInput := time.Date(2025, time.October, 23, 0, 0, 0, tc.nanoSeconds, tz)

			result := FormatTimeMicroWithoutTrailingZeros(tInput)

			assert.Equal(t, tc.expectedOutput, result)

			// Verification: Ensure round-trip parsing still works and yields the correct microsecond value
			tParsed, parseErr := time.Parse(time.RFC3339Nano, result)
			assert.NoError(t, parseErr)
			// Microsecond precision is preserved
			assert.Equal(t, tInput.Truncate(time.Microsecond).UnixMicro(), tParsed.UnixMicro())
		})
	}
}

// funcutil/time_test.go (Updated TestConvertUnixMicroToTimezoneString)

// TestConvertUnixMicroToTimezoneString tests the conversion of Unix Microsecond
// to a TZ-aware RFC3339Nano string, now including the trailing zero removal logic.
func TestConvertUnixMicroToTimezoneString(t *testing.T) {
	// Base time 1: 2025-03-20T10:30:00.123456Z (UTC). Nanoseconds: 123456000
	baseTimeUTC := time.Date(2025, time.March, 20, 10, 30, 0, 123456000, time.UTC)
	baseUnixMicro := baseTimeUTC.UnixMicro() // 1710921000123456

	// Base time 2: 2025-10-23T15:30:00.000000Z (UTC). Nanoseconds: 0
	dstTimeUTC := time.Date(2025, time.October, 23, 15, 30, 0, 0, time.UTC)
	dstUnixMicro := dstTimeUTC.UnixMicro() // 1729697400000000

	testCases := []struct {
		name             string
		unixMicro        int64
		targetTZ         string
		expectedOutput   string // NOW CLEANED OF TRAILING ZEROS
		expectError      bool
		errorContainsMsg string
	}{
		// --- Basic Functionality & Precision (Using baseUnixMicro - ends in .123456) ---
		{
			name:           "Case 1: Standard conversion to UTC (Microsecond precision)",
			unixMicro:      baseUnixMicro,
			targetTZ:       "UTC",
			expectedOutput: "2025-03-20T10:30:00.123456Z", // Removed "000"
			expectError:    false,
		},
		{
			name:           "Case 2: Conversion to Asia/Shanghai (+08:00)",
			unixMicro:      baseUnixMicro,
			targetTZ:       "Asia/Shanghai",                    // UTC+08:00
			expectedOutput: "2025-03-20T18:30:00.123456+08:00", // Removed "000"
			expectError:    false,
		},
		{
			// Fix from previous round: 10:30 UTC - 7 hours (PDT) = 03:30 local time.
			name:           "Case 3: Conversion to America/Los_Angeles (PDT on Mar 20)",
			unixMicro:      baseUnixMicro,
			targetTZ:       "America/Los_Angeles",              // PDT = UTC-07:00
			expectedOutput: "2025-03-20T03:30:00.123456-07:00", // Removed "000"
			expectError:    false,
		},

		// --- DST Handling (Using dstUnixMicro - ends in .000000) ---
		{
			name:           "Case 4: DST active (America/New_York) - UTC-04:00 (Second precision)",
			unixMicro:      dstUnixMicro,
			targetTZ:       "America/New_York",          // EDT = UTC-04:00 on this date
			expectedOutput: "2025-10-23T11:30:00-04:00", // Removed all fractional zeros and the dot
			expectError:    false,
		},

		// --- Custom Test Case: Only Millisecond precision (must remove 3 trailing zeros) ---
		{
			name: "Case 5: Millisecond precision input",
			// Use the calculated value for 2025-03-20T10:30:00.123Z.
			// If you were previously using baseUnixMicro, calculate the new value:
			// (baseUnixMicro / 1000000) * 1000000 + 123000
			unixMicro:      (baseUnixMicro / 1000000 * 1000000) + 123000,
			targetTZ:       "UTC",
			expectedOutput: "2025-03-20T10:30:00.123Z",
			expectError:    false,
		},

		// --- Error Handling ---
		{
			name:             "Case 6: Invalid Timezone String",
			unixMicro:        baseUnixMicro,
			targetTZ:         "Invalid/TZ_Name",
			expectedOutput:   "",
			expectError:      true,
			errorContainsMsg: "invalid target timezone string",
		},
		{
			name:           "Case 7: UnixMicro 0 (Epoch) - Second precision",
			unixMicro:      0,
			targetTZ:       "UTC",
			expectedOutput: "1970-01-01T00:00:00Z", // Removed all fractional zeros and the dot
			expectError:    false,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			result, err := ConvertUnixMicroToTimezoneString(tc.unixMicro, tc.targetTZ)

			if tc.expectError {
				assert.Error(t, err)
				if err != nil {
					assert.Contains(t, err.Error(), tc.errorContainsMsg)
				}
			} else {
				assert.NoError(t, err)
				assert.Equal(t, tc.expectedOutput, result)

				// Verify round-trip consistency: the resulting string must parse back
				// to the original microsecond value (since no significant digits were lost).
				tParsed, parseErr := time.Parse(time.RFC3339Nano, result)
				assert.NoError(t, parseErr)
				assert.Equal(t, tc.unixMicro, tParsed.UnixMicro())
			}
		})
	}
}

// TestIsTimezoneValid tests the IsTimezoneValid function.
func TestIsTimezoneValid(t *testing.T) {
	// Common valid IANA timezone names
	validTimezones := []string{
		"UTC",
		"Local",
		"Asia/Shanghai",
		"America/New_York",
		"Europe/London",
		"Australia/Sydney",
		"Etc/GMT+10",
	}

	// Common invalid or malformed timezone names
	invalidTimezones := []string{
		"",
		"GMT+8",
		"CST",
		"PST",
		"Invalid/Zone",
		"Asia/Beijingg",
		"99:00",
		time.FixedZone("MyZone", 3600).String(), // Custom location names (like "MyZone") are not recognized by time.LoadLocation.
	}

	// Test valid timezones
	for _, tz := range validTimezones {
		t.Run("Valid_"+tz, func(t *testing.T) {
			if !IsTimezoneValid(tz) {
				t.Errorf("IsTimezoneValid(\"%s\") expected true, but got false", tz)
			}
		})
	}

	// Test invalid timezones
	for _, tz := range invalidTimezones {
		t.Run("Invalid_"+tz, func(t *testing.T) {
			if IsTimezoneValid(tz) {
				t.Errorf("IsTimezoneValid(\"%s\") expected false, but got true", tz)
			}
		})
	}
}
