package ca

import (
	"context"
	"net"
	"testing"

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

// ---- encodePut: every DBF branch + error paths -----------------------

func TestEncodePut(t *testing.T) {
	cases := []struct {
		name    string
		dbf     int
		v       any
		wantDBR uint16
	}{
		{"double", proto.DBFDouble, 1.5, proto.DBRDouble},
		{"float", proto.DBFFloat, float32(2.0), proto.DBRDouble},
		{"long", proto.DBFLong, int(3), proto.DBRLong},
		{"short", proto.DBFShort, int16(4), proto.DBRShort},
		{"char", proto.DBFChar, int(5), proto.DBRShort},
		{"enum", proto.DBFEnum, int(1), proto.DBRShort},
		{"string", proto.DBFString, "hi", proto.DBRString},
	}
	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			dbr, payload, err := encodePut(c.dbf, c.v)
			if err != nil {
				t.Fatalf("encodePut: %v", err)
			}
			if dbr != c.wantDBR {
				t.Errorf("dbr = %d, want %d", dbr, c.wantDBR)
			}
			if len(payload) == 0 {
				t.Error("empty payload")
			}
		})
	}

	// Coercion failures propagate.
	if _, _, err := encodePut(proto.DBFDouble, "nope"); err == nil {
		t.Error("double from string: want error")
	}
	if _, _, err := encodePut(proto.DBFLong, "nope"); err == nil {
		t.Error("long from string: want error")
	}
	if _, _, err := encodePut(proto.DBFShort, "nope"); err == nil {
		t.Error("short from string: want error")
	}
	// Unsupported field type.
	if _, _, err := encodePut(9999, 1); err == nil {
		t.Error("unsupported DBF: want error")
	}
}

// ---- NewClient: error path + default name/host -----------------------

func TestNewClientNoAddrs(t *testing.T) {
	if _, err := NewClient(context.Background(), Config{AutoAddrList: false}); err == nil {
		t.Error("NewClient with no addresses: want error")
	}
}

func TestNewClientDefaultsNameHost(t *testing.T) {
	// AddrList present but ClientName/HostName empty → default branches run.
	cli, err := NewClient(context.Background(), Config{
		AddrList:     []string{"127.0.0.1:5064"},
		AutoAddrList: false,
	})
	if err != nil {
		t.Fatalf("NewClient: %v", err)
	}
	defer cli.Close()
	if cli.cfg.ClientName == "" {
		t.Error("ClientName should be defaulted")
	}
}

// ---- EncodeSearchReply with an explicit server IP --------------------

func TestEncodeSearchReplyExplicitIP(t *testing.T) {
	pkt := EncodeSearchReply(7, net.ParseIP("10.1.2.3"), 5064)
	h, _, err := proto.DecodeHeader(newBytesReader(pkt))
	if err != nil {
		t.Fatalf("DecodeHeader: %v", err)
	}
	if h.Command != proto.CmdSearch {
		t.Errorf("command = %d, want CmdSearch", h.Command)
	}
}

// ---- parseReply robustness -------------------------------------------

func TestParseReplyTruncated(t *testing.T) {
	se := &searchEngine{waiters: make(map[uint32]*searchWaiter)}
	src := &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 5064}
	// Must not panic on a too-short datagram.
	se.parseReply([]byte{0x00, 0x01}, src)
}

func TestParseReplyUnknownSearchID(t *testing.T) {
	se := &searchEngine{waiters: make(map[uint32]*searchWaiter)}
	src := &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 5064}
	// Valid reply but no waiter is registered for this search ID → dropped.
	se.parseReply(EncodeSearchReply(12345, nil, 5064), src)
}
