package internet

import (
	"math/rand"
	"net/netip"
	"testing"

	"github.com/soypat/lneto/tcp"
)

func TestBasicStack(t *testing.T) {
	rng := rand.New(rand.NewSource(1))
	var sbCl, sbSv StackBasic
	var connCl, connSv TCPConn
	setupClientServer(t, rng, &sbCl, &sbSv, &connCl, &connSv)
	var buf [2048]byte
	nextToSend := &sbCl
	nextToRecv := &sbSv
	exchangeAndExpectStates := func(clState, svState tcp.State) {
		t.Helper()
		expectExchange(t, nextToSend, nextToRecv, buf[:])
		gotCl := connCl.State()
		gotSv := connSv.State()
		if gotCl != clState {
			t.Errorf("want client state %s, got %s", clState, gotCl)
		}
		if gotSv != svState {
			t.Errorf("want server state %s, got %s", svState, gotSv)
		}
		nextToSend, nextToRecv = nextToRecv, nextToSend
	}
	exchangeAndExpectStates(tcp.StateSynSent, tcp.StateSynRcvd)         // Client sends over first SYN and server receives it.
	exchangeAndExpectStates(tcp.StateEstablished, tcp.StateSynRcvd)     // server sends back SYNACK, establishing connection on client side.
	exchangeAndExpectStates(tcp.StateEstablished, tcp.StateEstablished) // Client sends ACK, establishing connection in full.
}

func TestBasicStack2(t *testing.T) {
	rng := rand.New(rand.NewSource(1))
	var sbCl, sbSv StackBasic
	var connCl, connSv TCPConn
	setupClientServerEstablished(t, rng, &sbCl, &sbSv, &connCl, &connSv)

}

func expectExchange(t *testing.T, from, to *StackBasic, buf []byte) {
	t.Helper()
	n, err := from.Handle(buf)
	if err != nil {
		t.Error("expectExchange:Handle:", err)
	} else if n == 0 {
		t.Error("expected data exchange")
		return
	}
	err = to.Recv(buf[:n])
	if err != nil {
		t.Error("expectExchange:Recv:", err)
	}
}

func setupClientServerEstablished(t *testing.T, rng *rand.Rand, client, server *StackBasic, connClient, connServer *TCPConn) {
	t.Helper()
	setupClientServer(t, rng, client, server, connClient, connServer)
	var buf [2048]byte
	nextToSend := client
	nextToRecv := server
	exchangeAndExpectStates := func(clState, svState tcp.State) {
		t.Helper()
		expectExchange(t, nextToSend, nextToRecv, buf[:])
		gotCl := connClient.State()
		gotSv := connServer.State()
		if gotCl != clState {
			t.Errorf("want client state %s, got %s", clState, gotCl)
		}
		if gotSv != svState {
			t.Errorf("want server state %s, got %s", svState, gotSv)
		}
		nextToSend, nextToRecv = nextToRecv, nextToSend
	}
	exchangeAndExpectStates(tcp.StateSynSent, tcp.StateSynRcvd)         // Client sends over first SYN and server receives it.
	exchangeAndExpectStates(tcp.StateEstablished, tcp.StateSynRcvd)     // server sends back SYNACK, establishing connection on client side.
	exchangeAndExpectStates(tcp.StateEstablished, tcp.StateEstablished) // Client sends ACK, establishing connection in full.
	if t.Failed() {
		t.Error("establishment failed")
		t.FailNow()
	}
}

func setupClientServer(t *testing.T, rng *rand.Rand, client, server *StackBasic, connClient, connServer *TCPConn) {
	bufsize := 2048
	// Ensure buffer sizes are OK with reused buffers.
	svip := netip.AddrPortFrom(netip.AddrFrom4([4]byte{192, 168, 1, 0}), 80)
	clip := netip.AddrPortFrom(netip.AddrFrom4([4]byte{192, 168, 1, 1}), 1337)
	server.SetAddr(svip.Addr())
	client.SetAddr(clip.Addr())

	err := connServer.Configure(&TCPConnConfig{
		RxBuf:             make([]byte, bufsize),
		TxBuf:             make([]byte, bufsize),
		TxPacketQueueSize: 3,
		Logger:            nil,
	})
	if err != nil {
		t.Fatal(err)
	}
	err = connClient.Configure(&TCPConnConfig{
		RxBuf:             make([]byte, bufsize),
		TxBuf:             make([]byte, bufsize),
		TxPacketQueueSize: 3,
		Logger:            nil,
	})
	if err != nil {
		t.Fatal(err)
	}

	err = connServer.OpenListen(svip.Port(), 200)
	if err != nil {
		t.Fatal(err)
	}
	err = connClient.OpenActive(svip, clip.Port(), 100)
	if err != nil {
		t.Fatal(err)
	}

	err = server.RegisterTCPConn(connServer)
	if err != nil {
		t.Fatal(err)
	}
	err = client.RegisterTCPConn(connClient)
	if err != nil {
		t.Fatal(err)
	}
}
