package handler

import (
	"context"
	"crypto"
	"crypto/x509"
	"encoding/asn1"
	"encoding/base64"
	"encoding/pem"
	"fmt"
	"io"
	"net/http"
	"strings"

	"github.com/certctl-io/certctl/internal/api/middleware"
	"github.com/certctl-io/certctl/internal/domain"
	"github.com/certctl-io/certctl/internal/pkcs7"
)

// SCEPService defines the service interface for SCEP enrollment operations.
// SCEP (RFC 8894) is a protocol for certificate enrollment used by MDM platforms
// and network devices.
type SCEPService interface {
	// GetCACaps returns the SCEP server capabilities as a newline-separated string.
	GetCACaps(ctx context.Context) string

	// GetCACert returns the PEM-encoded CA certificate chain.
	GetCACert(ctx context.Context) (string, error)

	// PKCSReq processes a PKCS#10 CSR and returns a signed certificate.
	// Used by the MVP raw-CSR fall-through path; preserved unchanged for
	// backward compat with lightweight SCEP clients.
	PKCSReq(ctx context.Context, csrPEM string, challengePassword string, transactionID string) (*domain.SCEPEnrollResult, error)

	// PKCSReqWithEnvelope processes a SCEP PKCSReq from the RFC 8894 path
	// (the handler successfully parsed an EnvelopedData + signerInfo POPO).
	// Returns *SCEPResponseEnvelope (not error + *SCEPEnrollResult) because
	// RFC 8894 §3.3 mandates a CertRep PKIMessage on every response, even
	// failures. Returns nil to signal 'invalid challenge password' (caller
	// translates to HTTP 403, matching the MVP path's wire shape).
	PKCSReqWithEnvelope(ctx context.Context, csrPEM string, challengePassword string, envelope *domain.SCEPRequestEnvelope) *domain.SCEPResponseEnvelope

	// RenewalReqWithEnvelope processes a SCEP RenewalReq (RFC 8894 §3.3.1.2)
	// from the RFC 8894 path. Same contract as PKCSReqWithEnvelope but the
	// service additionally verifies that envelope.SignerCert chains to the
	// issuer's CA — RenewalReq requires a previously-issued cert as POPO.
	RenewalReqWithEnvelope(ctx context.Context, csrPEM string, challengePassword string, envelope *domain.SCEPRequestEnvelope) *domain.SCEPResponseEnvelope

	// GetCertInitialWithEnvelope handles SCEP polling requests (RFC 8894
	// §3.3.3). The v1 implementation always returns FAILURE+badCertID
	// because deferred-issuance isn't supported (every PKCSReq either
	// succeeds or fails synchronously); wiring is in place for a future
	// 'queue for manual approval' workflow.
	GetCertInitialWithEnvelope(ctx context.Context, envelope *domain.SCEPRequestEnvelope) *domain.SCEPResponseEnvelope
}

// SCEPHandler handles HTTP requests for the SCEP protocol (RFC 8894).
//
// SCEP uses a single endpoint with operation-based dispatch via query parameters.
// All operations use GET or POST to the same path.
//
// Supported operations:
//   - GET  ?operation=GetCACaps    — server capabilities
//   - GET  ?operation=GetCACert    — CA certificate distribution
//   - POST ?operation=PKIOperation — certificate enrollment (PKCSReq)
//
// SCEP RFC 8894 + Intune master bundle Phase 2.3: SCEPHandler now optionally
// carries an RA cert + key pair. When set, the handler tries the new RFC 8894
// PKIMessage path FIRST (parse SignedData → verify POPO → decrypt EnvelopedData).
// On any parse failure it falls through to the legacy MVP raw-CSR path (preserves
// backward compat with lightweight SCEP clients). When RA pair is unset, the
// handler runs MVP-only (the v2.0.x behavior).
type SCEPHandler struct {
	svc    SCEPService
	raCert *x509.Certificate // RFC 8894 path: RA cert clients encrypt CSR to
	raKey  crypto.PrivateKey // RFC 8894 path: RA key for EnvelopedData decrypt + CertRep signing

	// SCEP RFC 8894 + Intune master bundle Phase 6.5: per-profile mTLS
	// trust bundle. When set, HandleSCEPMTLS verifies the inbound client
	// cert chain against this pool. Nil when the profile has MTLSEnabled=false
	// — HandleSCEPMTLS rejects unconditionally in that case (the route
	// shouldn't even be registered, but defense in depth).
	mtlsTrustPool *x509.CertPool
}

