// repository_test.go validates Postgres row mapping and attachment helpers.
// Layer: adapters/outbound/postgres mapper regression tests.
package postgres

import (
	"database/sql"
	"testing"
	"time"
)

func TestMapObservationParentRowNullables(t *testing.T) {
	observedAt := time.Date(2026, 3, 19, 23, 45, 0, 0, time.FixedZone("CST", -6*3600))
	isDay := true
	temp := 18.25

	obs := mapObservationParentRow(observationParentRow{
		StationID:       sql.NullString{String: "KSTL", Valid: true},
		StationName:     sql.NullString{String: "St. Louis", Valid: true},
		ObservedAt:      observedAt,
		ConditionCode:   2,
		IsDay:           sql.NullBool{Bool: isDay, Valid: true},
		TemperatureC:    sql.NullFloat64{Float64: temp, Valid: true},
		TextDescription: sql.NullString{String: "Partly Cloudy", Valid: true},
	})

	if obs.StationID != "KSTL" {
		t.Fatalf("expected station id KSTL, got %q", obs.StationID)
	}
	if obs.IsDay == nil || !*obs.IsDay {
		t.Fatalf("expected isDay pointer true, got %v", obs.IsDay)
	}
	if obs.TemperatureC == nil || *obs.TemperatureC != temp {
		t.Fatalf("expected temperature %v, got %v", temp, obs.TemperatureC)
	}
	if obs.DewpointC != nil {
		t.Fatalf("expected nil dewpoint, got %v", *obs.DewpointC)
	}
	if got := obs.Timestamp.Location().String(); got != "UTC" {
		t.Fatalf("expected UTC timestamp, got %s", got)
	}
}

func TestMapObservationPresentWeatherRow(t *testing.T) {
	row := observationPresentWeatherRow{
		WeatherIndex: 1,
		RawText:      sql.NullString{String: `{"code":61,"text":"rain"}`, Valid: true},
	}

	pw, err := mapObservationPresentWeatherRow(row)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if pw.Raw == nil {
		t.Fatalf("expected raw map to be populated")
	}
	if got, ok := pw.Raw["text"].(string); !ok || got != "rain" {
		t.Fatalf("expected raw text rain, got %#v", pw.Raw["text"])
	}
}

func TestMapForecastPeriodRowNullables(t *testing.T) {
	start := time.Date(2026, 3, 20, 0, 0, 0, 0, time.UTC)
	end := start.Add(1 * time.Hour)
	period := mapForecastPeriodRow(forecastPeriodRow{
		StartTime:       start,
		EndTime:         end,
		ConditionCode:   80,
		Name:            sql.NullString{String: "Midnight", Valid: true},
		TemperatureC:    sql.NullFloat64{Float64: 12.5, Valid: true},
		TemperatureCMin: sql.NullFloat64{Valid: false},
	})

	if period.Name != "Midnight" {
		t.Fatalf("expected period name Midnight, got %q", period.Name)
	}
	if period.TemperatureC == nil || *period.TemperatureC != 12.5 {
		t.Fatalf("expected temperature pointer 12.5, got %v", period.TemperatureC)
	}
	if period.TemperatureCMin != nil {
		t.Fatalf("expected nil TemperatureCMin, got %v", *period.TemperatureCMin)
	}
	if !period.StartTime.Equal(start) || !period.EndTime.Equal(end) {
		t.Fatalf("unexpected time range: %s - %s", period.StartTime, period.EndTime)
	}
}

