package pvdata_test

import (
	"bytes"
	"testing"

	"github.com/uopi/gopva/pvdata"
)

// ---- Compact size encoding -------------------------------------------

func TestCompactSize(t *testing.T) {
	cases := []int64{0, 1, 10, 254, 255, 256, 1000, 65535, 70000, 1<<31 - 1}
	for _, n := range cases {
		var buf bytes.Buffer
		if err := pvdata.WriteSize(&buf, n); err != nil {
			t.Fatalf("WriteSize(%d): %v", n, err)
		}
		got, err := pvdata.ReadSize(&buf)
		if err != nil {
			t.Fatalf("ReadSize for %d: %v", n, err)
		}
		if got != n {
			t.Errorf("round-trip size %d → %d", n, got)
		}
	}
}

// ---- String encoding ------------------------------------------------

func TestString(t *testing.T) {
	cases := []string{"", "hello", "UOPI:TICK", "a string with spaces and unicode: é"}
	for _, s := range cases {
		var buf bytes.Buffer
		if err := pvdata.WriteString(&buf, s); err != nil {
			t.Fatalf("WriteString(%q): %v", s, err)
		}
		got, err := pvdata.ReadString(&buf)
		if err != nil {
			t.Fatalf("ReadString for %q: %v", s, err)
		}
		if got != s {
			t.Errorf("round-trip string %q → %q", s, got)
		}
	}
}

// ---- FieldDesc (introspection) round-trip ---------------------------

func TestFieldDescScalar(t *testing.T) {
	desc := pvdata.FieldDesc{Kind: pvdata.KindScalar, TypeCode: pvdata.TypeCodeDouble}
	var buf bytes.Buffer
	if err := pvdata.WriteFieldDesc(&buf, desc); err != nil {
		t.Fatal(err)
	}
	got, err := pvdata.ReadFieldDesc(&buf)
	if err != nil {
		t.Fatal(err)
	}
	if got.TypeCode != desc.TypeCode || got.Kind != desc.Kind {
		t.Errorf("scalar round-trip: got %+v, want %+v", got, desc)
	}
}

func TestFieldDescStruct(t *testing.T) {
	// Minimal NTScalar-like struct: value(double) + timeStamp(struct{…})
	desc := pvdata.FieldDesc{
		Kind:     pvdata.KindStruct,
		TypeCode: pvdata.TypeCodeStruct,
		TypeID:   "epics:nt/NTScalar:1.0",
		Fields: []pvdata.Field{
			{Name: "value", Desc: pvdata.FieldDesc{Kind: pvdata.KindScalar, TypeCode: pvdata.TypeCodeDouble}},
			{Name: "alarm", Desc: pvdata.FieldDesc{
				Kind: pvdata.KindStruct, TypeCode: pvdata.TypeCodeStruct, TypeID: "alarm_t",
				Fields: []pvdata.Field{
					{Name: "severity", Desc: pvdata.FieldDesc{Kind: pvdata.KindScalar, TypeCode: pvdata.TypeCodeInt}},
					{Name: "status", Desc: pvdata.FieldDesc{Kind: pvdata.KindScalar, TypeCode: pvdata.TypeCodeInt}},
					{Name: "message", Desc: pvdata.FieldDesc{Kind: pvdata.KindString, TypeCode: pvdata.TypeCodeString}},
				},
			}},
			{Name: "timeStamp", Desc: pvdata.FieldDesc{
				Kind: pvdata.KindStruct, TypeCode: pvdata.TypeCodeStruct, TypeID: "time_t",
				Fields: []pvdata.Field{
					{Name: "secondsPastEpoch", Desc: pvdata.FieldDesc{Kind: pvdata.KindScalar, TypeCode: pvdata.TypeCodeLong}},
					{Name: "nanoseconds", Desc: pvdata.FieldDesc{Kind: pvdata.KindScalar, TypeCode: pvdata.TypeCodeInt}},
					{Name: "userTag", Desc: pvdata.FieldDesc{Kind: pvdata.KindScalar, TypeCode: pvdata.TypeCodeInt}},
				},
			}},
		},
	}

	var buf bytes.Buffer
	if err := pvdata.WriteFieldDesc(&buf, desc); err != nil {
		t.Fatal(err)
	}
	got, err := pvdata.ReadFieldDesc(&buf)
	if err != nil {
		t.Fatal(err)
	}
	if got.TypeID != desc.TypeID {
		t.Errorf("typeID: got %q want %q", got.TypeID, desc.TypeID)
	}
	if len(got.Fields) != len(desc.Fields) {
		t.Fatalf("field count: got %d want %d", len(got.Fields), len(desc.Fields))
	}
	for i, f := range desc.Fields {
		if got.Fields[i].Name != f.Name {
			t.Errorf("field[%d] name: got %q want %q", i, got.Fields[i].Name, f.Name)
		}
	}
}

