package proto_test

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

	"github.com/uopi/goca/proto"
)

// -------------------------------------------------------------------------- //
// DBR_TIME_* variants not covered by proto_test.go                            //
// -------------------------------------------------------------------------- //

func TestDecodeTimeFloat(t *testing.T) {
	hdr := buildTimeHeader(0, 0, 0, 0)
	val := make([]byte, 4)
	binary.BigEndian.PutUint32(val, math.Float32bits(2.5))
	tv, ok := proto.DecodeTimeValue(proto.DBRTimeFloat, 1, append(hdr, val...))
	if !ok || math.Abs(float64(tv.Float)-2.5) > 1e-6 {
		t.Errorf("float: ok=%v Float=%g", ok, tv.Float)
	}
}

func TestDecodeTimeShort(t *testing.T) {
	hdr := buildTimeHeader(0, 0, 0, 0)
	val := []byte{0x00, 0x00, 0xFF, 0xD6} // 2-byte pad + int16(-42)
	tv, ok := proto.DecodeTimeValue(proto.DBRTimeShort, 1, append(hdr, val...))
	if !ok || tv.Short != -42 {
		t.Errorf("short: ok=%v Short=%d", ok, tv.Short)
	}
}

func TestDecodeTimeChar(t *testing.T) {
	hdr := buildTimeHeader(0, 0, 0, 0)
	val := []byte{0x00, 0x00, 0x00, 0x41} // pad0 + pad1 + value 'A'
	tv, ok := proto.DecodeTimeValue(proto.DBRTimeChar, 1, append(hdr, val...))
	if !ok || tv.Char != 0x41 {
		t.Errorf("char: ok=%v Char=%d", ok, tv.Char)
	}
}

func TestDecodeTimeUnknownType(t *testing.T) {
	hdr := buildTimeHeader(0, 0, 0, 0)
	if _, ok := proto.DecodeTimeValue(9999, 1, append(hdr, make([]byte, 16)...)); ok {
		t.Error("unknown DBR type should return false")
	}
}

// -------------------------------------------------------------------------- //
// DBR_CTRL_LONG / DBR_CTRL_STRING                                             //
// -------------------------------------------------------------------------- //

func TestDecodeCtrlLong(t *testing.T) {
	p := make([]byte, 48)
	binary.BigEndian.PutUint16(p[2:], 2) // severity = Major
	copy(p[4:], "counts")
	i32 := func(off int, v int32) { binary.BigEndian.PutUint32(p[off:], uint32(v)) }
	i32(12, 1000) // upper_disp
	i32(16, -10)  // lower_disp
	i32(44, 777)  // value

	cl, ok := proto.DecodeCtrlLong(p)
	if !ok {
		t.Fatal("DecodeCtrlLong returned false")
	}
	if cl.Units != "counts" || cl.Value != 777 || cl.UpperDispLimit != 1000 || cl.LowerDispLimit != -10 {
		t.Errorf("got %+v", cl)
	}
	if cl.Severity != proto.SeverityMajor {
		t.Errorf("severity = %v, want Major", cl.Severity)
	}
	if _, ok := proto.DecodeCtrlLong(make([]byte, 10)); ok {
		t.Error("short CtrlLong payload should return false")
	}
}

func TestDecodeCtrlString(t *testing.T) {
	p := make([]byte, 44)
	binary.BigEndian.PutUint16(p[2:], 1) // severity = Minor
	copy(p[4:], "READY")

	cs, ok := proto.DecodeCtrlString(p)
	if !ok {
		t.Fatal("DecodeCtrlString returned false")
	}
	if cs.Value != "READY" || cs.Severity != proto.SeverityMinor {
		t.Errorf("got %+v", cs)
	}
	if _, ok := proto.DecodeCtrlString(make([]byte, 10)); ok {
		t.Error("short CtrlString payload should return false")
	}
}

// -------------------------------------------------------------------------- //
// EncodeShort                                                                 //
// -------------------------------------------------------------------------- //

func TestEncodeShort(t *testing.T) {
	b := proto.EncodeShort(-7)
	if len(b) != 8 {
		t.Fatalf("len = %d, want 8", len(b))
	}
	if got := int16(binary.BigEndian.Uint16(b)); got != -7 {
		t.Errorf("decoded = %d, want -7", got)
	}
}

// -------------------------------------------------------------------------- //
// NativeCtrlType                                                              //
// -------------------------------------------------------------------------- //

func TestNativeCtrlType(t *testing.T) {
	cases := []struct {
		dbf  int
		want uint16
	}{
		{proto.DBFDouble, proto.DBRCtrlDouble},
		{proto.DBFFloat, proto.DBRCtrlDouble},
		{proto.DBFLong, proto.DBRCtrlLong},
		{proto.DBFShort, proto.DBRCtrlLong},
		{proto.DBFChar, proto.DBRCtrlLong},
		{proto.DBFEnum, proto.DBRCtrlEnum},
		{proto.DBFString, proto.DBRCtrlString},
		{9999, proto.DBRCtrlDouble}, // default
	}
	for _, c := range cases {
		if got := proto.NativeCtrlType(c.dbf); got != c.want {
			t.Errorf("NativeCtrlType(%d) = %d, want %d", c.dbf, got, c.want)
		}
	}
}

// -------------------------------------------------------------------------- //
// DecodeHeader: extended encoding + error path                                //
// -------------------------------------------------------------------------- //

func TestDecodeHeaderExtended(t *testing.T) {
	raw := make([]byte, 24)
	binary.BigEndian.PutUint16(raw[0:], proto.CmdReadNotify)
	binary.BigEndian.PutUint16(raw[2:], 0xFFFF) // extended marker
	binary.BigEndian.PutUint16(raw[6:], 0x0000)
	binary.BigEndian.PutUint32(raw[16:], 70000) // real payload size
	binary.BigEndian.PutUint32(raw[20:], 5)     // real count

	h, n, err := proto.DecodeHeader(bytes.NewReader(raw))
	if err != nil {
		t.Fatal(err)
	}
	if n != 24 {
		t.Errorf("bytes read = %d, want 24", n)
	}
	if h.PayloadSize != 70000 || h.DataCount != 5 {
		t.Errorf("extended header = %+v", h)
	}
}

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