func TestAttachAlertReferencesPreservesOrder(t *testing.T) {
	sent1 := time.Date(2026, 3, 20, 1, 0, 0, 0, time.UTC)
	sent2 := sent1.Add(10 * time.Minute)

	alerts := []indexedAlert{
		{Index: 4, Alert: mapAlertRow(alertRow{AlertIndex: 4, AlertID: "a-4"}).Alert},
		{Index: 9, Alert: mapAlertRow(alertRow{AlertIndex: 9, AlertID: "a-9"}).Alert},
	}
	references := []indexedAlertReference{
		mapAlertReferenceRow(alertReferenceRow{AlertIndex: 4, Identifier: sql.NullString{String: "r1", Valid: true}, Sent: sql.NullTime{Time: sent1, Valid: true}}),
		mapAlertReferenceRow(alertReferenceRow{AlertIndex: 4, Identifier: sql.NullString{String: "r2", Valid: true}, Sent: sql.NullTime{Time: sent2, Valid: true}}),
		mapAlertReferenceRow(alertReferenceRow{AlertIndex: 9, Identifier: sql.NullString{String: "r9", Valid: true}}),
	}

	out := attachAlertReferences(alerts, references)
	if len(out) != 2 {
		t.Fatalf("expected 2 alerts, got %d", len(out))
	}
	if len(out[0].References) != 2 {
		t.Fatalf("expected first alert to have 2 references, got %d", len(out[0].References))
	}
	if out[0].References[0].Identifier != "r1" || out[0].References[1].Identifier != "r2" {
		t.Fatalf("unexpected first alert reference order: %+v", out[0].References)
	}
	if len(out[1].References) != 1 || out[1].References[0].Identifier != "r9" {
		t.Fatalf("unexpected second alert references: %+v", out[1].References)
	}
}

func TestMapCurrentConditionsRowNoSamplesReturnsNil(t *testing.T) {
	got := mapCurrentConditionsRow(currentConditionsRow{
		SampleCount: 0,
	})
	if got != nil {
		t.Fatalf("expected nil for empty sample window, got %+v", got)
	}
}

func TestMapCurrentConditionsRowMapsFields(t *testing.T) {
	isDay := true
	got := mapCurrentConditionsRow(currentConditionsRow{
		SampleCount:             12,
		TemperatureC:            sql.NullFloat64{Float64: 15.5, Valid: true},
		ApparentTemperatureC:    sql.NullFloat64{Float64: 14.2, Valid: true},
		DewpointC:               sql.NullFloat64{Float64: 10.1, Valid: true},
		RelativeHumidityPercent: sql.NullFloat64{Float64: 72, Valid: true},
		WindSpeedKmh:            sql.NullFloat64{Float64: 24.8, Valid: true},
		WindDirectionDegrees:    sql.NullFloat64{Float64: 182.5, Valid: true},
		ConditionCode:           sql.NullInt64{Int64: 65, Valid: true},
		IsDay:                   sql.NullBool{Bool: isDay, Valid: true},
	})
	if got == nil {
		t.Fatalf("expected mapped current conditions")
	}
	if got.TemperatureC == nil || *got.TemperatureC != 15.5 {
		t.Fatalf("expected temperature pointer 15.5, got %v", got.TemperatureC)
	}
	if got.ApparentTemperatureC == nil || *got.ApparentTemperatureC != 14.2 {
		t.Fatalf("expected apparent temp pointer 14.2, got %v", got.ApparentTemperatureC)
	}
	if got.DewpointC == nil || *got.DewpointC != 10.1 {
		t.Fatalf("expected dewpoint pointer 10.1, got %v", got.DewpointC)
	}
	if got.RelativeHumidityPercent == nil || *got.RelativeHumidityPercent != 72 {
		t.Fatalf("expected rh pointer 72, got %v", got.RelativeHumidityPercent)
	}
	if got.WindSpeedKmh == nil || *got.WindSpeedKmh != 24.8 {
		t.Fatalf("expected wind speed pointer 24.8, got %v", got.WindSpeedKmh)
	}
	if got.WindDirectionDegrees == nil || *got.WindDirectionDegrees != 182.5 {
		t.Fatalf("expected wind direction pointer 182.5, got %v", got.WindDirectionDegrees)
	}
	if got.ConditionCode != 65 {
		t.Fatalf("expected condition code 65, got %d", got.ConditionCode)
	}
	if got.IsDay == nil || !*got.IsDay {
		t.Fatalf("expected isDay pointer true, got %v", got.IsDay)
	}
}