// NewSCEPHandler creates a new SCEPHandler with the legacy MVP-only behavior.
// SetRAPair below upgrades the handler to the RFC 8894 path; that's the route
// cmd/server/main.go takes when the operator supplies CERTCTL_SCEP_RA_*.
func NewSCEPHandler(svc SCEPService) SCEPHandler {
	return SCEPHandler{svc: svc}
}

// SetRAPair injects the RA cert + key the RFC 8894 path needs. Called by
// cmd/server/main.go after the per-profile preflight gate validates the pair.
// Without this call the handler runs MVP-only (the legacy v2.0.x behavior).
func (h *SCEPHandler) SetRAPair(raCert *x509.Certificate, raKey crypto.PrivateKey) {
	h.raCert = raCert
	h.raKey = raKey
}

// SetMTLSTrustPool injects the per-profile client-cert trust pool the
// `/scep-mtls/<PathID>` sibling route uses to verify inbound device
// bootstrap certs. SCEP RFC 8894 + Intune master bundle Phase 6.5.
//
// The TLS layer (cmd/server/main.go::buildServerTLSConfig) uses
// VerifyClientCertIfGiven against the UNION of every enabled mTLS
// profile's bundle, so the same TLS listener serves both /scep
// (challenge-password-only) and /scep-mtls/<PathID> (cert + challenge).
// The per-profile gate at the handler layer enforces 'cert must chain to
// THIS profile's bundle' so a cert that chains to profile A's bundle
// cannot enroll against profile B even though it passed the TLS layer.
func (h *SCEPHandler) SetMTLSTrustPool(pool *x509.CertPool) {
	h.mtlsTrustPool = pool
}

// HandleSCEPMTLS is the entry point for the `/scep-mtls/<PathID>` sibling
// route. SCEP RFC 8894 + Intune master bundle Phase 6.5.
//
// Gates on the inbound client cert chain — the request must:
//
//  1. Carry a TLS connection (r.TLS != nil) — defense in depth even
//     though the HTTPS-only listener guarantees this.
//  2. Have presented a peer cert (len(r.TLS.PeerCertificates) > 0) — the
//     listener uses VerifyClientCertIfGiven, so a missing cert is a
//     legitimate failure here, not a TLS error.
//  3. The peer cert chain must verify against THIS profile's trust pool
//     (h.mtlsTrustPool). The TLS layer verified against the union pool
//     of all mTLS profiles, but a cert that chains to profile A cannot
//     enroll against profile B — verify per-profile here.
//
// Failures return HTTP 401 (Unauthorized — mTLS failure is authentication,
// not authorization). On success the call delegates to HandleSCEP — the
// challenge-password gate still fires (defense in depth: mTLS is additive,
// not replacement).
func (h SCEPHandler) HandleSCEPMTLS(w http.ResponseWriter, r *http.Request) {
	if h.mtlsTrustPool == nil {
		// Profile is misconfigured — handler registered for /scep-mtls but
		// SetMTLSTrustPool was never called. The startup preflight should
		// have caught this; surfacing as 500 makes the deploy bug loud.
		ErrorWithRequestID(w, http.StatusInternalServerError, "mTLS handler missing trust pool", middleware.GetRequestID(r.Context()))
		return
	}
	if r.TLS == nil || len(r.TLS.PeerCertificates) == 0 {
		// Client didn't present a cert. With VerifyClientCertIfGiven the
		// TLS handshake completes anyway — the per-profile gate enforces
		// 'cert required' at the application layer.
		ErrorWithRequestID(w, http.StatusUnauthorized, "Client certificate required for /scep-mtls", middleware.GetRequestID(r.Context()))
		return
	}
	leaf := r.TLS.PeerCertificates[0]
	intermediates := x509.NewCertPool()
	for _, c := range r.TLS.PeerCertificates[1:] {
		intermediates.AddCert(c)
	}
	if _, err := leaf.Verify(x509.VerifyOptions{
		Roots:         h.mtlsTrustPool,
		Intermediates: intermediates,
		KeyUsages:     []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageAny},
	}); err != nil {
		ErrorWithRequestID(w, http.StatusUnauthorized, "Client certificate not trusted by this profile", middleware.GetRequestID(r.Context()))
		return
	}
	// Defense in depth — mTLS is ADDITIVE. The request still flows through
	// HandleSCEP which enforces the challenge-password gate at the service
	// layer. A stolen device cert without the matching challenge password
	// still gets rejected (and vice versa).
	h.HandleSCEP(w, r)
}

