package utils

import (
	"testing"
	"time"
)

// Test location: Auckland, New Zealand (approx coordinates)
var testLocationAuckland = struct {
	lat float64
	lon float64
}{
	lat: -36.8485,
	lon: 174.7633,
}

// Test location: London, UK
var testLocationLondon = struct {
	lat float64
	lon float64
}{
	lat: 51.5074,
	lon: -0.1278,
}

func TestCalculateAstronomicalData(t *testing.T) {
	t.Run("should return valid types for all fields", func(t *testing.T) {
		// Winter midnight in Auckland (should be solar night)
		winterMidnight := parseTime(t, "2024-06-15T12:00:00Z") // UTC midnight = noon in Auckland (winter)
		duration := 60.0                                       // 1 minute

		result := CalculateAstronomicalData(winterMidnight, duration, testLocationAuckland.lat, testLocationAuckland.lon)

		// Check types exist
		if result.MoonPhase < 0 || result.MoonPhase > 1 {
			t.Errorf("MoonPhase out of range: got %f, want 0-1", result.MoonPhase)
		}
	})

	t.Run("should return false for solar night during daytime hours", func(t *testing.T) {
		// Summer midday in Auckland (should NOT be solar night)
		summerMidday := parseTime(t, "2024-12-15T00:00:00Z") // UTC midnight = noon in Auckland (summer)
		duration := 60.0                                      // 1 minute

		result := CalculateAstronomicalData(summerMidday, duration, testLocationAuckland.lat, testLocationAuckland.lon)

		// During summer midday, should NOT be solar night
		if result.SolarNight {
			t.Error("Expected SolarNight to be false during daytime")
		}
		if result.CivilNight {
			t.Error("Expected CivilNight to be false during daytime")
		}
	})

	t.Run("should handle different durations correctly", func(t *testing.T) {
		timestamp := parseTime(t, "2024-06-15T10:00:00Z")
		shortDuration := 30.0   // 30 seconds
		longDuration := 3600.0  // 1 hour

		shortResult := CalculateAstronomicalData(timestamp, shortDuration, testLocationAuckland.lat, testLocationAuckland.lon)
		longResult := CalculateAstronomicalData(timestamp, longDuration, testLocationAuckland.lat, testLocationAuckland.lon)

		// Both should have valid results
		if shortResult.MoonPhase < 0 || shortResult.MoonPhase > 1 {
			t.Errorf("Short duration moon phase out of range: %f", shortResult.MoonPhase)
		}
		if longResult.MoonPhase < 0 || longResult.MoonPhase > 1 {
			t.Errorf("Long duration moon phase out of range: %f", longResult.MoonPhase)
		}
	})

	t.Run("should calculate midpoint time correctly", func(t *testing.T) {
		// Test that the calculation uses the midpoint, not the start time
		startTime := parseTime(t, "2024-06-15T10:00:00Z")
		duration := 7200.0 // 2 hours (midpoint would be 1 hour later)

		result := CalculateAstronomicalData(startTime, duration, testLocationAuckland.lat, testLocationAuckland.lon)

		// Should calculate based on 11:00 UTC, not 10:00 UTC
		// Just verify we get valid boolean results
		_ = result.SolarNight
		_ = result.CivilNight
	})

	t.Run("should handle different geographical locations", func(t *testing.T) {
		timestamp := parseTime(t, "2024-06-15T12:00:00Z") // UTC noon
		duration := 60.0

		aucklandResult := CalculateAstronomicalData(timestamp, duration, testLocationAuckland.lat, testLocationAuckland.lon)
		londonResult := CalculateAstronomicalData(timestamp, duration, testLocationLondon.lat, testLocationLondon.lon)

		// Both should have valid boolean results (don't compare values, just that they're boolean)
		_ = aucklandResult.SolarNight
		_ = londonResult.SolarNight

		// Results might differ due to different timezones and seasons
		// Auckland: UTC noon = midnight local (winter) = likely night
		// London: UTC noon = 1pm local (summer) = likely day
	})

	t.Run("should return valid moon phase values", func(t *testing.T) {
		timestamp := parseTime(t, "2024-06-15T12:00:00Z")
		duration := 60.0

		result := CalculateAstronomicalData(timestamp, duration, testLocationAuckland.lat, testLocationAuckland.lon)

		if result.MoonPhase < 0 || result.MoonPhase > 1 {
			t.Errorf("MoonPhase out of range: got %f, want 0-1", result.MoonPhase)
		}
	})

	t.Run("should handle edge cases with very short durations", func(t *testing.T) {
		timestamp := parseTime(t, "2024-06-15T12:00:00Z")
		duration := 0.1 // 0.1 seconds

		result := CalculateAstronomicalData(timestamp, duration, testLocationAuckland.lat, testLocationAuckland.lon)

		if result.MoonPhase < 0 || result.MoonPhase > 1 {
			t.Errorf("MoonPhase out of range: got %f, want 0-1", result.MoonPhase)
		}
	})

	t.Run("should handle edge cases with very long durations", func(t *testing.T) {
		timestamp := parseTime(t, "2024-06-15T12:00:00Z")
		duration := 86400.0 // 24 hours

		result := CalculateAstronomicalData(timestamp, duration, testLocationAuckland.lat, testLocationAuckland.lon)

		if result.MoonPhase < 0 || result.MoonPhase > 1 {
			t.Errorf("MoonPhase out of range: got %f, want 0-1", result.MoonPhase)
		}
	})
}

