package tciserver

import (
	"fmt"
	"strings"
	"testing"
)

// fakeRig is a radio that remembers what it was told. Everything here is about
// what OpsLog does with a client's command, so the rig only has to answer and
// record.
type fakeRig struct {
	freq, rxFreq int64
	mode         string
	split        bool
	txHz         int64
	ptt          bool
	splitErr     error
	pttErr       error
	calls        []string
}

func (r *fakeRig) Freq() int64          { return r.freq }
func (r *fakeRig) RxFreq() int64        { return r.rxFreq }
func (r *fakeRig) Mode() string         { return r.mode }
func (r *fakeRig) Split() (bool, int64) { return r.split, r.txHz }
func (r *fakeRig) SetFreq(hz int64) error {
	r.calls = append(r.calls, fmt.Sprintf("freq=%d", hz))
	r.freq, r.rxFreq = hz, hz
	return nil
}
func (r *fakeRig) SetMode(m string) error {
	r.calls = append(r.calls, "mode="+m)
	r.mode = m
	return nil
}
func (r *fakeRig) SetPTT(on bool) error {
	// Refused BEFORE the state moves, like a radio that never got the frame.
	if r.pttErr != nil {
		return r.pttErr
	}
	r.calls = append(r.calls, fmt.Sprintf("ptt=%v", on))
	r.ptt = on
	return nil
}
func (r *fakeRig) SetSplit(on bool, txHz int64) error {
	if r.splitErr != nil {
		return r.splitErr
	}
	r.calls = append(r.calls, fmt.Sprintf("split=%v,%d", on, txHz))
	r.split, r.txHz = on, txHz
	return nil
}

// srv builds a server with no listener — handle() and publish() are the whole
// protocol, and neither needs a socket.
func srv(r *fakeRig) *Server { return New(0, r, nil) }

// A client with no connection: send() would need one, so reads are checked
// through the returned line instead. This is why handle returns what it sent.
func ask(t *testing.T, s *Server, cmd string) string {
	t.Helper()
	return s.handle(&client{}, cmd)
}

// The initialisation block is what a client needs before it will believe there
// is a radio at all. Its contents come from §4.1 of the protocol document, and
// a client that does not see ready; simply waits for ever.
func TestInitBlockCarriesTheDocumentedInitialisationSet(t *testing.T) {
	s := srv(&fakeRig{freq: 14074000, rxFreq: 14074000, mode: "USB"})
	block := strings.Join(s.initBlock(), "")
	for _, want := range []string{
		"protocol:ExpertSDR3,", "device:", "receive_only:false;", "trx_count:1;",
		"channel_count:2;", "vfo_limits:", "if_limits:", "modulations_list:",
		"ready;", "start;",
		// Transmit permission. A client that models it starts out assuming it
		// may NOT transmit, and without this never even tries — PTT does
		// nothing and the server never sees a trx command at all.
		"tx_enable:0,true;",
	} {
		if !strings.Contains(block, want) {
			t.Errorf("the initialisation block is missing %q — a client would not proceed past connect", want)
		}
	}
	// And the current state, so a client that connects mid-session shows the
	// right frequency instead of waiting for the operator to touch something.
	if !strings.Contains(block, "vfo:0,0,14074000;") {
		t.Errorf("no current frequency in the block:\n%s", block)
	}
	if !strings.Contains(block, "modulation:0,usb;") {
		t.Errorf("no current mode in the block:\n%s", block)
	}
}

// Channel A is where we LISTEN, channel B where we transmit. Handing these to a
// client the wrong way round is the one mistake here that puts a station on the
// DX's own frequency, so it is pinned in both directions.
func TestSplitPutsTheListeningFrequencyOnChannelA(t *testing.T) {
	// OpsLog's RigState is ADIF: Freq is the TRANSMIT frequency, RxFreq where we
	// listen. A DX transmitting on 14025 and listening up 2.
	r := &fakeRig{freq: 14027000, rxFreq: 14025000, mode: "CW", split: true, txHz: 14027000}
	s := srv(r)
	block := strings.Join(s.initBlock(), "")
	if !strings.Contains(block, "vfo:0,0,14025000;") {
		t.Errorf("channel A is not the receive frequency:\n%s", block)
	}
	if !strings.Contains(block, "vfo:0,1,14027000;") {
		t.Errorf("channel B is not the transmit frequency:\n%s", block)
	}
	if !strings.Contains(block, "split_enable:0,true;") {
		t.Errorf("split was not announced:\n%s", block)
	}
}