// HandleSCEP is the single entry point for all SCEP operations.
// It dispatches based on the "operation" query parameter.
func (h SCEPHandler) HandleSCEP(w http.ResponseWriter, r *http.Request) {
	operation := r.URL.Query().Get("operation")

	switch operation {
	case "GetCACaps":
		h.getCACaps(w, r)
	case "GetCACert":
		h.getCACert(w, r)
	case "PKIOperation":
		h.pkiOperation(w, r)
	default:
		http.Error(w, fmt.Sprintf("Unknown SCEP operation: %s", operation), http.StatusBadRequest)
	}
}

// getCACaps handles GET ?operation=GetCACaps
// Returns the SCEP server capabilities as plaintext, one per line.
func (h SCEPHandler) getCACaps(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	caps := h.svc.GetCACaps(r.Context())
	w.Header().Set("Content-Type", "text/plain")
	w.WriteHeader(http.StatusOK)
	w.Write([]byte(caps))
}

// getCACert handles GET ?operation=GetCACert
// Returns the CA certificate(s). Single cert as DER, chain as PKCS#7.
func (h SCEPHandler) getCACert(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	caCertPEM, err := h.svc.GetCACert(r.Context())
	if err != nil {
		requestID := middleware.GetRequestID(r.Context())
		ErrorWithRequestID(w, http.StatusInternalServerError, fmt.Sprintf("Failed to get CA certificate: %v", err), requestID)
		return
	}

	// Parse PEM to DER chain
	derCerts, err := pkcs7.PEMToDERChain(caCertPEM)
	if err != nil {
		requestID := middleware.GetRequestID(r.Context())
		ErrorWithRequestID(w, http.StatusInternalServerError, "Failed to parse CA certificates", requestID)
		return
	}

	if len(derCerts) == 1 {
		// Single CA cert — return as raw DER
		w.Header().Set("Content-Type", "application/x-x509-ca-cert")
		w.WriteHeader(http.StatusOK)
		w.Write(derCerts[0])
		return
	}

	// Multiple certs (CA + RA or chain) — return as PKCS#7
	pkcs7Data, err := pkcs7.BuildCertsOnlyPKCS7(derCerts)
	if err != nil {
		requestID := middleware.GetRequestID(r.Context())
		ErrorWithRequestID(w, http.StatusInternalServerError, "Failed to build PKCS#7 response", requestID)
		return
	}

	w.Header().Set("Content-Type", "application/x-x509-ca-ra-cert")
	w.WriteHeader(http.StatusOK)
	w.Write(pkcs7Data)
}

