package transport

import (
	"testing"
	"time"

	pb "github.com/antitbone/dwarfctl/proto"
	"google.golang.org/protobuf/proto"
)

// TestEnvelopeRoundTrip verifies that a WsPacket envelope survives a
// marshal → unmarshal round-trip with all fields intact. This is the core
// guarantee for every command sent to the telescope.
func TestEnvelopeRoundTrip(t *testing.T) {
	original := &pb.WsPacket{
		MajorVersion: WsMajorVersion,
		MinorVersion: WsMinorVersion,
		DeviceId:     1,
		ModuleId:     6, // MODULE_MOTOR
		Cmd:          14006,
		Type:         uint32(MsgRequest),
		Data:         []byte{0x08, 0x2a}, // some inner proto bytes
		ClientId:     "test-client-007",
	}

	raw, err := proto.Marshal(original)
	if err != nil {
		t.Fatalf("marshal WsPacket: %v", err)
	}
	if len(raw) == 0 {
		t.Fatal("marshaled envelope is empty")
	}

	decoded := &pb.WsPacket{}
	if err := proto.Unmarshal(raw, decoded); err != nil {
		t.Fatalf("unmarshal WsPacket: %v", err)
	}

	if decoded.GetMajorVersion() != original.GetMajorVersion() {
		t.Errorf("major_version: got %d, want %d", decoded.GetMajorVersion(), original.GetMajorVersion())
	}
	if decoded.GetMinorVersion() != original.GetMinorVersion() {
		t.Errorf("minor_version: got %d, want %d", decoded.GetMinorVersion(), original.GetMinorVersion())
	}
	if decoded.GetDeviceId() != original.GetDeviceId() {
		t.Errorf("device_id: got %d, want %d", decoded.GetDeviceId(), original.GetDeviceId())
	}
	if decoded.GetModuleId() != original.GetModuleId() {
		t.Errorf("module_id: got %d, want %d", decoded.GetModuleId(), original.GetModuleId())
	}
	if decoded.GetCmd() != original.GetCmd() {
		t.Errorf("cmd: got %d, want %d", decoded.GetCmd(), original.GetCmd())
	}
	if decoded.GetType() != original.GetType() {
		t.Errorf("type: got %d, want %d", decoded.GetType(), original.GetType())
	}
	if decoded.GetClientId() != original.GetClientId() {
		t.Errorf("client_id: got %q, want %q", decoded.GetClientId(), original.GetClientId())
	}
	if string(decoded.GetData()) != string(original.GetData()) {
		t.Errorf("data: got %x, want %x", decoded.GetData(), original.GetData())
	}
}

// TestEnvelopeWithInnerProto verifies that inner proto messages (the Data
// field) encode and decode correctly within the envelope.
func TestEnvelopeWithInnerProto(t *testing.T) {
	inner := &pb.ReqMotorServiceJoystick{
		VectorAngle:  90.5,
		VectorLength: 0.75,
	}
	innerBytes, err := proto.Marshal(inner)
	if err != nil {
		t.Fatalf("marshal inner: %v", err)
	}

	envelope := &pb.WsPacket{
		MajorVersion: WsMajorVersion,
		MinorVersion: WsMinorVersion,
		DeviceId:     1,
		ModuleId:     6,
		Cmd:          14006,
		Type:         uint32(MsgRequest),
		Data:         innerBytes,
		ClientId:     "dwarfctl-test",
	}
	raw, err := proto.Marshal(envelope)
	if err != nil {
		t.Fatalf("marshal envelope: %v", err)
	}

	decoded := &pb.WsPacket{}
	if err := proto.Unmarshal(raw, decoded); err != nil {
		t.Fatalf("unmarshal envelope: %v", err)
	}

	// Decode the inner message
	innerDecoded := &pb.ReqMotorServiceJoystick{}
	if err := proto.Unmarshal(decoded.GetData(), innerDecoded); err != nil {
		t.Fatalf("unmarshal inner: %v", err)
	}

	if innerDecoded.GetVectorAngle() != 90.5 {
		t.Errorf("vector_angle: got %f, want 90.5", innerDecoded.GetVectorAngle())
	}
	if innerDecoded.GetVectorLength() != 0.75 {
		t.Errorf("vector_length: got %f, want 0.75", innerDecoded.GetVectorLength())
	}
}