// Simplex: both channels report the one frequency, so a client reading either
// gets the right answer.
func TestSimplexReportsTheSameFrequencyOnBothChannels(t *testing.T) {
	s := srv(&fakeRig{freq: 7100000, rxFreq: 7100000, mode: "SSB"})
	if got := ask(t, s, "vfo:0,0"); got != "vfo:0,0,7100000;" {
		t.Errorf("read of channel A = %q", got)
	}
	if got := ask(t, s, "vfo:0,1"); got != "vfo:0,1,7100000;" {
		t.Errorf("read of channel B = %q", got)
	}
	// 7 MHz is below 10, so SSB is lower sideband — a client told "ssb" would
	// not recognise it at all, the modulation list is the vocabulary.
	if got := ask(t, s, "modulation:0"); got != "modulation:0,lsb;" {
		t.Errorf("read of the mode = %q, want lsb below 10 MHz", got)
	}
}

// The client tunes the radio.
func TestAClientCanTuneAndSetModeAndKey(t *testing.T) {
	r := &fakeRig{freq: 14074000, rxFreq: 14074000, mode: "USB"}
	s := srv(r)
	ask(t, s, "vfo:0,0,14200000")
	ask(t, s, "modulation:0,cw")
	ask(t, s, "trx:0,true")
	ask(t, s, "trx:0,false")
	want := []string{"freq=14200000", "mode=CW", "ptt=true", "ptt=false"}
	if strings.Join(r.calls, " ") != strings.Join(want, " ") {
		t.Errorf("the radio was told %v, want %v", r.calls, want)
	}
}

// Channel B is the SPLIT transmit frequency. Writing it while the rig is
// simplex must not move the only VFO there is: the client asked to prepare a
// transmit frequency, not to QSY — and a logger that did this on every spot
// click would drag the operator off the station they were listening to.
func TestWritingChannelBWhileSimplexLeavesTheRigAlone(t *testing.T) {
	r := &fakeRig{freq: 14074000, rxFreq: 14074000, mode: "USB"}
	s := srv(r)
	ask(t, s, "vfo:0,1,14080000")
	if len(r.calls) != 0 {
		t.Errorf("the radio was told %v — a split TX frequency moved a simplex rig", r.calls)
	}
	// With split armed it means what it says.
	r.split, r.txHz = true, 14074000
	ask(t, s, "vfo:0,1,14080000")
	if len(r.calls) != 1 || r.calls[0] != "split=true,14080000" {
		t.Errorf("with split on the radio was told %v, want the new transmit frequency", r.calls)
	}
}

// A backend that cannot split says so, and the refusal must not be dressed up
// as success: the client can then tell the operator to use Fake It, where
// before it would transmit on the receive frequency believing all was well.
func TestARefusedSplitIsNotAnnouncedAsDone(t *testing.T) {
	r := &fakeRig{freq: 14025000, rxFreq: 14025000, mode: "CW", splitErr: fmt.Errorf("this backend cannot split")}
	s := srv(r)
	if got := ask(t, s, "split_enable:0,true"); got != "" {
		t.Errorf("a refused split answered %q", got)
	}
	if r.split {
		t.Error("the rig was recorded as split after the backend refused")
	}
}

// Only what moved is sent. TCI clients redraw on every command they receive, so
// re-sending an unchanged frequency four times a second makes a VFO readout
// flicker and, in some clients, fights the operator's own tuning.
func TestOnlyChangesAreSent(t *testing.T) {
	r := &fakeRig{freq: 14074000, rxFreq: 14074000, mode: "USB"}
	s := srv(r)

	first := s.publish()
	if len(first) == 0 {
		t.Fatal("the first pass sent nothing — a client would never learn the state")
	}
	if got := s.publish(); len(got) != 0 {
		t.Errorf("an unchanged radio produced %v", got)
	}

	r.freq, r.rxFreq = 14200000, 14200000
	got := strings.Join(s.publish(), "")
	// Both channels move together on a simplex rig, and the transmit frequency
	// has its own command besides — a client showing "TX 14.200" reads that one,
	// and channel B alone leaves it stale.
	for _, want := range []string{"vfo:0,0,14200000;", "vfo:0,1,14200000;", "tx_frequency:14200000;"} {
		if !strings.Contains(got, want) {
			t.Errorf("after a QSY the clients were not told %q — got %q", want, got)
		}
	}
	if got := s.publish(); len(got) != 0 {
		t.Errorf("the QSY was re-sent: %v", got)
	}
}