// ---- Value round-trip -----------------------------------------------

func TestValueDouble(t *testing.T) {
	desc := pvdata.FieldDesc{Kind: pvdata.KindScalar, TypeCode: pvdata.TypeCodeDouble}
	var v pvdata.Value = float64(3.14159)
	var buf bytes.Buffer
	if err := pvdata.WriteValue(&buf, desc, v); err != nil {
		t.Fatal(err)
	}
	got, err := pvdata.ReadValue(&buf, desc)
	if err != nil {
		t.Fatal(err)
	}
	if got.(float64) != v.(float64) {
		t.Errorf("double round-trip: got %v want %v", got, v)
	}
}

func TestValueIntArray(t *testing.T) {
	desc := pvdata.FieldDesc{Kind: pvdata.KindScalarArr, TypeCode: pvdata.TypeCodeIntArr}
	var v pvdata.Value = []int32{1, 2, 3, -7, 1 << 30}
	var buf bytes.Buffer
	if err := pvdata.WriteValue(&buf, desc, v); err != nil {
		t.Fatal(err)
	}
	got, err := pvdata.ReadValue(&buf, desc)
	if err != nil {
		t.Fatal(err)
	}
	arr := got.([]int32)
	src := v.([]int32)
	if len(arr) != len(src) {
		t.Fatalf("int32 array length: got %d want %d", len(arr), len(src))
	}
	for i := range src {
		if arr[i] != src[i] {
			t.Errorf("int32[%d]: got %d want %d", i, arr[i], src[i])
		}
	}
}

func TestValueStruct(t *testing.T) {
	desc := pvdata.FieldDesc{
		Kind:     pvdata.KindStruct,
		TypeCode: pvdata.TypeCodeStruct,
		TypeID:   "test_t",
		Fields: []pvdata.Field{
			{Name: "x", Desc: pvdata.FieldDesc{Kind: pvdata.KindScalar, TypeCode: pvdata.TypeCodeDouble}},
			{Name: "n", Desc: pvdata.FieldDesc{Kind: pvdata.KindScalar, TypeCode: pvdata.TypeCodeInt}},
			{Name: "s", Desc: pvdata.FieldDesc{Kind: pvdata.KindString, TypeCode: pvdata.TypeCodeString}},
		},
	}
	sv := pvdata.StructValue{
		TypeID: "test_t",
		Fields: []pvdata.FieldValue{
			{Name: "x", Value: float64(2.718)},
			{Name: "n", Value: int32(42)},
			{Name: "s", Value: "hello"},
		},
	}
	var buf bytes.Buffer
	if err := pvdata.WriteValue(&buf, desc, sv); err != nil {
		t.Fatal(err)
	}
	got, err := pvdata.ReadValue(&buf, desc)
	if err != nil {
		t.Fatal(err)
	}
	gsv := got.(pvdata.StructValue)
	if gsv.Fields[0].Value.(float64) != sv.Fields[0].Value.(float64) {
		t.Errorf("x field mismatch")
	}
	if gsv.Fields[1].Value.(int32) != sv.Fields[1].Value.(int32) {
		t.Errorf("n field mismatch")
	}
	if gsv.Fields[2].Value.(string) != sv.Fields[2].Value.(string) {
		t.Errorf("s field mismatch")
	}
}

// ---- BitSet ---------------------------------------------------------

func TestBitSet(t *testing.T) {
	var bs pvdata.BitSet
	bs.Set(0)
	bs.Set(3)
	bs.Set(15)
	bs.Set(16)

	var buf bytes.Buffer
	if err := pvdata.WriteBitSet(&buf, bs); err != nil {
		t.Fatal(err)
	}
	got, err := pvdata.ReadBitSet(&buf)
	if err != nil {
		t.Fatal(err)
	}
	for _, idx := range []int{0, 3, 15, 16} {
		if !got.Has(idx) {
			t.Errorf("BitSet missing index %d after round-trip", idx)
		}
	}
	for _, idx := range []int{1, 2, 4, 14, 17} {
		if got.Has(idx) {
			t.Errorf("BitSet has unexpected index %d", idx)
		}
	}
}
