package cluster

import "testing"

func TestParseSpot(t *testing.T) {
	cases := []struct {
		line     string
		wantCall string
		wantKHz  float64
		wantBand string
		wantLoc  string
	}{
		{
			`DX de DK0SWL:    14195.5  W1AW         CQ Field Day                 1745Z`,
			"W1AW", 14195.5, "20m", "",
		},
		{
			// The cluster prints its prompt with no trailing newline, so the
			// first spot arrives glued to it. Seen live on 2026-08-02.
			`login: DX de YO2CK-#:   21074.0  PY4PTO       FT8  -16 dB                    1733Z`,
			"PY4PTO", 21074.0, "15m", "",
		},
		{
			`DX de F4XYZ-#:    7074.0  3DA0RU       FT8 -10                       0823Z JN18`,
			"3DA0RU", 7074.0, "40m", "JN18",
		},
		{
			`DX de N1MM:       14010.0 K1JT         599 NJ                        1234Z FN20`,
			"K1JT", 14010.0, "20m", "FN20",
		},
		{
			`DX de YO3JW       3573.0  EA1ABC       CQ                            2010Z IN73`,
			"EA1ABC", 3573.0, "80m", "IN73",
		},
	}
	for _, c := range cases {
		s, ok := parseSpot(c.line)
		if !ok {
			t.Errorf("%q: parse failed", c.line)
			continue
		}
		if s.DXCall != c.wantCall {
			t.Errorf("%q: call=%q want %q", c.line, s.DXCall, c.wantCall)
		}
		if s.FreqKHz != c.wantKHz {
			t.Errorf("%q: kHz=%v want %v", c.line, s.FreqKHz, c.wantKHz)
		}
		if s.Band != c.wantBand {
			t.Errorf("%q: band=%q want %q", c.line, s.Band, c.wantBand)
		}
		if s.Locator != c.wantLoc {
			t.Errorf("%q: loc=%q want %q", c.line, s.Locator, c.wantLoc)
		}
	}
}

func TestParseSpotRejectsNoise(t *testing.T) {
	noise := []string{
		"Welcome to DXCluster",
		"login:",
		"WX bulletin from G4ABC: heavy rain",
		"To ALL de F1XYZ: anyone using XYZ contest log?",
		"",
		"sh/dx 10",
	}
	for _, line := range noise {
		if _, ok := parseSpot(line); ok {
			t.Errorf("noise line parsed as spot: %q", line)
		}
	}
}

// The reply to SH/DX is a TABLE, not the "DX de …" broadcast — a completely
// different shape. Because only the broadcast form was parsed, a SH/DX/100 reply
// arrived, matched nothing and was dropped: the command looked like it did
// nothing at all. These lines must now land in the grid, flagged Historical.
func TestParseShowDX(t *testing.T) {
	cases := []struct {
		line    string
		call    string
		khz     float64
		spotter string
	}{
		{"  14195.0  EA8DHH       3-Jul-2026 1234Z  CQ DX                <F5ABC>", "EA8DHH", 14195.0, "F5ABC"},
		{" 7005.5   RA3XYZ       14-Jul-2026 0912Z                       <DL1ABC>", "RA3XYZ", 7005.5, "DL1ABC"},
		{"21025.0  VK9/DL2XYZ   1-Jan-2026 0001Z  up 2                   <JA1ABC>", "VK9/DL2XYZ", 21025.0, "JA1ABC"},
		// No spotter brackets — still a spot, just without a DE.
		{"  50313.0  IK0ABC       3-Jul-2026 1500Z  FT8", "IK0ABC", 50313.0, ""},
	}
	for _, c := range cases {
		got, ok := parseShowDX(c.line)
		if !ok {
			t.Errorf("parseShowDX(%q) failed — the SH/DX reply would be dropped again", c.line)
			continue
		}
		if got.DXCall != c.call || got.FreqKHz != c.khz || got.Spotter != c.spotter {
			t.Errorf("parseShowDX(%q) = call %q / %.1f / de %q, want %q / %.1f / %q",
				c.line, got.DXCall, got.FreqKHz, got.Spotter, c.call, c.khz, c.spotter)
		}
		if !got.Historical {
			t.Errorf("parseShowDX(%q): must be flagged Historical — otherwise 100 replayed spots fire 100 alerts", c.line)
		}
		if got.Band == "" {
			t.Errorf("parseShowDX(%q): band not derived from %.1f kHz", c.line, c.khz)
		}
	}
}

// The SH/DX parser must not swallow ordinary cluster prose — a console full of
// chatter turned into fake spots would be worse than no parser at all.
func TestParseShowDXRejectsNoise(t *testing.T) {
	noise := []string{
		"DX de F5ABC:     14195.0  EA8DHH       CQ DX                 1234Z", // the broadcast form: spotRE owns it
		"Hello and welcome to the DXSpider cluster",
		"WWV de VE7CC <18Z> : SFI=110, A=16, K=2",
		"F4BPO de GB7DXC 12-Jul-2026 2130Z dxspider >",
		"",
		"There are 42 users online",
	}
	for _, l := range noise {
		if s, ok := parseShowDX(l); ok {
			t.Errorf("parseShowDX(%q) wrongly produced a spot: %+v", l, s)
		}
	}
}
