package signer_test

// Behavior-equivalence test suite for the Signer abstraction.
//
// Phase 2's exit criteria assert that existing tests in the local issuer
// pass after the refactor. That's necessary but not sufficient: existing
// tests cover specific scenarios and may not catch a subtle byte-level
// divergence (e.g., the wrapped Signer marshaling the public key in a
// different DER ordering, or producing a slightly different signature
// padding). This file is the explicit guard against that class of
// regression.
//
// Three signing surfaces are exercised, mirroring the four call sites in
// internal/connector/issuer/local/local.go:
//   - leaf certificate signing       (mirrors local.go::generateCertificate / line ~613)
//   - CRL signing                    (mirrors local.go::GenerateCRL / line ~849)
//   - OCSP response signing          (mirrors local.go::SignOCSPResponse / line ~887)
//   The CA-bootstrap call (line ~482) is implicitly covered by leaf
//   signing — it's the same x509.CreateCertificate API.
//
// For each surface, two signatures are compared:
//   - RSA-2048 / SHA-256: byte-strict equality (PKCS#1 v1.5 is
//     deterministic given key + digest, so wrapped vs. raw produces
//     identical full DER bytes).
//   - ECDSA-P256 / SHA-256: structural equality (ECDSA uses random k
//     per signature, so signature bytes differ; TBSCertificate /
//     TBSCertificateList / TBSResponseData bytes — everything signed —
//     must be byte-equal across raw and wrapped).
//
// A negative test (TestEquivalence_Sentinel) proves the equivalence
// checker would actually catch a regression — without it, a vacuously-
// passing assertion would let real divergence through.

import (
	"bytes"
	"crypto"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha256"
	"crypto/x509"
	"crypto/x509/pkix"
	"math/big"
	"testing"
	"time"

	"golang.org/x/crypto/ocsp"

	"github.com/certctl-io/certctl/internal/crypto/signer"
)

// fixedTemplate returns an x509 cert template with deterministic fields
// (no time.Now, no random serial) so two calls to CreateCertificate
// produce TBSCertificate bytes that are byte-equal modulo the signature.
func fixedTemplate(t *testing.T) (*x509.Certificate, *x509.Certificate) {
	t.Helper()
	notBefore := time.Date(2026, 4, 28, 12, 0, 0, 0, time.UTC)
	notAfter := notBefore.Add(365 * 24 * time.Hour)

	caTpl := &x509.Certificate{
		SerialNumber:          big.NewInt(0xCAFE),
		Subject:               pkix.Name{CommonName: "Equiv CA"},
		NotBefore:             notBefore,
		NotAfter:              notAfter.Add(10 * 365 * 24 * time.Hour),
		KeyUsage:              x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
		IsCA:                  true,
		BasicConstraintsValid: true,
	}

	leafTpl := &x509.Certificate{
		SerialNumber: big.NewInt(0xC0FFEE),
		Subject:      pkix.Name{CommonName: "leaf.example.com"},
		NotBefore:    notBefore,
		NotAfter:     notAfter,
		KeyUsage:     x509.KeyUsageDigitalSignature,
		ExtKeyUsage:  []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
	}
	return caTpl, leafTpl
}

// ---------------------------------------------------------------------------
// Leaf certificate signing
// ---------------------------------------------------------------------------