// pkiOperation handles POST ?operation=PKIOperation
// Processes a SCEP enrollment request containing a PKCS#7-wrapped CSR.
//
// SCEP RFC 8894 + Intune master bundle Phase 2.3: this handler tries the
// new RFC 8894 PKIMessage path FIRST (parse outer SignedData → verify
// signerInfo POPO → extract authenticatedAttributes → decrypt EnvelopedData
// to recover the inner CSR). On any parse failure it falls through to the
// legacy MVP raw-CSR path (extractCSRFromPKCS7). The MVP path stays
// unchanged for backward compat with lightweight SCEP clients.
//
// Path selection rules:
//   - h.raCert / h.raKey unset → MVP-only (legacy v2.0.x behavior, never tries RFC 8894)
//   - RA pair set + RFC 8894 parse succeeds → RFC 8894 path (CertRep PKIMessage response)
//   - RA pair set + RFC 8894 parse fails → MVP fall-through (degenerate certs-only response)
//
// The Phase 3 commit will replace the MVP-fall-through writeSCEPResponse
// with writeCertRepPKIMessage for the RFC 8894 path; the MVP path keeps
// using writeSCEPResponse so lightweight clients see no behavior change.
func (h SCEPHandler) pkiOperation(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	requestID := middleware.GetRequestID(r.Context())

	body, err := io.ReadAll(io.LimitReader(r.Body, 1<<20)) // 1MB limit
	if err != nil {
		ErrorWithRequestID(w, http.StatusBadRequest, "Failed to read request body", requestID)
		return
	}
	defer r.Body.Close()

	if len(body) == 0 {
		ErrorWithRequestID(w, http.StatusBadRequest, "Empty request body", requestID)
		return
	}

	// Try the RFC 8894 path first when an RA pair is configured. On any
	// parse failure we fall through to the MVP path silently — that's the
	// backward-compat contract for lightweight clients.
	if h.raCert != nil && h.raKey != nil {
		if envelope, csrPEM, challengePassword, ok := h.tryParseRFC8894(body); ok {
			// SCEP RFC 8894 + Intune master bundle Phase 4.1: dispatch on
			// the parsed messageType. PKCSReq + RenewalReq exercise the
			// full enrollment pipeline (different audit actions + chain
			// validation for renewal); GetCertInitial is the polling
			// shape (v1 stub returns badCertID since deferred-issuance
			// isn't supported); unknown messageType returns CertRep with
			// FAILURE+badRequest per RFC 8894 §3.3.2.2.
			var resp *domain.SCEPResponseEnvelope
			switch envelope.MessageType {
			case domain.SCEPMessageTypePKCSReq:
				resp = h.svc.PKCSReqWithEnvelope(r.Context(), csrPEM, challengePassword, envelope)
			case domain.SCEPMessageTypeRenewalReq:
				resp = h.svc.RenewalReqWithEnvelope(r.Context(), csrPEM, challengePassword, envelope)
			case domain.SCEPMessageTypeGetCertInitial:
				resp = h.svc.GetCertInitialWithEnvelope(r.Context(), envelope)
			default:
				// Unknown messageType — emit a CertRep+FAILURE so the
				// client sees a structured response rather than a vague
				// 400. RFC 8894 §3.2.1.4.1 enumerates the valid types;
				// anything else is a malformed client.
				resp = &domain.SCEPResponseEnvelope{
					Status:         domain.SCEPStatusFailure,
					FailInfo:       domain.SCEPFailBadRequest,
					TransactionID:  envelope.TransactionID,
					RecipientNonce: envelope.SenderNonce,
				}
			}
			if resp == nil {
				// nil signals 'invalid challenge password' from the
				// service layer (only PKCSReq + RenewalReq paths can
				// return nil — GetCertInitial always returns a
				// CertRep). RFC 8894 §3.3.1 is silent on whether to
				// return a CertRep or an HTTP error for the wrong-
				// password case; we mirror the MVP path's HTTP 403
				// wire shape so the client sees a clear auth failure
				// rather than trying to interpret a structurally-valid
				// CertRep+failInfo (which conflates 'wrong secret'
				// with 'wrong CSR shape').
				ErrorWithRequestID(w, http.StatusForbidden, "Invalid challenge password", requestID)
				return
			}
			// SCEP RFC 8894 Phase 3.2: emit CertRep PKIMessage for both
			// success AND failure paths (RFC 8894 §3.3 mandates a
			// PKIMessage response on every PKIOperation request, including
			// failures). The MVP path keeps using writeSCEPResponse —
			// that's the legacy certs-only response shape lightweight
			// clients understand.
			h.writeCertRepPKIMessage(w, r, envelope, resp)
			return
		}
		// RFC 8894 parse failed — fall through to the MVP path.
	}

	// MVP path: extract the PKCS#10 CSR from the PKCS#7 SignedData envelope
	// using the legacy parser. This is what lightweight clients (raw-CSR-
	// inside-SignedData, or even bare CSRs in some cases) hit.
	csrDER, challengePassword, transactionID, err := extractCSRFromPKCS7(body)
	if err != nil {
		ErrorWithRequestID(w, http.StatusBadRequest, fmt.Sprintf("Invalid SCEP message: %v", err), requestID)
		return
	}

	// Validate the CSR
	csr, err := x509.ParseCertificateRequest(csrDER)
	if err != nil {
		ErrorWithRequestID(w, http.StatusBadRequest, fmt.Sprintf("Invalid CSR: %v", err), requestID)
		return
	}
	if err := csr.CheckSignature(); err != nil {
		ErrorWithRequestID(w, http.StatusBadRequest, fmt.Sprintf("CSR signature invalid: %v", err), requestID)
		return
	}

	// Convert DER CSR to PEM for the service layer
	csrPEM := string(pem.EncodeToMemory(&pem.Block{
		Type:  "CERTIFICATE REQUEST",
		Bytes: csrDER,
	}))

	result, err := h.svc.PKCSReq(r.Context(), csrPEM, challengePassword, transactionID)
	if err != nil {
		if strings.Contains(err.Error(), "challenge password") {
			ErrorWithRequestID(w, http.StatusForbidden, "Invalid challenge password", requestID)
			return
		}
		ErrorWithRequestID(w, http.StatusInternalServerError, fmt.Sprintf("Enrollment failed: %v", err), requestID)
		return
	}

	// Build response: issued cert wrapped in PKCS#7 certs-only
	h.writeSCEPResponse(w, result)
}

