package modbus

import (
	"math"
	"testing"

	"github.com/uopi/uopi/internal/datasource"
)

func TestDecodeEncodings(t *testing.T) {
	cases := []struct {
		name  string
		reg   Register
		words []uint16
		want  any
	}{
		{"uint16", Register{Encoding: "uint16"}, []uint16{42}, int64(42)},
		{"int16 neg", Register{Encoding: "int16"}, []uint16{0xFFFF}, int64(-1)},
		{"uint32", Register{Encoding: "uint32"}, []uint16{0x0001, 0x0000}, int64(65536)},
		{"int32 neg", Register{Encoding: "int32"}, []uint16{0xFFFF, 0xFFFF}, int64(-1)},
		{"scaled", Register{Encoding: "int16", Scale: 0.1}, []uint16{235}, 23.5},
		{"float32", Register{Encoding: "float32"}, f32Words(3.5), 3.5},
		{"float64", Register{Encoding: "float64"}, f64Words(2.25), 2.25},
	}
	for _, tc := range cases {
		got, err := tc.reg.decode(tc.words)
		if err != nil {
			t.Errorf("%s: decode error %v", tc.name, err)
			continue
		}
		switch w := tc.want.(type) {
		case int64:
			if got != w {
				t.Errorf("%s: got %v (%T), want %v", tc.name, got, got, w)
			}
		case float64:
			gf, ok := got.(float64)
			if !ok || math.Abs(gf-w) > 1e-6 {
				t.Errorf("%s: got %v (%T), want %v", tc.name, got, got, w)
			}
		}
	}
}

func TestWordOrder(t *testing.T) {
	big := Register{Encoding: "uint32", WordOrder: "big"}
	little := Register{Encoding: "uint32", WordOrder: "little"}
	words := []uint16{0x0001, 0x0002} // big: 0x00010002, little reverses to 0x00020001
	gb, _ := big.decode(words)
	gl, _ := little.decode(words)
	if gb != int64(0x00010002) {
		t.Errorf("big = %v, want %d", gb, 0x00010002)
	}
	if gl != int64(0x00020001) {
		t.Errorf("little = %v, want %d", gl, 0x00020001)
	}
}

func TestEncodeRoundTrip(t *testing.T) {
	for _, enc := range []string{"uint16", "int16", "uint32", "int32", "float32", "float64"} {
		reg := Register{Encoding: enc}
		words, err := reg.encode(123)
		if err != nil {
			t.Fatalf("%s encode: %v", enc, err)
		}
		got, err := reg.decode(words)
		if err != nil {
			t.Fatalf("%s decode: %v", enc, err)
		}
		var f float64
		switch v := got.(type) {
		case int64:
			f = float64(v)
		case float64:
			f = v
		}
		if math.Abs(f-123) > 1e-3 {
			t.Errorf("%s round-trip = %v, want 123", enc, f)
		}
	}
}

func TestDataTypeAndWritable(t *testing.T) {
	if got := (Register{Kind: "coil"}).dataType(); got != datasource.TypeBool {
		t.Errorf("coil type = %v, want bool", got)
	}
	if got := (Register{Encoding: "float32"}).dataType(); got != datasource.TypeFloat64 {
		t.Errorf("float type = %v, want float64", got)
	}
	if got := (Register{Encoding: "uint16", Scale: 0.5}).dataType(); got != datasource.TypeFloat64 {
		t.Errorf("scaled int type = %v, want float64", got)
	}
	if got := (Register{Encoding: "uint16"}).dataType(); got != datasource.TypeInt64 {
		t.Errorf("plain int type = %v, want int64", got)
	}
	if (Register{Kind: "input", Writable: true}).writable() {
		t.Error("input register must be read-only")
	}
	if (Register{Kind: "discrete", Writable: true}).writable() {
		t.Error("discrete input must be read-only")
	}
	if !(Register{Kind: "holding", Writable: true}).writable() {
		t.Error("writable holding should be writable")
	}
}

func f32Words(v float32) []uint16 {
	u := math.Float32bits(v)
	return []uint16{uint16(u >> 16), uint16(u)}
}

func f64Words(v float64) []uint16 {
	u := math.Float64bits(v)
	return []uint16{uint16(u >> 48), uint16(u >> 32), uint16(u >> 16), uint16(u)}
}