func TestEquivalence_RSA_LeafCert_BytesIdentical(t *testing.T) {
	caKey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		t.Fatalf("rsa keygen: %v", err)
	}
	leafKey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		t.Fatalf("leaf rsa keygen: %v", err)
	}
	wrapped, err := signer.Wrap(caKey)
	if err != nil {
		t.Fatalf("Wrap: %v", err)
	}
	caTpl, leafTpl := fixedTemplate(t)

	// Self-sign the CA so we have a parsed *x509.Certificate to use as
	// the leaf cert's parent (CreateCertificate needs both template and
	// parent; using the same template for both produces a self-signed
	// CA cert that we then parse).
	caDER, err := x509.CreateCertificate(rand.Reader, caTpl, caTpl, &caKey.PublicKey, caKey)
	if err != nil {
		t.Fatalf("create CA: %v", err)
	}
	caCert, err := x509.ParseCertificate(caDER)
	if err != nil {
		t.Fatalf("parse CA: %v", err)
	}

	// Sign the same leaf cert twice — once via raw caKey, once via
	// wrapped Signer. PKCS#1 v1.5 is deterministic, so the full DER
	// must be byte-identical.
	der1, err := x509.CreateCertificate(rand.Reader, leafTpl, caCert, &leafKey.PublicKey, caKey)
	if err != nil {
		t.Fatalf("create leaf (raw): %v", err)
	}
	der2, err := x509.CreateCertificate(rand.Reader, leafTpl, caCert, &leafKey.PublicKey, wrapped)
	if err != nil {
		t.Fatalf("create leaf (wrapped): %v", err)
	}
	if !bytes.Equal(der1, der2) {
		t.Fatalf("RSA leaf cert DER differs between raw and wrapped signer:\n  raw:     %x\n  wrapped: %x", der1, der2)
	}
}

func TestEquivalence_ECDSA_LeafCert_TBSIdentical(t *testing.T) {
	caKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		t.Fatalf("ecdsa keygen: %v", err)
	}
	leafKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		t.Fatalf("leaf ecdsa keygen: %v", err)
	}
	wrapped, err := signer.Wrap(caKey)
	if err != nil {
		t.Fatalf("Wrap: %v", err)
	}
	caTpl, leafTpl := fixedTemplate(t)

	caDER, err := x509.CreateCertificate(rand.Reader, caTpl, caTpl, &caKey.PublicKey, caKey)
	if err != nil {
		t.Fatalf("create CA: %v", err)
	}
	caCert, err := x509.ParseCertificate(caDER)
	if err != nil {
		t.Fatalf("parse CA: %v", err)
	}

	der1, err := x509.CreateCertificate(rand.Reader, leafTpl, caCert, &leafKey.PublicKey, caKey)
	if err != nil {
		t.Fatalf("create leaf (raw): %v", err)
	}
	der2, err := x509.CreateCertificate(rand.Reader, leafTpl, caCert, &leafKey.PublicKey, wrapped)
	if err != nil {
		t.Fatalf("create leaf (wrapped): %v", err)
	}

	cert1, err := x509.ParseCertificate(der1)
	if err != nil {
		t.Fatalf("parse leaf (raw): %v", err)
	}
	cert2, err := x509.ParseCertificate(der2)
	if err != nil {
		t.Fatalf("parse leaf (wrapped): %v", err)
	}

	// TBSCertificate is everything that gets signed — Subject, Issuer,
	// Validity, SubjectPublicKeyInfo, Extensions, etc. The signature
	// bytes themselves differ (ECDSA random k) but the input to the
	// signature MUST be byte-identical or the wrapper is doing
	// something behavioral-different than the raw key.
	if !bytes.Equal(cert1.RawTBSCertificate, cert2.RawTBSCertificate) {
		t.Fatalf("ECDSA leaf cert TBSCertificate differs between raw and wrapped signer (expected: signature bytes differ; everything else byte-equal)")
	}

	// Confirm both signatures are independently valid against the CA's
	// public key. This is the proof that the wrapper actually signed
	// (not just produced random bytes that happened to match length).
	if err := cert1.CheckSignatureFrom(caCert); err != nil {
		t.Fatalf("raw-signed leaf failed validation: %v", err)
	}
	if err := cert2.CheckSignatureFrom(caCert); err != nil {
		t.Fatalf("wrapped-signed leaf failed validation: %v", err)
	}
}

// ---------------------------------------------------------------------------
// CRL signing (mirrors internal/connector/issuer/local/local.go::GenerateCRL)
// ---------------------------------------------------------------------------