// Modes travel both ways, and the data family is the interesting half: a client
// switching to "digital" must not overwrite the mode the operator chose in
// OpsLog with a guess at which data mode it was.
func TestModeMapping(t *testing.T) {
	up := []struct {
		adif string
		hz   int64
		want string
	}{
		{"CW", 14025000, "cw"},
		{"SSB", 14200000, "usb"},
		{"SSB", 7100000, "lsb"},
		{"USB", 7100000, "usb"}, // an explicit sideband is never second-guessed
		{"FT8", 14074000, "digu"},
		{"RTTY", 14080000, "digl"},
		{"AM", 3700000, "am"},
		{"FM", 145500000, "nfm"},
		{"", 14074000, ""},
	}
	for _, c := range up {
		if got := adifToTCIMode(c.adif, c.hz); got != c.want {
			t.Errorf("adifToTCIMode(%q, %d) = %q, want %q", c.adif, c.hz, got, c.want)
		}
	}
	down := map[string]string{
		"cw": "CW", "usb": "USB", "lsb": "LSB", "am": "AM", "sam": "AM",
		"nfm": "FM", "digu": "DATA", "digl": "DATA", "": "",
	}
	for in, want := range down {
		if got := tciModeToADIF(in); got != want {
			t.Errorf("tciModeToADIF(%q) = %q, want %q", in, got, want)
		}
	}
}

// A command for something OpsLog is not — audio streams, CW macros, the
// panorama's settings — is met with silence rather than an error. A client
// sends these hopefully at connect, and a refusal it did not ask for reads as a
// fault with the rig.
func TestUnknownCommandsAreQuiet(t *testing.T) {
	s := srv(&fakeRig{freq: 14074000, rxFreq: 14074000, mode: "USB"})
	for _, cmd := range []string{"audio_start:0", "cw_macros_speed:25", "rx_filter_band:0,-2700,-100", "iq_start:0"} {
		if got := ask(t, s, cmd); got != "" {
			t.Errorf("%q answered %q", cmd, got)
		}
	}
}

// Split, with the client sending the two commands in the order it prefers.
//
// A client working split has to say two things: where to transmit, and that
// split is on. Nothing obliges it to say them in that order, and the frequency
// arriving first is the dangerous case: discarding it and then arming split
// leaves the transmit VFO on whatever it held — the RECEIVE frequency — so the
// operator transmits straight onto the DX while their software shows exactly
// what they asked for.
func TestSplitIsArmedOnTheFrequencyTheClientGaveWhicheverOrderItCame(t *testing.T) {
	// Frequency first, then split — the order that used to lose the frequency.
	r := &fakeRig{freq: 14025000, rxFreq: 14025000, mode: "CW"}
	s := srv(r)
	ask(t, s, "vfo:0,1,14027000")
	ask(t, s, "split_enable:0,true")
	if len(r.calls) != 1 || r.calls[0] != "split=true,14027000" {
		t.Errorf("frequency first: the radio was told %v, want split armed on 14027000", r.calls)
	}

	// Split first, then the frequency — the order that always worked.
	r2 := &fakeRig{freq: 14025000, rxFreq: 14025000, mode: "CW"}
	s2 := srv(r2)
	ask(t, s2, "split_enable:0,true")
	ask(t, s2, "vfo:0,1,14027000")
	if len(r2.calls) == 0 || r2.calls[len(r2.calls)-1] != "split=true,14027000" {
		t.Errorf("split first: the radio was told %v, want it to end on 14027000", r2.calls)
	}
}

