package psu

import (
	"errors"
	"strings"
	"testing"
	"time"
)

// The CRC is the one thing here that cannot be checked by inspection, and every
// frame depends on it. CRC-16/MODBUS has a published check value: the CRC of
// the ASCII digits "123456789" is 0x4B37. If this passes, the polynomial, the
// initial value, the reflection and the absence of a final xor are all right.
func TestCRCMatchesTheStandardCheckValue(t *testing.T) {
	if got := crc16([]byte("123456789")); got != 0x4B37 {
		t.Fatalf("crc16(\"123456789\") = 0x%04X, want 0x4B37 — this is not CRC-16/MODBUS", got)
	}
}

// A frame including its own CRC checks to zero. That property is what crcOK
// relies on, so it is worth pinning separately from the check value.
func TestAFrameVerifiesItself(t *testing.T) {
	for _, f := range [][]byte{
		buildRead(1, regVolts, 2),
		buildWrite(1, regOnOff, 1),
		buildWrite(15, regOnOff, 0),
	} {
		if !crcOK(f) {
			t.Errorf("% X does not verify against its own CRC", f)
		}
		// And a single flipped bit must be caught.
		bad := append([]byte(nil), f...)
		bad[2] ^= 0x01
		if crcOK(bad) {
			t.Errorf("% X passed the CRC with a corrupted byte", bad)
		}
	}
}

// The frame layout, byte for byte against the manual: address, function,
// register high/low, count or value high/low, then CRC low byte first.
func TestFrameLayout(t *testing.T) {
	r := buildRead(1, 0x0010, 2)
	if len(r) != 8 {
		t.Fatalf("read frame is %d bytes, want 8", len(r))
	}
	want := []byte{0x01, 0x03, 0x00, 0x10, 0x00, 0x02}
	for i := range want {
		if r[i] != want[i] {
			t.Fatalf("read frame % X, want % X…", r, want)
		}
	}
	w := buildWrite(1, regOnOff, 1)
	want = []byte{0x01, 0x06, 0x00, 0x01, 0x00, 0x01}
	for i := range want {
		if w[i] != want[i] {
			t.Fatalf("write frame % X, want % X…", w, want)
		}
	}
	// The CRC goes out low byte first — the manual is explicit, and getting it
	// backwards makes every frame be ignored in silence.
	c := crc16(w[:6])
	if w[6] != byte(c&0xFF) || w[7] != byte(c>>8) {
		t.Errorf("CRC bytes % X, want %02X %02X (low first)", w[6:], byte(c&0xFF), byte(c>>8))
	}
}

func TestParseRead(t *testing.T) {
	// Two registers: 13.80 V (1380 at 2 decimals) and 2.500 A (2500 at 3).
	frame := appendCRC([]byte{0x01, 0x03, 0x04, 0x05, 0x64, 0x09, 0xC4})
	got, err := parseRead(1, 2, frame)
	if err != nil {
		t.Fatalf("parseRead: %v", err)
	}
	if len(got) != 2 || got[0] != 1380 || got[1] != 2500 {
		t.Fatalf("got %v, want [1380 2500]", got)
	}
	if v := float64(got[0]) / voltScale; v != 13.80 {
		t.Errorf("voltage scaled to %v, want 13.8", v)
	}
}

// A reply from another slave on the same bus must not be read as ours.
func TestParseRejectsAnotherSlave(t *testing.T) {
	frame := appendCRC([]byte{0x02, 0x03, 0x02, 0x05, 0x64})
	if _, err := parseRead(1, 1, frame); err == nil {
		t.Error("a reply from address 2 was accepted as address 1")
	}
}

func TestParseRejectsABadCRC(t *testing.T) {
	frame := appendCRC([]byte{0x01, 0x03, 0x02, 0x05, 0x64})
	frame[3] ^= 0xFF
	if _, err := parseRead(1, 1, frame); err == nil || !strings.Contains(err.Error(), "CRC") {
		t.Errorf("err = %v, want a CRC complaint", err)
	}
}

// An exception reply is the supply refusing, not the line failing, and the two
// need different answers from the operator.
func TestParseReportsAnException(t *testing.T) {
	frame := appendCRC([]byte{0x01, 0x83, 0x02})
	_, err := parseRead(1, 1, frame)
	var me modbusError
	if !errors.As(err, &me) {
		t.Fatalf("err = %v, want a modbusError", err)
	}
	if me.code != 2 || !strings.Contains(err.Error(), "illegal data address") {
		t.Errorf("exception decoded as %v", err)
	}
}

// The echo is the ONLY confirmation that the output actually switched. A reply
// echoing a different value means the supply did something else, and reporting
// that as success is how a radio ends up with no power and a green light.
func TestWriteEchoMustMatch(t *testing.T) {
	ok := appendCRC([]byte{0x01, 0x06, 0x00, 0x01, 0x00, 0x01})
	if err := parseWriteEcho(1, regOnOff, 1, ok); err != nil {
		t.Fatalf("a correct echo was rejected: %v", err)
	}
	wrongVal := appendCRC([]byte{0x01, 0x06, 0x00, 0x01, 0x00, 0x00})
	if err := parseWriteEcho(1, regOnOff, 1, wrongVal); err == nil {
		t.Error("an echo of 0 was accepted for a command of 1 — the output never switched")
	}
	wrongReg := appendCRC([]byte{0x01, 0x06, 0x00, 0x30, 0x00, 0x01})
	if err := parseWriteEcho(1, regOnOff, 1, wrongReg); err == nil {
		t.Error("an echo for register 0x0030 was accepted for a write to 0x0001")
	}
}

// quietPort delivers a frame in pieces, then goes quiet — a serial port that
// reports a timeout as (0, nil), which is what Windows does.
type quietPort struct {
	chunks [][]byte
	i      int
}

func (p *quietPort) Read(b []byte) (int, error) {
	if p.i >= len(p.chunks) {
		time.Sleep(2 * time.Millisecond)
		return 0, nil // timeout, not an error
	}
	n := copy(b, p.chunks[p.i])
	p.i++
	return n, nil
}

// A Modbus RTU frame has no terminator: it ends when the line falls quiet. The
// reader must assemble a dribbled frame and then stop on its own.
func TestReadFrameAssemblesUntilQuiet(t *testing.T) {
	want := appendCRC([]byte{0x01, 0x03, 0x04, 0x05, 0x64, 0x09, 0xC4})
	p := &quietPort{chunks: [][]byte{want[:2], want[2:5], want[5:]}}
	got, err := readFrame(p, time.Second)
	if err != nil {
		t.Fatalf("readFrame: %v", err)
	}
	if len(got) != len(want) {
		t.Fatalf("read % X, want % X", got, want)
	}
	for i := range want {
		if got[i] != want[i] {
			t.Fatalf("read % X, want % X", got, want)
		}
	}
}

// A supply that is switched off, or not on this port, must produce an error
// rather than a wait that never ends.
func TestReadFrameGivesUpOnSilence(t *testing.T) {
	done := make(chan error, 1)
	go func() { _, err := readFrame(&quietPort{}, 80*time.Millisecond); done <- err }()
	select {
	case err := <-done:
		if err == nil {
			t.Error("silence was reported as a frame")
		}
	case <-time.After(3 * time.Second):
		t.Fatal("readFrame never returned")
	}
}