func TestEquivalence_RSA_CRL_BytesIdentical(t *testing.T) {
	caKey, _ := rsa.GenerateKey(rand.Reader, 2048)
	wrapped, err := signer.Wrap(caKey)
	if err != nil {
		t.Fatalf("Wrap: %v", err)
	}
	caTpl, _ := fixedTemplate(t)
	caDER, _ := x509.CreateCertificate(rand.Reader, caTpl, caTpl, &caKey.PublicKey, caKey)
	caCert, _ := x509.ParseCertificate(caDER)

	thisUpdate := time.Date(2026, 4, 28, 12, 0, 0, 0, time.UTC)
	crlTpl := &x509.RevocationList{
		Number:     big.NewInt(1),
		ThisUpdate: thisUpdate,
		NextUpdate: thisUpdate.Add(7 * 24 * time.Hour),
		RevokedCertificateEntries: []x509.RevocationListEntry{
			{
				SerialNumber:   big.NewInt(0xDEAD),
				RevocationTime: thisUpdate,
			},
		},
	}

	der1, err := x509.CreateRevocationList(rand.Reader, crlTpl, caCert, caKey)
	if err != nil {
		t.Fatalf("create CRL (raw): %v", err)
	}
	der2, err := x509.CreateRevocationList(rand.Reader, crlTpl, caCert, wrapped)
	if err != nil {
		t.Fatalf("create CRL (wrapped): %v", err)
	}
	if !bytes.Equal(der1, der2) {
		t.Fatalf("RSA CRL DER differs between raw and wrapped signer:\n  raw:     %x\n  wrapped: %x", der1[:64], der2[:64])
	}
}

func TestEquivalence_ECDSA_CRL_TBSIdentical(t *testing.T) {
	caKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	wrapped, err := signer.Wrap(caKey)
	if err != nil {
		t.Fatalf("Wrap: %v", err)
	}
	caTpl, _ := fixedTemplate(t)
	caDER, _ := x509.CreateCertificate(rand.Reader, caTpl, caTpl, &caKey.PublicKey, caKey)
	caCert, _ := x509.ParseCertificate(caDER)

	thisUpdate := time.Date(2026, 4, 28, 12, 0, 0, 0, time.UTC)
	crlTpl := &x509.RevocationList{
		Number:     big.NewInt(1),
		ThisUpdate: thisUpdate,
		NextUpdate: thisUpdate.Add(7 * 24 * time.Hour),
	}

	der1, err := x509.CreateRevocationList(rand.Reader, crlTpl, caCert, caKey)
	if err != nil {
		t.Fatalf("create CRL (raw): %v", err)
	}
	der2, err := x509.CreateRevocationList(rand.Reader, crlTpl, caCert, wrapped)
	if err != nil {
		t.Fatalf("create CRL (wrapped): %v", err)
	}

	crl1, err := x509.ParseRevocationList(der1)
	if err != nil {
		t.Fatalf("parse CRL (raw): %v", err)
	}
	crl2, err := x509.ParseRevocationList(der2)
	if err != nil {
		t.Fatalf("parse CRL (wrapped): %v", err)
	}

	// RawTBSRevocationList is the signed input. Must be byte-equal for
	// equivalence; signature bytes differ for ECDSA.
	if !bytes.Equal(crl1.RawTBSRevocationList, crl2.RawTBSRevocationList) {
		t.Fatalf("ECDSA CRL TBSRevocationList differs between raw and wrapped signer")
	}

	// Both CRLs must validate against the CA.
	if err := crl1.CheckSignatureFrom(caCert); err != nil {
		t.Fatalf("raw-signed CRL failed validation: %v", err)
	}
	if err := crl2.CheckSignatureFrom(caCert); err != nil {
		t.Fatalf("wrapped-signed CRL failed validation: %v", err)
	}
}

