package mtls

import (
	"bytes"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"path/filepath"
	"testing"
)

// newTestCA creates a CA in the given temporary directory.
func newTestCA(t *testing.T, dir string) *CA {
	t.Helper()
	ca, err := NewCA(CAConfig{
		KeyPath:  filepath.Join(dir, "ca.key"),
		CertPath: filepath.Join(dir, "ca.crt"),
	})
	if err != nil {
		t.Fatalf("NewCA: %v", err)
	}
	return ca
}

// newTestCSR generates an ECDSA P-256 key and a CSR PEM for testing.
func newTestCSR(t *testing.T) ([]byte, *ecdsa.PrivateKey) {
	t.Helper()
	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		t.Fatalf("generate CSR key: %v", err)
	}
	tmpl := &x509.CertificateRequest{
		Subject: pkix.Name{CommonName: "ignored-by-signer"},
	}
	csrDER, err := x509.CreateCertificateRequest(rand.Reader, tmpl, key)
	if err != nil {
		t.Fatalf("CreateCertificateRequest: %v", err)
	}
	csrPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrDER})
	return csrPEM, key
}

func TestCA_GenerateAndLoad(t *testing.T) {
	dir := t.TempDir()

	// First call: generate a new CA.
	ca1 := newTestCA(t, dir)
	if len(ca1.CAPEM()) == 0 {
		t.Fatal("CAPEM returned empty")
	}

	// Second call with same paths: must load from disk without error.
	ca2, err := NewCA(CAConfig{
		KeyPath:  filepath.Join(dir, "ca.key"),
		CertPath: filepath.Join(dir, "ca.crt"),
	})
	if err != nil {
		t.Fatalf("NewCA reload: %v", err)
	}

	// The CA certificates must be byte-identical.
	if !bytes.Equal(ca1.CAPEM(), ca2.CAPEM()) {
		t.Fatal("reloaded CA cert differs from generated CA cert")
	}
}

func TestCA_SignCSR_CNOverride(t *testing.T) {
	dir := t.TempDir()
	ca := newTestCA(t, dir)

	csrPEM, _ := newTestCSR(t)
	nodeUUID := "node-11111111-2222-3333-4444-555555555555"

	certPEM, err := ca.SignCSR(csrPEM, nodeUUID)
	if err != nil {
		t.Fatalf("SignCSR: %v", err)
	}

	block, _ := pem.Decode(certPEM)
	if block == nil {
		t.Fatal("SignCSR returned empty PEM")
	}
	cert, err := x509.ParseCertificate(block.Bytes)
	if err != nil {
		t.Fatalf("ParseCertificate: %v", err)
	}

	// CN must match nodeUUID regardless of what the CSR contained.
	if cert.Subject.CommonName != nodeUUID {
		t.Errorf("CN = %q; want %q", cert.Subject.CommonName, nodeUUID)
	}
}

func TestCA_SignCSR_VerifiesAgainstCAPool(t *testing.T) {
	dir := t.TempDir()
	ca := newTestCA(t, dir)

	csrPEM, _ := newTestCSR(t)
	certPEM, err := ca.SignCSR(csrPEM, "node-test")
	if err != nil {
		t.Fatalf("SignCSR: %v", err)
	}

	block, _ := pem.Decode(certPEM)
	cert, err := x509.ParseCertificate(block.Bytes)
	if err != nil {
		t.Fatalf("ParseCertificate: %v", err)
	}

	// Build a pool with only our CA and verify the issued cert against it.
	pool := x509.NewCertPool()
	pool.AddCert(ca.CACert())

	_, err = cert.Verify(x509.VerifyOptions{
		Roots:     pool,
		KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
	})
	if err != nil {
		t.Errorf("cert.Verify: %v", err)
	}
}

func TestCA_SignCSR_RejectsInvalidPEM(t *testing.T) {
	dir := t.TempDir()
	ca := newTestCA(t, dir)

	_, err := ca.SignCSR([]byte("not-a-pem"), "any-uuid")
	if err == nil {
		t.Fatal("expected error for invalid CSR PEM, got nil")
	}
}
