//go:build linux

package transport

import (
	"net"
	"reflect"
	"testing"

	"omnisocketgo/cmd/internal/latencylog"
	"omnisocketgo/cmd/internal/protocol"
)

func TestLinuxTimestampingRecordsKernelEvents(t *testing.T) {
	tests := []struct {
		name string
		msg  protocol.Message
	}{
		{
			name: "text",
			msg: protocol.Message{
				Type: protocol.MessageTypeText,
				ID:   41,
				From: "peer-a",
				To:   "peer-b",
				Body: []byte("hello over tcp"),
			},
		},
		{
			name: "file",
			msg: protocol.Message{
				Type:     protocol.MessageTypeFile,
				ID:       42,
				From:     "peer-a",
				To:       "peer-b",
				FileName: "payload.bin",
				Body:     []byte{0x00, 0x01, 0x02, 0xff},
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			clientConn, serverConn := newTCPPair(t)

			senderLogger := &recordingLogger{}
			receiverLogger := &recordingLogger{}
			sender, err := NewTCPConn(
				clientConn,
				WithLogger(senderLogger, latencylog.NodeRolePeer, "peer-a"),
			)
			if err != nil {
				t.Fatalf("NewTCPConn(sender) error = %v", err)
			}
			receiver, err := NewTCPConn(
				serverConn,
				WithLogger(receiverLogger, latencylog.NodeRolePeer, "peer-b"),
			)
			if err != nil {
				t.Fatalf("NewTCPConn(receiver) error = %v", err)
			}
			t.Cleanup(func() {
				_ = sender.Close()
				_ = receiver.Close()
			})

			sendErr := make(chan error, 1)
			go func() {
				sendErr <- sender.Send(tt.msg)
			}()

			got, err := receiver.Receive()
			if err != nil {
				t.Fatalf("Receive() error = %v", err)
			}
			if err := <-sendErr; err != nil {
				t.Fatalf("Send() error = %v", err)
			}
			if !reflect.DeepEqual(got, tt.msg) {
				t.Fatalf("message mismatch: got %+v want %+v", got, tt.msg)
			}

			assertHasEvent(t, senderLogger.Events(), latencylog.EventATXSched, tt.msg.ID)
			assertHasEvent(t, senderLogger.Events(), latencylog.EventATXSoftware, tt.msg.ID)
			assertHasEvent(t, receiverLogger.Events(), latencylog.EventBRXSoftware, tt.msg.ID)
		})
	}
}

func newTCPPair(t *testing.T) (net.Conn, net.Conn) {
	t.Helper()

	listener, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		t.Fatalf("net.Listen() error = %v", err)
	}

	type acceptResult struct {
		conn net.Conn
		err  error
	}

	accepted := make(chan acceptResult, 1)
	go func() {
		conn, acceptErr := listener.Accept()
		accepted <- acceptResult{conn: conn, err: acceptErr}
	}()

	clientConn, err := net.Dial("tcp", listener.Addr().String())
	if err != nil {
		_ = listener.Close()
		t.Fatalf("net.Dial() error = %v", err)
	}

	result := <-accepted
	if err := listener.Close(); err != nil {
		t.Fatalf("listener.Close() error = %v", err)
	}
	if result.err != nil {
		_ = clientConn.Close()
		t.Fatalf("listener.Accept() error = %v", result.err)
	}

	return clientConn, result.conn
}

func assertHasEvent(t *testing.T, events []latencylog.Event, wantEvent string, wantMessageID uint64) {
	t.Helper()

	for _, event := range events {
		if event.Event == wantEvent && event.MessageID == wantMessageID {
			if event.TsUnixNano <= 0 {
				t.Fatalf("event %s timestamp must be positive: %+v", wantEvent, event)
			}
			return
		}
	}

	t.Fatalf("missing event %s for message %d in %+v", wantEvent, wantMessageID, events)
}