func TestBooleanLogicValidation(t *testing.T) {
	t.Run("should never return invalid values for valid inputs", func(t *testing.T) {
		testCases := []string{
			"2024-06-15T06:00:00Z", // Dawn/dusk time
			"2024-06-15T12:00:00Z", // Midday/midnight
			"2024-06-15T18:00:00Z", // Evening/morning
			"2024-12-15T06:00:00Z", // Summer dawn/dusk
			"2024-12-15T12:00:00Z", // Summer midday/midnight
			"2024-12-15T18:00:00Z", // Summer evening/morning
		}

		for _, timestamp := range testCases {
			t.Run(timestamp, func(t *testing.T) {
				ts := parseTime(t, timestamp)
				result := CalculateAstronomicalData(ts, 60, testLocationAuckland.lat, testLocationAuckland.lon)

				// These should be proper boolean types
				_ = result.SolarNight
				_ = result.CivilNight

				// MoonPhase should be in valid range
				if result.MoonPhase < 0 || result.MoonPhase > 1 {
					t.Errorf("MoonPhase out of range: got %f, want 0-1", result.MoonPhase)
				}
			})
		}
	})

	t.Run("should return false for daytime recordings", func(t *testing.T) {
		// Test a known daytime period in Auckland (summer midday UTC)
		summerMidday := parseTime(t, "2024-12-15T00:30:00Z") // Should be daytime in Auckland
		duration := 60.0

		result := CalculateAstronomicalData(summerMidday, duration, testLocationAuckland.lat, testLocationAuckland.lon)

		// The key test: false values should remain false
		if result.SolarNight && result.CivilNight {
			// This would be unexpected during midday
			t.Logf("Note: Both SolarNight and CivilNight are true (may be valid depending on season)")
		}
	})

	t.Run("should return true for nighttime recordings", func(t *testing.T) {
		// Test a known nighttime period in Auckland (winter midnight UTC)
		winterMidnight := parseTime(t, "2024-06-15T12:30:00Z") // Should be nighttime in Auckland
		duration := 60.0

		result := CalculateAstronomicalData(winterMidnight, duration, testLocationAuckland.lat, testLocationAuckland.lon)

		// The key test: true values should remain true
		_ = result.SolarNight
		_ = result.CivilNight
	})
}

func TestCalculateMidpointTime(t *testing.T) {
	t.Run("should calculate midpoint correctly", func(t *testing.T) {
		startTime := parseTime(t, "2024-06-15T10:00:00Z")
		duration := 3600.0 // 1 hour

		midpoint := CalculateMidpointTime(startTime, duration)
		expected := parseTime(t, "2024-06-15T10:30:00Z")

		if !midpoint.Equal(expected) {
			t.Errorf("Midpoint incorrect: got %v, want %v", midpoint, expected)
		}
	})

	t.Run("should handle short durations", func(t *testing.T) {
		startTime := parseTime(t, "2024-06-15T10:00:00Z")
		duration := 10.0 // 10 seconds

		midpoint := CalculateMidpointTime(startTime, duration)
		expected := parseTime(t, "2024-06-15T10:00:05Z")

		if !midpoint.Equal(expected) {
			t.Errorf("Midpoint incorrect: got %v, want %v", midpoint, expected)
		}
	})
}

// Helper function to parse time strings
func parseTime(t *testing.T, s string) time.Time {
	t.Helper()
	parsed, err := time.Parse(time.RFC3339, s)
	if err != nil {
		t.Fatalf("Failed to parse time %s: %v", s, err)
	}
	return parsed
}