// tryParseRFC8894 attempts to parse the request body as an RFC 8894 SCEP
// PKIMessage:
//  1. Parse outer SignedData; pluck the device's transient signing cert.
//  2. Verify the signerInfo signature (POPO over auth-attrs).
//  3. Extract messageType / transactionID / senderNonce auth-attrs.
//  4. The encapContent is the inner pkcsPKIEnvelope (an EnvelopedData);
//     decrypt it with h.raKey to recover the PKCS#10 CSR DER.
//  5. Parse the CSR + extract the challengePassword attribute (RFC 2985
//     §5.4.1) so the service-layer's challenge-password gate can run.
//  6. PEM-encode the CSR for the service layer.
//
// Returns (envelope, csrPEM, challengePassword, true) on success;
// (nil, "", "", false) on any parse / verify / decrypt failure. The
// handler treats false as 'fall through to MVP path' so lightweight
// clients keep working.
func (h SCEPHandler) tryParseRFC8894(body []byte) (*domain.SCEPRequestEnvelope, string, string, bool) {
	sd, err := pkcs7.ParseSignedData(body)
	if err != nil {
		return nil, "", "", false
	}
	if len(sd.SignerInfos) == 0 {
		return nil, "", "", false
	}
	si := sd.SignerInfos[0]
	if err := si.VerifySignature(); err != nil {
		return nil, "", "", false
	}
	mt, err := si.GetMessageType()
	if err != nil {
		return nil, "", "", false
	}
	tid, err := si.GetTransactionID()
	if err != nil {
		return nil, "", "", false
	}
	nonce, err := si.GetSenderNonce()
	if err != nil {
		// senderNonce is optional in some clients; treat missing as empty.
		nonce = nil
	}
	// EncapContent is the inner pkcsPKIEnvelope (EnvelopedData). Parse +
	// decrypt with the RA key.
	if len(sd.EncapContent) == 0 {
		return nil, "", "", false
	}
	env, err := pkcs7.ParseEnvelopedData(sd.EncapContent)
	if err != nil {
		return nil, "", "", false
	}
	csrDER, err := env.Decrypt(h.raKey, h.raCert)
	if err != nil {
		return nil, "", "", false
	}
	// Verify the recovered bytes really are a CSR. If not, fall through.
	csr, err := x509.ParseCertificateRequest(csrDER)
	if err != nil {
		return nil, "", "", false
	}
	// Extract the challengePassword attribute (RFC 2985 §5.4.1). Empty
	// when missing; the service-layer gate then refuses with 'invalid
	// challenge password' (correct behavior for clients that omit the
	// auth attribute).
	challengePassword := extractChallengePasswordFromCSR(csr)
	csrPEM := string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrDER}))
	envelope := &domain.SCEPRequestEnvelope{
		MessageType:   mt,
		TransactionID: tid,
		SenderNonce:   nonce,
		SignerCert:    si.SignerCert.Raw,
	}
	return envelope, csrPEM, challengePassword, true
}

