package client_test

import (
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/tls"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/hex"
	"encoding/pem"
	"math/big"
	"net"
	"sync"
	"testing"
	"time"

	"github.com/enmanuel/unibus/pkg/client"
	"github.com/enmanuel/unibus/pkg/frame"
	"github.com/enmanuel/unibus/pkg/membership"
	"github.com/enmanuel/unibus/pkg/room"
)

// genTestCA mints a throwaway self-signed CA plus a server certificate (SAN
// 127.0.0.1 / localhost) signed by it, mirroring deploy/tls/generate-certs.sh
// without shelling out to openssl. It returns the server's *tls.Config (cert it
// presents) and the CA pool a client must trust to complete the handshake.
func genTestCA(t *testing.T) (server *tls.Config, caPool *x509.CertPool) {
	t.Helper()

	// --- CA ---
	caKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		t.Fatalf("ca key: %v", err)
	}
	caTmpl := &x509.Certificate{
		SerialNumber:          big.NewInt(1),
		Subject:               pkix.Name{CommonName: "unibus-test-ca"},
		NotBefore:             time.Now().Add(-time.Hour),
		NotAfter:              time.Now().Add(24 * time.Hour),
		IsCA:                  true,
		KeyUsage:              x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature,
		BasicConstraintsValid: true,
	}
	caDER, err := x509.CreateCertificate(rand.Reader, caTmpl, caTmpl, &caKey.PublicKey, caKey)
	if err != nil {
		t.Fatalf("ca cert: %v", err)
	}
	caCert, err := x509.ParseCertificate(caDER)
	if err != nil {
		t.Fatalf("parse ca: %v", err)
	}

	// --- server cert signed by the CA ---
	srvKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		t.Fatalf("server key: %v", err)
	}
	srvTmpl := &x509.Certificate{
		SerialNumber: big.NewInt(2),
		Subject:      pkix.Name{CommonName: "unibus-test-server"},
		NotBefore:    time.Now().Add(-time.Hour),
		NotAfter:     time.Now().Add(24 * time.Hour),
		KeyUsage:     x509.KeyUsageDigitalSignature,
		ExtKeyUsage:  []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
		DNSNames:     []string{"localhost"},
		IPAddresses:  []net.IP{net.IPv4(127, 0, 0, 1)},
	}
	srvDER, err := x509.CreateCertificate(rand.Reader, srvTmpl, caCert, &srvKey.PublicKey, caKey)
	if err != nil {
		t.Fatalf("server cert: %v", err)
	}

	srvCertPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: srvDER})
	srvKeyDER, err := x509.MarshalECPrivateKey(srvKey)
	if err != nil {
		t.Fatalf("marshal server key: %v", err)
	}
	srvKeyPEM := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: srvKeyDER})
	srvPair, err := tls.X509KeyPair(srvCertPEM, srvKeyPEM)
	if err != nil {
		t.Fatalf("server keypair: %v", err)
	}

	pool := x509.NewCertPool()
	pool.AddCert(caCert)
	return &tls.Config{Certificates: []tls.Certificate{srvPair}, MinVersion: tls.VersionTLS12}, pool
}

// TestNatsTLS validates the TLS data plane: a client trusting the bus CA
// completes the handshake and uses the bus (golden); a client that does NOT
// trust the CA fails the handshake (error path).
func TestNatsTLS(t *testing.T) {
	serverTLS, caPool := genTestCA(t)
	h := bootHarness(t, membership.AuthOff, false, serverTLS)
	waitHealth(t, h.ctrlURL)

	// Golden: client pinning the CA connects over TLS and operates.
	clientTLS := &tls.Config{RootCAs: caPool, MinVersion: tls.VersionTLS12}
	a, err := client.NewWithOptions(h.natsURL, h.ctrlURL, mustIdentity(t), client.Options{TLS: clientTLS})
	if err != nil {
		t.Fatalf("client trusting the CA should complete the TLS handshake: %v", err)
	}
	defer a.Close()
	if _, err := a.CreateRoom("room.tls", room.ModeNATS); err != nil {
		t.Fatalf("TLS client should operate on the bus: %v", err)
	}

	// Error path: a client that does not trust the CA fails the handshake. Use an
	// empty pool (system roots would also reject this private CA, but an empty
	// pool makes the intent explicit and avoids depending on the host's roots).
	badTLS := &tls.Config{RootCAs: x509.NewCertPool(), MinVersion: tls.VersionTLS12}
	if _, err := client.NewWithOptions(h.natsURL, h.ctrlURL, mustIdentity(t), client.Options{TLS: badTLS}); err == nil {
		t.Fatalf("client without the CA must fail the TLS handshake")
	}
}

// TestSecureBusEndToEnd is the headline golden of issue 0001: with ALL three
// layers active at once — control-plane request signing (enforce), NATS nkey
// auth, and TLS — two registered peers run an encrypted room end to end. A
// creates a Matrix-policy room, invites B, A publishes and B decrypts. This
// proves the layers compose: signed HTTP control plane + authenticated,
// encrypted data plane + E2E room content.
func TestSecureBusEndToEnd(t *testing.T) {
	serverTLS, caPool := genTestCA(t)
	h := bootHarness(t, membership.AuthEnforce, true, serverTLS)
	waitHealth(t, h.ctrlURL)

	clientTLS := &tls.Config{RootCAs: caPool, MinVersion: tls.VersionTLS12}
	secure := func(t *testing.T, handle string) (*client.Client, membership.AuthMode) {
		id := mustIdentity(t)
		if err := h.store.AddUser(hex.EncodeToString(id.SignPub), handle, membership.RoleMember); err != nil {
			t.Fatalf("register %s: %v", handle, err)
		}
		c, err := client.NewWithOptions(h.natsURL, h.ctrlURL, id, client.Options{UseNkey: true, TLS: clientTLS})
		if err != nil {
			t.Fatalf("connect %s securely: %v", handle, err)
		}
		return c, 0
	}

	a, _ := secure(t, "alice")
	defer a.Close()
	b, _ := secure(t, "bob")
	defer b.Close()

	roomID, err := a.CreateRoom("room.secure", room.ModeMatrix)
	if err != nil {
		t.Fatalf("A create encrypted room over secure bus: %v", err)
	}
	if err := a.Invite(roomID, b.Endpoint()); err != nil {
		t.Fatalf("A invite B: %v", err)
	}
	if err := b.Join(roomID); err != nil {
		t.Fatalf("B join: %v", err)
	}

	var mu sync.Mutex
	var got []string
	sub, err := b.Subscribe(roomID, func(_ frame.Frame, plaintext []byte) {
		mu.Lock()
		got = append(got, string(plaintext))
		mu.Unlock()
	})
	if err != nil {
		t.Fatalf("B subscribe: %v", err)
	}
	defer sub.Unsubscribe()
	time.Sleep(150 * time.Millisecond)

	const msg = "mensaje sobre bus seguro (auth+TLS+E2E)"
	if err := a.Publish(roomID, []byte(msg)); err != nil {
		t.Fatalf("A publish: %v", err)
	}
	if !waitFor(&mu, &got, func(rs []string) bool {
		for _, r := range rs {
			if r == msg {
				return true
			}
		}
		return false
	}, 2*time.Second) {
		t.Fatalf("B did not receive/decrypt the message over the secured bus; got %v", snapshot(&mu, &got))
	}
}
