package udp

import (
	"net"
	"testing"
	"time"
)

// feed runs one datagram through a remote-call listener and returns the event
// it produced, or nil.
func feed(t *testing.T, pkt []byte) *Event {
	t.Helper()
	out := make(chan Event, 4)
	s := &Server{
		cfg: Config{ID: 1, Name: "DX HUNTER", ServiceType: ServiceRemoteCall, Port: 2241},
		out: out,
	}
	s.handle(pkt, &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 2241})
	select {
	case ev := <-out:
		return &ev
	case <-time.After(200 * time.Millisecond):
		return nil
	}
}

// A RadioInfo datagram must never be read as a remote-call request.
//
// This is the loop from a reported session. An inbound remote-call row and an
// outbound RadioInfo row shared port 2241, so every datagram OpsLog sent came
// straight back on the loopback. The tag-stripping heuristic read its last
// token — <ActiveRadioNr>1</ActiveRadioNr> — as the callsign "1", and <Freq> as
// a tune request for the frequency the rig was already on.
//
// It stayed harmless only while <Freq> was misread as MHz: the tune failed "out
// of the CAT range" and the loop died there. Reading the unit correctly closed
// it, and with JTDX "Fake It" — which shifts the dial for every over — each
// transmission set off a burst of sets echoing between OpsLog and itself until
// the rig stopped answering IF; and the shared CAT link dropped.
func TestRadioInfoIsNotARemoteCall(t *testing.T) {
	pkt := BuildN1MMRadioInfo("F5PHW", 24_915_000, 24_915_000, "FT8", "F5PHW")

	// The trap this closes: the payload really does parse as a plausible dial
	// frequency, so nothing downstream would have questioned it.
	m := remoteFreqRe.FindStringSubmatch(string(pkt))
	if m == nil {
		t.Fatal("the RadioInfo no longer carries a <Freq> — this test is checking nothing")
	}
	if hz := remoteTuneHz(m[1]); hz != 24_915_000 {
		t.Fatalf("remoteTuneHz(%q) = %d — the dial frequency back is what made the loop live", m[1], hz)
	}

	if ev := feed(t, pkt); ev != nil {
		t.Errorf("a RadioInfo produced a remote-call event %+v — the rig would be re-tuned to where it already is", *ev)
	}
}

// A callsign has a letter in it. Refusing by shape as well as by name means the
// next program to broadcast its state on this port cannot drive the rig either.
func TestRemoteCallNeedsALetter(t *testing.T) {
	for _, body := range []string{"<CALLSIGN>1</CALLSIGN>", "<CALLSIGN>0</CALLSIGN>", "12345"} {
		if ev := feed(t, []byte(body)); ev != nil {
			t.Errorf("%q was accepted as a callsign: %+v", body, *ev)
		}
	}
	// A genuine spot click still gets through, tune request and all.
	ev := feed(t, []byte("<CALLSIGN>OJ0YL<FREQ>10.112<MODE>CW"))
	if ev == nil {
		t.Fatal("a genuine spot click produced no event")
	}
	if ev.DXCall != "OJ0YL" || ev.TuneFreqHz != 10_112_000 || ev.TuneMode != "CW" {
		t.Errorf("spot click decoded as %+v, want OJ0YL / 10112000 Hz / CW", *ev)
	}
}