// extractChallengePasswordFromCSR walks the parsed CSR's attributes for
// the RFC 2985 §5.4.1 challengePassword (OID 1.2.840.113549.1.9.7).
// Returns empty string when missing.
//
// SA1019 carve-out: csr.Attributes is deprecated by Go's stdlib for the
// requestedExtensions attribute, but RFC 2985 challengePassword (OID
// 1.2.840.113549.1.9.7) is a SEPARATE CSR attribute that cannot be
// retrieved via csr.Extensions. There is no non-deprecated stdlib API
// for it; the same `lint:ignore SA1019` line precedent set by
// extractCSRFields applies here.
func extractChallengePasswordFromCSR(csr *x509.CertificateRequest) string {
	oidChallengePassword := asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 7}
	//lint:ignore SA1019 RFC 2985 challengePassword has no non-deprecated stdlib API; see extractCSRFields docblock for the M-028 audit closure rationale.
	for _, attr := range csr.Attributes {
		if attr.Type.Equal(oidChallengePassword) {
			if len(attr.Value) > 0 && len(attr.Value[0]) > 0 {
				if pwd, ok := attr.Value[0][0].Value.(string); ok {
					return pwd
				}
			}
		}
	}
	return ""
}

// writeCertRepPKIMessage builds and writes a SCEP CertRep PKIMessage as
// the response to a PKIOperation request that was successfully parsed
// via the RFC 8894 path.
//
// SCEP RFC 8894 + Intune master bundle Phase 3.2.
//
// Both success AND failure responses go through here — RFC 8894 §3.3
// mandates a PKIMessage response on every PKIOperation request, with
// pkiStatus + (on failure) failInfo signaling the outcome to the client.
//
// On failure to BUILD the response (a programmer / config bug — e.g. a
// device cert that's not RSA), we return HTTP 500 rather than try to
// construct a fallback PKIMessage that might re-trigger the same bug.
// Operators see a clear failure log + the request fails loud, which is
// preferable to silently emitting a half-built response.
func (h SCEPHandler) writeCertRepPKIMessage(w http.ResponseWriter, r *http.Request, req *domain.SCEPRequestEnvelope, resp *domain.SCEPResponseEnvelope) {
	pkiMessageDER, err := pkcs7.BuildCertRepPKIMessage(req, resp, h.raCert, h.raKey)
	if err != nil {
		ErrorWithRequestID(w, http.StatusInternalServerError, fmt.Sprintf("Failed to build CertRep PKIMessage: %v", err), middleware.GetRequestID(r.Context()))
		return
	}
	w.Header().Set("Content-Type", "application/x-pki-message")
	w.WriteHeader(http.StatusOK)
	_, _ = w.Write(pkiMessageDER)
}

// silence unused-import warning if some narrow build excludes the path
// where crypto.PrivateKey is used (the RA key field above).
var _ crypto.PrivateKey = (*interface{})(nil)