// "Fake It" uses no split at all: the client shifts the DIAL at the start of
// transmit and shifts it back at the end. All it needs is channel A, and it
// must reach the radio both ways.
func TestFakeItIsJustTheDialMoving(t *testing.T) {
	r := &fakeRig{freq: 14074000, rxFreq: 14074000, mode: "USB"}
	s := srv(r)
	ask(t, s, "vfo:0,0,14075300") // up for the over
	ask(t, s, "vfo:0,0,14074000") // and back
	want := []string{"freq=14075300", "freq=14074000"}
	if strings.Join(r.calls, " ") != strings.Join(want, " ") {
		t.Errorf("the radio was told %v, want %v", r.calls, want)
	}
}

// A client in Fake It still says "split off" to be sure. The rig is already
// simplex, so there is nothing to do — and saying so beats asking a backend
// that may not be able to set split at all.
//
// This is what broke JTDX through the rigctl server: an error answered to a
// request that was already true, read as rig control failing, and the
// transmission abandoned a second into the frame.
func TestSayingSplitOffWhenAlreadySimplexTouchesNothing(t *testing.T) {
	r := &fakeRig{freq: 14074000, rxFreq: 14074000, mode: "USB",
		splitErr: fmt.Errorf("this backend cannot split")}
	s := srv(r)
	ask(t, s, "split_enable:0,false")
	if len(r.calls) != 0 {
		t.Errorf("the radio was told %v for a state it was already in", r.calls)
	}
}

// A client restating PTT must not re-command the radio. Through the rigctl
// server, one sent set_ptt 0 sixteen times a second and the Flex's own transmit
// request was overwritten between two of them inside a millisecond.
func TestRepeatedPTTIsNotResentToTheRadio(t *testing.T) {
	r := &fakeRig{freq: 14074000, rxFreq: 14074000, mode: "USB"}
	s := srv(r)
	for i := 0; i < 5; i++ {
		ask(t, s, "trx:0,false")
	}
	if len(r.calls) != 1 || r.calls[0] != "ptt=false" {
		// The FIRST one always goes through: there is no knowing how the radio
		// was left.
		t.Errorf("the radio was told %v, want one unkey and no repeats", r.calls)
	}
	ask(t, s, "trx:0,true")
	ask(t, s, "trx:0,true")
	if len(r.calls) != 2 || r.calls[1] != "ptt=true" {
		t.Errorf("the radio was told %v, want the change through and the repeat dropped", r.calls)
	}
}

// A refused un-key must never be remembered as done.
//
// The failure an operator hit running JTDX over TCI with an Icom on CI-V: the
// rig went to transmit, the un-key was refused on a lost acknowledgement, and
// from then on NOTHING could take it out of transmit. The cache had stamped
// "off" before the radio was even commanded and kept it after the refusal, so
// every later trx:0,false was dismissed as a repeat of a state the radio had
// never reached. It is per-server, not per-connection, so reconnecting the
// client changed nothing either — the transmitter stayed keyed into the
// amplifier, with no drive, until the radio was switched off by hand.
func TestARefusedUnkeyIsNotRememberedAsDone(t *testing.T) {
	r := &fakeRig{freq: 14074000, rxFreq: 14074000, mode: "USB"}
	s := srv(r)
	ask(t, s, "trx:0,true")
	if !r.ptt {
		t.Fatal("the rig was never keyed — the test would prove nothing")
	}

	r.pttErr = fmt.Errorf("icom: timeout waiting for response")
	ask(t, s, "trx:0,false")
	if !r.ptt {
		t.Fatal("the fake rig un-keyed on a refusal — the test would prove nothing")
	}

	// The client asks again, and this time the radio answers. It MUST be told.
	r.pttErr = nil
	ask(t, s, "trx:0,false")
	if r.ptt {
		t.Error("still keyed: the refused un-key was cached as done and the retry was dropped as a repeat")
	}
}

// A client that walks away mid-over must not leave the rig transmitting, and
// the release must be once-only — a second call has nothing to un-key and must
// not re-command a radio that is already receiving.
func TestReleasePTTUnkeysOnceWhenTheClientLeaves(t *testing.T) {
	r := &fakeRig{freq: 14074000, rxFreq: 14074000, mode: "USB"}
	s := srv(r)
	ask(t, s, "trx:0,true")

	s.releasePTT("client left")
	if r.ptt {
		t.Error("the rig is still keyed after the client left")
	}
	n := len(r.calls)
	s.releasePTT("client left")
	if len(r.calls) != n {
		t.Errorf("released twice — the radio was told %v", r.calls)
	}
}
