package pva

import (
	"bytes"
	"encoding/binary"
	"testing"
)

// ---- Header codec -----------------------------------------------------

func TestHeaderRoundTrip(t *testing.T) {
	h := PVAHeader{Version: 1, Flags: flagApp, Command: CmdGet, Size: 1234}
	var buf bytes.Buffer
	if err := WriteHeader(&buf, h); err != nil {
		t.Fatal(err)
	}
	got, err := ReadHeader(&buf)
	if err != nil {
		t.Fatal(err)
	}
	if got.Version != h.Version || got.Command != h.Command || got.Size != h.Size {
		t.Errorf("round-trip header: got %+v want %+v", got, h)
	}
}

func TestReadHeaderBadMagic(t *testing.T) {
	raw := []byte{0xAB, 0x01, 0x00, CmdGet, 0, 0, 0, 0}
	if _, err := ReadHeader(bytes.NewReader(raw)); err == nil {
		t.Error("bad magic: want error")
	}
}

func TestReadHeaderTruncated(t *testing.T) {
	if _, err := ReadHeader(bytes.NewReader([]byte{0xCA, 0x01})); err == nil {
		t.Error("truncated header: want error")
	}
}

func TestHeaderBigEndian(t *testing.T) {
	// Hand-assemble a big-endian server header with size 0x00010203.
	raw := []byte{magic, 0x01, flagBigEndian | flagFromServer, CmdMonitor, 0x00, 0x01, 0x02, 0x03}
	h, err := ReadHeader(bytes.NewReader(raw))
	if err != nil {
		t.Fatal(err)
	}
	if h.isLittleEndian() {
		t.Error("expected big-endian header")
	}
	if !h.isFromServer() {
		t.Error("expected from-server bit")
	}
	if h.byteOrder() != binary.BigEndian {
		t.Error("byteOrder should be big-endian")
	}
	if h.Size != 0x00010203 {
		t.Errorf("size = %#x, want 0x00010203", h.Size)
	}
}

func TestHeaderPredicates(t *testing.T) {
	le := PVAHeader{Flags: flagApp}
	if !le.isLittleEndian() || le.isFromServer() || le.isControl() {
		t.Errorf("app/LE header predicates wrong: %+v", le)
	}
	ctrl := PVAHeader{Flags: flagControl}
	if !ctrl.isControl() {
		t.Error("control header should report isControl")
	}
}

// ---- BuildMessage -----------------------------------------------------

func TestBuildMessage(t *testing.T) {
	payload := []byte{1, 2, 3, 4, 5}
	// Pass the server flag too; BuildMessage must clear it (client direction).
	msg := BuildMessage(CmdCreateChannel, flagFromServer, payload)
	h, err := ReadHeader(bytes.NewReader(msg))
	if err != nil {
		t.Fatal(err)
	}
	if h.Command != CmdCreateChannel {
		t.Errorf("command = %#x, want %#x", h.Command, CmdCreateChannel)
	}
	if h.isFromServer() {
		t.Error("BuildMessage must clear the from-server flag")
	}
	if int(h.Size) != len(payload) {
		t.Errorf("size = %d, want %d", h.Size, len(payload))
	}
	if got := msg[headerSize:]; !bytes.Equal(got, payload) {
		t.Errorf("payload = %v, want %v", got, payload)
	}
}

// ---- Status decoding --------------------------------------------------

func TestReadStatusOKShort(t *testing.T) {
	s, err := ReadStatus(bytes.NewReader([]byte{StatusOK}), binary.LittleEndian)
	if err != nil {
		t.Fatal(err)
	}
	if !s.OK() || s.Error() != "" {
		t.Errorf("short OK status: got %+v", s)
	}
}

func TestReadStatusError(t *testing.T) {
	var buf bytes.Buffer
	buf.WriteByte(StatusError)
	buf.WriteByte(0x04) // compact size 4
	buf.WriteString("boom")
	buf.WriteByte(0x00) // empty stack
	s, err := ReadStatus(&buf, binary.LittleEndian)
	if err != nil {
		t.Fatal(err)
	}
	if s.OK() {
		t.Error("error status should not be OK")
	}
	if s.Message != "boom" {
		t.Errorf("message = %q, want boom", s.Message)
	}
	if s.Error() == "" {
		t.Error("Error() should be non-empty for an error status")
	}
}

func TestReadStatusTruncated(t *testing.T) {
	if _, err := ReadStatus(bytes.NewReader(nil), binary.LittleEndian); err == nil {
		t.Error("empty status: want error")
	}
	// type byte present but the message string is missing.
	if _, err := ReadStatus(bytes.NewReader([]byte{StatusError}), binary.LittleEndian); err == nil {
		t.Error("status missing message: want error")
	}
}

// ---- readPVAString ----------------------------------------------------

func TestReadPVAString(t *testing.T) {
	// Short form.
	short := append([]byte{0x03}, []byte("abc")...)
	s, err := readPVAString(bytes.NewReader(short), binary.LittleEndian)
	if err != nil || s != "abc" {
		t.Errorf("short string: got %q err %v", s, err)
	}
	// Empty.
	s, err = readPVAString(bytes.NewReader([]byte{0x00}), binary.LittleEndian)
	if err != nil || s != "" {
		t.Errorf("empty string: got %q err %v", s, err)
	}
	// Extended form: 0xFF + int32 length + bytes.
	var ext bytes.Buffer
	ext.WriteByte(0xFF)
	_ = binary.Write(&ext, binary.LittleEndian, int32(5))
	ext.WriteString("hello")
	s, err = readPVAString(&ext, binary.LittleEndian)
	if err != nil || s != "hello" {
		t.Errorf("extended string: got %q err %v", s, err)
	}
}

// ---- env --------------------------------------------------------------

func TestAddrsFromEnv(t *testing.T) {
	t.Setenv("EPICS_PVA_ADDR_LIST", "")
	if got := addrsFromEnv(); got != nil {
		t.Errorf("empty env: got %v, want nil", got)
	}
	t.Setenv("EPICS_PVA_ADDR_LIST", "  1.2.3.4   5.6.7.8 ")
	got := addrsFromEnv()
	if len(got) != 2 || got[0] != "1.2.3.4" || got[1] != "5.6.7.8" {
		t.Errorf("addr list: got %v", got)
	}
}
