package udp

import (
	"testing"
	"time"
)

const ms = 1000

func sec(h, m, s int) uint32 { return uint32((h*3600 + m*60 + s) * ms) }

// WSJT-X stamps a decode with a time of DAY and no date, so the date has to come
// from our own clock — and around midnight the two disagree. A decode stamped
// 23:59:58 that reaches us at 00:00:01 would be dated the NEW day, putting it
// almost 24 hours in the future: it would sort to the top of the decodes panel
// and stay there for the rest of the session, and its period would never line up
// with the ones around it.
//
// The clock is passed IN. The previous version of this test read the real one
// and so failed every afternoon — a 23:59:58 stamp seen at 17:00 is genuinely
// hours in the future, and no rule can make it otherwise. A suite that is red
// for half the day is a suite nobody reads.
func TestDecodeTimeCrossesMidnight(t *testing.T) {
	// Just after midnight, a stamp from the last seconds of yesterday.
	now := time.Date(2026, 3, 14, 0, 0, 1, 0, time.UTC)
	got := decodeTimeAt(sec(23, 59, 58), now)
	if want := time.Date(2026, 3, 13, 23, 59, 58, 0, time.UTC); !got.Equal(want) {
		t.Errorf("23:59:58 seen at 00:00:01 → %s, want %s (yesterday)", got, want)
	}
	if got.After(now) {
		t.Errorf("%s is in the future — it would sort to the top of the list for the session", got)
	}
}

// The mirror case: just before midnight, a stamp from the first seconds of
// tomorrow. Our clock is still on the old day.
func TestDecodeTimeCrossesMidnightBackwards(t *testing.T) {
	now := time.Date(2026, 3, 13, 23, 59, 58, 0, time.UTC)
	got := decodeTimeAt(sec(0, 0, 1), now)
	if want := time.Date(2026, 3, 14, 0, 0, 1, 0, time.UTC); !got.Equal(want) {
		t.Errorf("00:00:01 seen at 23:59:58 → %s, want %s (tomorrow)", got, want)
	}
}

// The ordinary case, which is every decode that is not within a few seconds of
// midnight: the stamp belongs to today and is left alone.
func TestDecodeTimeOrdinary(t *testing.T) {
	now := time.Date(2026, 3, 13, 14, 30, 0, 0, time.UTC)
	got := decodeTimeAt(sec(14, 29, 45), now)
	if want := time.Date(2026, 3, 13, 14, 29, 45, 0, time.UTC); !got.Equal(want) {
		t.Errorf("a stamp close to now → %s, want %s", got, want)
	}
}

// The whole point of the timestamp is grouping, so two decodes from the same
// fifteen-second slot must floor to the same period however far apart in the
// slot they were heard.
func TestDecodesInOneSlotShareAPeriod(t *testing.T) {
	now := time.Date(2026, 3, 13, 12, 30, 7, 0, time.UTC)
	at := func(h, m, s int) time.Time { return decodeTimeAt(sec(h, m, s), now) }
	floor := func(x time.Time) int64 { return x.Unix() / 15 * 15 }

	a, b := at(12, 30, 0), at(12, 30, 14)
	if floor(a) != floor(b) {
		t.Errorf("%s and %s fell in different periods", a, b)
	}
	if floor(at(12, 30, 14)) == floor(at(12, 30, 15)) {
		t.Error("12:30:14 and 12:30:15 are different slots and must not share a period")
	}
}
