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

func TestCalculateAstronomicalData(t *testing.T) {
	tests := []struct {
		name         string
		timestamp    string
		duration     float64
		lat, lon     float64
		wantNoSNight bool // if true, assert SolarNight=false
		wantNoCNight bool // if true, assert CivilNight=false
	}{
		{name: "valid moon phase range", timestamp: "2024-06-15T12:00:00Z", duration: 60.0, lat: testLocationAuckland.lat, lon: testLocationAuckland.lon},
		{name: "no solar night during daytime", timestamp: "2024-12-15T00:00:00Z", duration: 60.0, lat: testLocationAuckland.lat, lon: testLocationAuckland.lon, wantNoSNight: true, wantNoCNight: true},
		{name: "short duration", timestamp: "2024-06-15T10:00:00Z", duration: 30.0, lat: testLocationAuckland.lat, lon: testLocationAuckland.lon},
		{name: "long duration", timestamp: "2024-06-15T10:00:00Z", duration: 3600.0, lat: testLocationAuckland.lat, lon: testLocationAuckland.lon},
		{name: "midpoint calculation", timestamp: "2024-06-15T10:00:00Z", duration: 7200.0, lat: testLocationAuckland.lat, lon: testLocationAuckland.lon},
		{name: "different location", timestamp: "2024-06-15T12:00:00Z", duration: 60.0, lat: testLocationAuckland.lat, lon: testLocationAuckland.lon},
		{name: "very short duration", timestamp: "2024-06-15T12:00:00Z", duration: 0.1, lat: testLocationAuckland.lat, lon: testLocationAuckland.lon},
		{name: "very long duration", timestamp: "2024-06-15T12:00:00Z", duration: 86400.0, lat: testLocationAuckland.lat, lon: testLocationAuckland.lon},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ts := parseTime(t, tt.timestamp)
			result := CalculateAstronomicalData(ts, tt.duration, tt.lat, tt.lon)

			if result.MoonPhase < 0 || result.MoonPhase > 1 {
				t.Errorf("MoonPhase out of range: got %f, want 0-1", result.MoonPhase)
			}
			if tt.wantNoSNight && result.SolarNight {
				t.Error("Expected SolarNight to be false")
			}
			if tt.wantNoCNight && result.CivilNight {
				t.Error("Expected CivilNight to be false")
			}
		})
	}
}

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
}