// writeSCEPResponse writes a SCEP enrollment response as PKCS#7 certs-only (DER).
func (h SCEPHandler) writeSCEPResponse(w http.ResponseWriter, result *domain.SCEPEnrollResult) {
	var derCerts [][]byte

	certDER, err := pkcs7.PEMToDERChain(result.CertPEM)
	if err != nil || len(certDER) == 0 {
		http.Error(w, "Failed to encode certificate", http.StatusInternalServerError)
		return
	}
	derCerts = append(derCerts, certDER...)

	if result.ChainPEM != "" {
		chainDER, err := pkcs7.PEMToDERChain(result.ChainPEM)
		if err == nil {
			derCerts = append(derCerts, chainDER...)
		}
	}

	pkcs7Data, err := pkcs7.BuildCertsOnlyPKCS7(derCerts)
	if err != nil {
		http.Error(w, "Failed to build PKCS#7 response", http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/x-pki-message")
	w.WriteHeader(http.StatusOK)
	w.Write(pkcs7Data)
}

// extractCSRFromPKCS7 extracts a PKCS#10 CSR from a SCEP PKCS#7 SignedData envelope.
//
// SCEP clients wrap the CSR in a PKCS#7 SignedData structure. For the MVP, we parse
// the outer ASN.1 structure to find the encapsulated content (the CSR bytes), and
// extract the challenge password from the CSR attributes.
//
// Returns: csrDER, challengePassword, transactionID, error
func extractCSRFromPKCS7(data []byte) ([]byte, string, string, error) {
	// Try to decode as PKCS#7 SignedData
	csrDER, err := parseSignedDataForCSR(data)
	if err != nil {
		// Fallback: some clients send the CSR directly (not wrapped in PKCS#7)
		// or send base64-encoded data
		decoded, decErr := base64.StdEncoding.DecodeString(strings.TrimSpace(string(data)))
		if decErr == nil {
			// Try the decoded data as PKCS#7
			csrDER2, err2 := parseSignedDataForCSR(decoded)
			if err2 == nil {
				return extractCSRFields(csrDER2)
			}
			// Maybe the decoded data IS the CSR directly
			if _, parseErr := x509.ParseCertificateRequest(decoded); parseErr == nil {
				return extractCSRFields(decoded)
			}
		}
		// Maybe the raw data IS the CSR directly (no PKCS#7 wrapping)
		if _, parseErr := x509.ParseCertificateRequest(data); parseErr == nil {
			return extractCSRFields(data)
		}
		return nil, "", "", fmt.Errorf("failed to extract CSR from PKCS#7: %w", err)
	}
	return extractCSRFields(csrDER)
}

// extractCSRFields extracts the challenge password and transaction ID from CSR attributes.
func extractCSRFields(csrDER []byte) ([]byte, string, string, error) {
	csr, err := x509.ParseCertificateRequest(csrDER)
	if err != nil {
		return nil, "", "", fmt.Errorf("invalid CSR: %w", err)
	}

	challengePassword := ""

	// OID for challengePassword: 1.2.840.113549.1.9.7
	oidChallengePassword := asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 7}

	// Extract challenge password from parsed CSR attributes.
	// Attributes is []pkix.AttributeTypeAndValueSET where each has Type (OID)
	// and Value ([][]pkix.AttributeTypeAndValue). The challenge password value
	// is stored as a string in the inner AttributeTypeAndValue.Value field.
	//
	// Audit M-028 carve-out: Go's stdlib deprecates `csr.Attributes` for the
	// specific use case of parsing the "requestedExtensions" CSR attribute
	// (OID 1.2.840.113549.1.9.14), pointing callers at `csr.Extensions` /
	// `csr.ExtraExtensions`. challengePassword (OID 1.2.840.113549.1.9.7)
	// per RFC 2985 §5.4.1 is a SEPARATE CSR attribute that cannot be
	// retrieved via Extensions. There is no non-deprecated stdlib API for
	// it; callers either accept the deprecation warning or parse the raw
	// `csr.RawAttributes` ASN.1 themselves. We accept the warning; the
	// staticcheck.conf and golangci-lint rules suppress SA1019 for this
	// specific line per the audit closure note.
	//lint:ignore SA1019 RFC 2985 challengePassword has no non-deprecated stdlib API; see comment above.
	for _, attr := range csr.Attributes {
		if attr.Type.Equal(oidChallengePassword) {
			if len(attr.Value) > 0 && len(attr.Value[0]) > 0 {
				if pwd, ok := attr.Value[0][0].Value.(string); ok {
					challengePassword = pwd
				}
			}
		}
	}

	// transactionID falls back to the CSR's CN. The MVP path (this
	// function) never extracts the SCEP transaction-ID attribute (OID
	// 2.16.840.1.113733.1.9.7) from CSR.Attributes — that's a known
	// gap; the RFC 8894 path (tryParseRFC8894 above) extracts it
	// properly from the PKCS#7 SignedData authenticatedAttributes,
	// which is where conformant clients put it anyway. CodeQL #18
	// flagged the pre-existing `if transactionID == ""` dead
	// conditional (transactionID was initialized to "" three lines
	// above and never reassigned); cleaned up here. The MVP path
	// stays usable for lightweight legacy clients that send the CSR
	// directly with no PKCS#7 wrapping — they get CN-as-transaction-ID
	// which is sufficient for matching against pollers in the existing
	// test suite.
	transactionID := csr.Subject.CommonName

	return csrDER, challengePassword, transactionID, nil
}

// pkcs7ContentInfo represents the outer ContentInfo structure.
type pkcs7ContentInfo struct {
	ContentType asn1.ObjectIdentifier
	Content     asn1.RawValue `asn1:"explicit,tag:0"`
}

// pkcs7SignedData represents a simplified SignedData structure for CSR extraction.
type pkcs7SignedData struct {
	Version          int
	DigestAlgorithms asn1.RawValue
	EncapContentInfo asn1.RawValue
}

// pkcs7EncapContent represents the EncapsulatedContentInfo.
type pkcs7EncapContent struct {
	ContentType asn1.ObjectIdentifier
	Content     asn1.RawValue `asn1:"explicit,optional,tag:0"`
}

// parseSignedDataForCSR extracts the encapsulated content (CSR) from PKCS#7 SignedData.
func parseSignedDataForCSR(data []byte) ([]byte, error) {
	var contentInfo pkcs7ContentInfo
	rest, err := asn1.Unmarshal(data, &contentInfo)
	if err != nil {
		return nil, fmt.Errorf("failed to parse ContentInfo: %w", err)
	}
	if len(rest) > 0 {
		// Trailing data is OK for some implementations
	}

	// OID for signedData: 1.2.840.113549.1.7.2
	oidSignedData := asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 2}
	if !contentInfo.ContentType.Equal(oidSignedData) {
		return nil, fmt.Errorf("not SignedData: got OID %v", contentInfo.ContentType)
	}

	// Parse the SignedData
	var signedData pkcs7SignedData
	_, err = asn1.Unmarshal(contentInfo.Content.Bytes, &signedData)
	if err != nil {
		return nil, fmt.Errorf("failed to parse SignedData: %w", err)
	}

	// Parse the EncapsulatedContentInfo to get the CSR
	var encapContent pkcs7EncapContent
	_, err = asn1.Unmarshal(signedData.EncapContentInfo.FullBytes, &encapContent)
	if err != nil {
		return nil, fmt.Errorf("failed to parse EncapsulatedContentInfo: %w", err)
	}

	if len(encapContent.Content.Bytes) == 0 {
		return nil, fmt.Errorf("empty encapsulated content")
	}

	// The content may be wrapped in an OCTET STRING
	var csrBytes []byte
	var octetString asn1.RawValue
	if _, err := asn1.Unmarshal(encapContent.Content.Bytes, &octetString); err == nil && octetString.Tag == asn1.TagOctetString {
		csrBytes = octetString.Bytes
	} else {
		csrBytes = encapContent.Content.Bytes
	}

	// Validate it's a parseable CSR
	if _, err := x509.ParseCertificateRequest(csrBytes); err != nil {
		return nil, fmt.Errorf("extracted content is not a valid CSR: %w", err)
	}

	return csrBytes, nil
}