// ---------------------------------------------------------------------------
// OCSP response signing
// (mirrors internal/connector/issuer/local/local.go::SignOCSPResponse)
// ---------------------------------------------------------------------------

func TestEquivalence_RSA_OCSPResponse_BytesIdentical(t *testing.T) {
	caKey, _ := rsa.GenerateKey(rand.Reader, 2048)
	wrapped, err := signer.Wrap(caKey)
	if err != nil {
		t.Fatalf("Wrap: %v", err)
	}
	caTpl, _ := fixedTemplate(t)
	caDER, _ := x509.CreateCertificate(rand.Reader, caTpl, caTpl, &caKey.PublicKey, caKey)
	caCert, _ := x509.ParseCertificate(caDER)

	thisUpdate := time.Date(2026, 4, 28, 12, 0, 0, 0, time.UTC)
	ocspTpl := ocsp.Response{
		Status:       ocsp.Good,
		SerialNumber: big.NewInt(0xCAFEBABE),
		ThisUpdate:   thisUpdate,
		NextUpdate:   thisUpdate.Add(24 * time.Hour),
	}

	resp1, err := ocsp.CreateResponse(caCert, caCert, ocspTpl, caKey)
	if err != nil {
		t.Fatalf("create OCSP (raw): %v", err)
	}
	resp2, err := ocsp.CreateResponse(caCert, caCert, ocspTpl, wrapped)
	if err != nil {
		t.Fatalf("create OCSP (wrapped): %v", err)
	}
	if !bytes.Equal(resp1, resp2) {
		t.Fatalf("RSA OCSP response differs between raw and wrapped signer (PKCS#1 v1.5 must be deterministic)")
	}
}

func TestEquivalence_ECDSA_OCSPResponse_StructurallyIdentical(t *testing.T) {
	caKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	wrapped, err := signer.Wrap(caKey)
	if err != nil {
		t.Fatalf("Wrap: %v", err)
	}
	caTpl, _ := fixedTemplate(t)
	caDER, _ := x509.CreateCertificate(rand.Reader, caTpl, caTpl, &caKey.PublicKey, caKey)
	caCert, _ := x509.ParseCertificate(caDER)

	thisUpdate := time.Date(2026, 4, 28, 12, 0, 0, 0, time.UTC)
	ocspTpl := ocsp.Response{
		Status:       ocsp.Good,
		SerialNumber: big.NewInt(0xCAFEBABE),
		ThisUpdate:   thisUpdate,
		NextUpdate:   thisUpdate.Add(24 * time.Hour),
	}

	resp1, err := ocsp.CreateResponse(caCert, caCert, ocspTpl, caKey)
	if err != nil {
		t.Fatalf("create OCSP (raw): %v", err)
	}
	resp2, err := ocsp.CreateResponse(caCert, caCert, ocspTpl, wrapped)
	if err != nil {
		t.Fatalf("create OCSP (wrapped): %v", err)
	}

	parsed1, err := ocsp.ParseResponse(resp1, caCert)
	if err != nil {
		t.Fatalf("parse OCSP (raw): %v", err)
	}
	parsed2, err := ocsp.ParseResponse(resp2, caCert)
	if err != nil {
		t.Fatalf("parse OCSP (wrapped): %v", err)
	}

	// Compare every field except Signature + RawResponderName (which
	// the parser may normalize differently across calls).
	if parsed1.Status != parsed2.Status {
		t.Fatalf("status differs: %d vs %d", parsed1.Status, parsed2.Status)
	}
	if parsed1.SerialNumber.Cmp(parsed2.SerialNumber) != 0 {
		t.Fatalf("serial differs: %v vs %v", parsed1.SerialNumber, parsed2.SerialNumber)
	}
	if !parsed1.ThisUpdate.Equal(parsed2.ThisUpdate) {
		t.Fatalf("ThisUpdate differs")
	}
	if !parsed1.NextUpdate.Equal(parsed2.NextUpdate) {
		t.Fatalf("NextUpdate differs")
	}

	// Both responses must validate against the CA.
	if err := parsed1.CheckSignatureFrom(caCert); err != nil {
		t.Fatalf("raw-signed OCSP failed validation: %v", err)
	}
	if err := parsed2.CheckSignatureFrom(caCert); err != nil {
		t.Fatalf("wrapped-signed OCSP failed validation: %v", err)
	}
}

