//go:build linux

package peer

import (
	"net"
	"strings"
	"sync"
	"testing"

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

func TestClientsExchangeMessagesWithLinuxTimestamps(t *testing.T) {
	hub := server.NewHub()
	serverAddr, cleanup := startRealHubServer(t, hub)
	defer cleanup()

	peerALogger := &recordingLogger{}
	peerA, err := Dial(serverAddr, "peer-a", WithLogger(peerALogger))
	if err != nil {
		t.Fatalf("Dial(peer-a) error = %v", err)
	}
	defer func() { _ = peerA.Close() }()

	peerBLogger := &recordingLogger{}
	peerB, err := Dial(serverAddr, "peer-b", WithLogger(peerBLogger))
	if err != nil {
		t.Fatalf("Dial(peer-b) error = %v", err)
	}
	defer func() { _ = peerB.Close() }()

	inboxDir := t.TempDir()

	waitFor(t, func() bool { return hub.HasPeer("peer-a") && hub.HasPeer("peer-b") }, "both peers to be registered")

	if err := peerA.SendText("peer-b", "hello"); err != nil {
		t.Fatalf("SendText() error = %v", err)
	}
	textMsg, err := peerB.Receive()
	if err != nil {
		t.Fatalf("peerB.Receive(text) error = %v", err)
	}
	if _, err := peerB.PersistMessage(textMsg, inboxDir); err != nil {
		t.Fatalf("peerB.PersistMessage(text) error = %v", err)
	}

	if err := peerA.SendFile("peer-b", "payload.bin", []byte{0x01, 0x02, 0x03}); err != nil {
		t.Fatalf("SendFile() error = %v", err)
	}
	fileMsg, err := peerB.Receive()
	if err != nil {
		t.Fatalf("peerB.Receive(file) error = %v", err)
	}
	if _, err := peerB.PersistMessage(fileMsg, inboxDir); err != nil {
		t.Fatalf("peerB.PersistMessage(file) error = %v", err)
	}

	waitFor(t, func() bool { return hasMessageEvents(peerALogger.Events(), 1, latencylog.EventAAppPrepBegin, latencylog.EventATXSched, latencylog.EventATXSoftware) }, "peer-a text kernel timestamps")
	waitFor(t, func() bool { return hasMessageEvents(peerALogger.Events(), 2, latencylog.EventAAppPrepBegin, latencylog.EventATXSched, latencylog.EventATXSoftware) }, "peer-a file kernel timestamps")
	waitFor(t, func() bool { return hasMessageEvents(peerBLogger.Events(), 1, latencylog.EventBRXSoftware, latencylog.EventBAppRecv, latencylog.EventBPersistBegin, latencylog.EventBPersistEnd) }, "peer-b text receive timestamps")
	waitFor(t, func() bool { return hasMessageEvents(peerBLogger.Events(), 2, latencylog.EventBRXSoftware, latencylog.EventBAppRecv, latencylog.EventBPersistBegin, latencylog.EventBPersistEnd) }, "peer-b file receive timestamps")
}

func startRealHubServer(t *testing.T, hub *server.Hub) (string, func()) {
	t.Helper()

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

	var (
		wg      sync.WaitGroup
		stop    = make(chan struct{})
		errOnce sync.Once
	)

	wg.Add(1)
	go func() {
		defer wg.Done()
		for {
			conn, acceptErr := listener.Accept()
			if acceptErr != nil {
				select {
				case <-stop:
					return
				default:
				}
				if strings.Contains(acceptErr.Error(), "closed") {
					return
				}
				t.Errorf("listener.Accept() error = %v", acceptErr)
				return
			}

			wg.Add(1)
			go func(rawConn net.Conn) {
				defer wg.Done()
				if serveErr := hub.ServeConn(rawConn); serveErr != nil && !isExpectedHubServeExit(serveErr) {
					errOnce.Do(func() {
						t.Logf("hub.ServeConn() ended with %v", serveErr)
					})
				}
			}(conn)
		}
	}()

	cleanup := func() {
		close(stop)
		_ = listener.Close()
		wg.Wait()
	}

	return listener.Addr().String(), cleanup
}

func hasMessageEvents(events []latencylog.Event, messageID uint64, wantEvents ...string) bool {
	seen := make(map[string]bool, len(wantEvents))
	for _, event := range events {
		if event.MessageID != messageID {
			continue
		}
		if event.TsUnixNano <= 0 {
			return false
		}
		seen[event.Event] = true
	}

	for _, wantEvent := range wantEvents {
		if !seen[wantEvent] {
			return false
		}
	}

	return true
}

func isExpectedHubServeExit(err error) bool {
	if err == nil {
		return true
	}

	message := err.Error()
	return strings.Contains(message, "closed") || strings.Contains(message, "protocol: read frame: EOF")
}

func TestLinuxTimestampedReceivePreservesBusinessMessageShape(t *testing.T) {
	hub := server.NewHub()
	serverAddr, cleanup := startRealHubServer(t, hub)
	defer cleanup()

	peerA, err := Dial(serverAddr, "peer-a")
	if err != nil {
		t.Fatalf("Dial(peer-a) error = %v", err)
	}
	defer func() { _ = peerA.Close() }()

	peerB, err := Dial(serverAddr, "peer-b")
	if err != nil {
		t.Fatalf("Dial(peer-b) error = %v", err)
	}
	defer func() { _ = peerB.Close() }()

	waitFor(t, func() bool { return hub.HasPeer("peer-a") && hub.HasPeer("peer-b") }, "both peers to be registered")

	want := protocol.Message{
		Type:     protocol.MessageTypeFile,
		ID:       1,
		From:     "peer-a",
		To:       "peer-b",
		FileName: "payload.bin",
		Body:     []byte{0xde, 0xad, 0xbe, 0xef},
	}
	if err := peerA.SendFile(want.To, want.FileName, want.Body); err != nil {
		t.Fatalf("SendFile() error = %v", err)
	}

	got, err := peerB.Receive()
	if err != nil {
		t.Fatalf("peerB.Receive() error = %v", err)
	}
	if got.Type != want.Type || got.ID != want.ID || got.From != want.From || got.To != want.To || got.FileName != want.FileName || string(got.Body) != string(want.Body) {
		t.Fatalf("received message mismatch: got %+v want %+v", got, want)
	}
}