// TestNewClientDefaults verifies that NewClient stores the client_id and
// device_id and initializes the pending map.
func TestNewClientDefaults(t *testing.T) {
	c := NewClient("my-client", 2)
	if c.clientID != "my-client" {
		t.Errorf("clientID: got %q, want %q", c.clientID, "my-client")
	}
	if c.deviceID != 2 {
		t.Errorf("deviceID: got %d, want 2", c.deviceID)
	}
	if c.pending == nil {
		t.Error("pending map is nil")
	}
	if c.done == nil {
		t.Error("done channel is nil")
	}
	if c.IsConnected() {
		t.Error("should not be connected before Connect()")
	}
}

// TestMsgTypeConstants verifies the wire-format type values match the
// original enum (request=0, response=1, notification=2, reply=3).
func TestMsgTypeConstants(t *testing.T) {
	cases := map[MsgType]uint32{
		MsgRequest:      0,
		MsgResponse:     1,
		MsgNotification: 2,
		MsgReply:        3,
	}
	for mt, expected := range cases {
		if uint32(mt) != expected {
			t.Errorf("MsgType %d: got value %d, want %d", mt, uint32(mt), expected)
		}
	}
}

// TestSubscribeNotifications verifies the notification fan-out works:
// multiple subscribers all receive the same packet.
func TestSubscribeNotifications(t *testing.T) {
	c := NewClient("test", 1)
	ch1 := c.SubscribeNotifications()
	ch2 := c.SubscribeNotifications()

	pkt := &pb.WsPacket{Cmd: 15243, Type: uint32(MsgNotification)}
	c.dispatchNotification(pkt)

	// Both channels should receive a copy
	select {
	case got := <-ch1:
		if got.GetCmd() != 15243 {
			t.Errorf("ch1 cmd: got %d, want 15243", got.GetCmd())
		}
	case <-time.After(100 * time.Millisecond):
		t.Fatal("ch1 did not receive notification")
	}

	select {
	case got := <-ch2:
		if got.GetCmd() != 15243 {
			t.Errorf("ch2 cmd: got %d, want 15243", got.GetCmd())
		}
	case <-time.After(100 * time.Millisecond):
		t.Fatal("ch2 did not receive notification")
	}
}

// TestEmptyDataEnvelope verifies that commands with no payload (empty Data)
// survive a round-trip.
func TestEmptyDataEnvelope(t *testing.T) {
	original := &pb.WsPacket{
		MajorVersion: WsMajorVersion,
		MinorVersion: WsMinorVersion,
		DeviceId:     1,
		ModuleId:     3, // MODULE_ASTRO
		Cmd:          11000,
		Type:         uint32(MsgRequest),
		Data:         nil,
		ClientId:     "dwarfctl",
	}
	raw, err := proto.Marshal(original)
	if err != nil {
		t.Fatalf("marshal: %v", err)
	}

	decoded := &pb.WsPacket{}
	if err := proto.Unmarshal(raw, decoded); err != nil {
		t.Fatalf("unmarshal: %v", err)
	}
	if decoded.GetCmd() != 11000 {
		t.Errorf("cmd: got %d, want 11000", decoded.GetCmd())
	}
	if len(decoded.GetData()) != 0 {
		t.Errorf("data should be empty, got %d bytes", len(decoded.GetData()))
	}
}

// TestWsPort verifies the port constant matches the reverse-engineered value.
func TestWsPort(t *testing.T) {
	if WsPort != 9900 {
		t.Errorf("WsPort: got %d, want 9900", WsPort)
	}
}

// TestVersionConstants verifies the protocol version constants.
func TestVersionConstants(t *testing.T) {
	if WsMajorVersion != 2 {
		t.Errorf("WsMajorVersion: got %d, want 2", WsMajorVersion)
	}
	if WsMinorVersion != 3 {
		t.Errorf("WsMinorVersion: got %d, want 3", WsMinorVersion)
	}
}
