package handshake_test

import (
	"bytes"
	"context"
	"crypto/ecdsa"
	"fmt"
	"net"
	"os"
	"sync"
	"testing"

	"koti.casa/numenor-labs/dsfx/pkg/crypto/identity"
	"koti.casa/numenor-labs/dsfx/pkg/handshake"
)

func TestHandshake(t *testing.T) {
	ctx := context.Background()

	// alice, represented by an ecdsa key pair.
	alice, _ := identity.GenerateSigningKey()
	// bob, also represented by an ecdsa key pair.
	bob, _ := identity.GenerateSigningKey()

	var (
		// the secret that alice should arrive at on her own
		aliceSecret []byte
		// any errors produce by alice
		aliceErr error
		// alice's public key as discovered by bob
		discoveredAlicePublicKey *ecdsa.PublicKey

		// the secret that bob should arrive at on his own
		bobSecret []byte
		// any errors produce by bob
		bobErr error
	)

	// Create a network pipe to simulate a network connection between alice and bob.
	client, server := net.Pipe()
	defer client.Close()
	defer server.Close()

	// Run the handshake in parallel so both sides can proceed concurrently.
	// Since they're talking through the network pipe, the entire process should
	// be simulated as if it were a real network connection.
	var wg sync.WaitGroup
	wg.Add(2)
	go func() {
		aliceSecret, aliceErr = handshake.Handshake(ctx, client, alice, &bob.PublicKey)
		wg.Done()
	}()
	go func() {
		discoveredAlicePublicKey, bobSecret, bobErr = handshake.AcceptHandshake(ctx, server, bob)
		wg.Done()
	}()
	wg.Wait()

	// Neither alice nor bob should have encountered any errors.
	if aliceErr != nil {
		t.Errorf("alice error: %v", aliceErr)
		return
	}
	if bobErr != nil {
		t.Errorf("bob error: %v", bobErr)
		return
	}
	// Both alice and bob should have arrived at a shared secret.
	if aliceSecret == nil || bobSecret == nil {
		t.Errorf("handshake failed: sessions are nil")
		return
	}
	// Alice and bob should have arrived at the SAME shared secret.
	if !bytes.Equal(aliceSecret, bobSecret) {
		t.Errorf("handshake failed: sessions are not equal")
		return
	}
	// Bob should have discovered alice's public key.
	if !alice.PublicKey.Equal(discoveredAlicePublicKey) {
		t.Errorf("handshake failed: discovered public key is not equal to alice's public key")
		return
	}
}

func BenchmarkHandshake(b *testing.B) {
	for b.Loop() {
		if err := runSimulation(); err != nil {
			fmt.Fprint(os.Stderr, err)
		}
	}
}

func runSimulation() error {
	ctx := context.Background()

	// alice, represented by an ecdsa key pair.
	alice, _ := identity.GenerateSigningKey()
	// bob, also represented by an ecdsa key pair.
	bob, _ := identity.GenerateSigningKey()

	var (
		// the secret that alice should arrive at on her own
		// aliceSecret []byte
		// any errors produce by alice
		aliceErr error
		// alice's public key as discovered by bob
		// discoveredAlicePublicKey *ecdsa.PublicKey

		// the secret that bob should arrive at on his own
		// bobSecret []byte
		// any errors produce by bob
		bobErr error
	)

	// Create a network pipe to simulate a network connection between alice and bob.
	client, server := net.Pipe()
	defer client.Close()
	defer server.Close()

	// Run the handshake in parallel so both sides can proceed concurrently.
	// Since they're talking through the network pipe, the entire process should
	// be simulated as if it were a real network connection.
	var wg sync.WaitGroup
	wg.Add(2)
	go func() {
		_, aliceErr = handshake.Handshake(ctx, client, alice, &bob.PublicKey)
		wg.Done()
	}()
	go func() {
		_, _, bobErr = handshake.AcceptHandshake(ctx, server, bob)
		wg.Done()
	}()
	wg.Wait()
	if aliceErr != nil {
		return aliceErr
	}
	if bobErr != nil {
		return bobErr
	}
	return nil
}