// ---------------------------------------------------------------------------
// Negative test: the equivalence checker isn't trivially-passing
// ---------------------------------------------------------------------------

// TestEquivalence_Sentinel_DifferentKeysProduceDifferentBytes is the smoke
// check that the equivalence assertions above would actually catch a
// regression. Sign with two different keys; assert the resulting cert
// DER bytes differ. If THIS test passes trivially (false negative), the
// equivalence checker is broken and the test suite above is not actually
// guarding anything.
func TestEquivalence_Sentinel_DifferentKeysProduceDifferentBytes(t *testing.T) {
	keyA, _ := rsa.GenerateKey(rand.Reader, 2048)
	keyB, _ := rsa.GenerateKey(rand.Reader, 2048)
	caTpl, leafTpl := fixedTemplate(t)
	leafKey, _ := rsa.GenerateKey(rand.Reader, 2048)

	caDERA, _ := x509.CreateCertificate(rand.Reader, caTpl, caTpl, &keyA.PublicKey, keyA)
	caCertA, _ := x509.ParseCertificate(caDERA)
	caDERB, _ := x509.CreateCertificate(rand.Reader, caTpl, caTpl, &keyB.PublicKey, keyB)
	caCertB, _ := x509.ParseCertificate(caDERB)

	der1, _ := x509.CreateCertificate(rand.Reader, leafTpl, caCertA, &leafKey.PublicKey, keyA)
	der2, _ := x509.CreateCertificate(rand.Reader, leafTpl, caCertB, &leafKey.PublicKey, keyB)
	if bytes.Equal(der1, der2) {
		t.Fatal("sentinel: certs signed by DIFFERENT keys must NOT byte-equal — equivalence checker is trivially-passing")
	}
}

// ---------------------------------------------------------------------------
// Sanity: the wrapped signer's Sign output is independently valid for
// arbitrary digests (covers the path that doesn't go through x509.*).
// ---------------------------------------------------------------------------

func TestEquivalence_WrappedSign_RSA_VerifiesAgainstStdlib(t *testing.T) {
	k, _ := rsa.GenerateKey(rand.Reader, 2048)
	w, err := signer.Wrap(k)
	if err != nil {
		t.Fatalf("Wrap: %v", err)
	}
	digest := sha256OfBytes([]byte("test message"))
	sig, err := w.Sign(rand.Reader, digest, crypto.SHA256)
	if err != nil {
		t.Fatalf("Sign: %v", err)
	}
	if err := rsa.VerifyPKCS1v15(&k.PublicKey, crypto.SHA256, digest, sig); err != nil {
		t.Fatalf("wrapped RSA Sign produced signature that does not verify with stdlib VerifyPKCS1v15: %v", err)
	}
}

func TestEquivalence_WrappedSign_ECDSA_VerifiesAgainstStdlib(t *testing.T) {
	k, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	w, err := signer.Wrap(k)
	if err != nil {
		t.Fatalf("Wrap: %v", err)
	}
	digest := sha256OfBytes([]byte("test message"))
	sig, err := w.Sign(rand.Reader, digest, crypto.SHA256)
	if err != nil {
		t.Fatalf("Sign: %v", err)
	}
	if !ecdsa.VerifyASN1(&k.PublicKey, digest, sig) {
		t.Fatal("wrapped ECDSA Sign produced signature that does not verify with stdlib VerifyASN1")
	}
}

func sha256OfBytes(b []byte) []byte {
	h := sha256.Sum256(b)
	return h[:]
}
