package security

import (
	"bytes"
	"errors"
	"regexp"
)

// privateKeyBlockRe matches the PEM header for any private key variant.
// Catches: RSA, EC, DSA, OPENSSH, ENCRYPTED, and the legacy PKCS#1 forms.
var privateKeyBlockRe = regexp.MustCompile(
	`-----BEGIN (?:RSA |EC |DSA |OPENSSH |ENCRYPTED |PGP |)PRIVATE KEY-----`,
)

// Redact removes all PEM private-key blocks from the input. It strips the
// header, base64 body, and footer of each private key block, replacing the
// block with a single line: `[REDACTED PRIVATE KEY]`.
//
// REQ-035: `orca cert show` MUST NOT print private key material, in either
// the default text or --json output. This helper is the single source of
// truth for that guarantee — call it on any PEM blob before display.
//
// The function is conservative: if the input contains no private key
// blocks, the input is returned unchanged (other than a copy). Errors are
// only returned for impossible states (e.g., a nil pattern hit, which
// can't happen in practice).
func Redact(pem []byte) []byte {
	if len(pem) == 0 {
		return pem
	}
	// Find all header positions.
	matches := privateKeyBlockRe.FindAllIndex(pem, -1)
	if len(matches) == 0 {
		// No private key blocks — return a defensive copy.
		out := make([]byte, len(pem))
		copy(out, pem)
		return out
	}

	// Process each block: locate the matching footer "-----END ... PRIVATE KEY-----"
	// and replace the entire block. Multiple matches possible.
	type span struct{ start, end int }
	spans := make([]span, 0, len(matches))
	for _, m := range matches {
		headerStart := m[0]
		// Find footer starting after the header.
		footerStart := findPrivateKeyFooter(pem[headerStart:])
		if footerStart < 0 {
			// Malformed PEM — leave the input alone for safety. The caller
			// will likely surface the parse error elsewhere.
			continue
		}
		end := headerStart + footerStart + len("-----END (any) PRIVATE KEY-----")
		// We don't know the exact footer length; use bytes.Index for it.
		if exactEnd := exactFooterEnd(pem[headerStart:]); exactEnd > 0 {
			end = headerStart + exactEnd
		}
		spans = append(spans, span{headerStart, end})
	}
	if len(spans) == 0 {
		out := make([]byte, len(pem))
		copy(out, pem)
		return out
	}

	// Build output: segments between spans + redaction marker.
	var out bytes.Buffer
	prev := 0
	for _, s := range spans {
		out.Write(pem[prev:s.start])
		out.WriteString("[REDACTED PRIVATE KEY]\n")
		prev = s.end
	}
	out.Write(pem[prev:])
	return out.Bytes()
}

// findPrivateKeyFooter returns the offset of the footer for a private key
// block whose header starts at pem[0]. Returns -1 if not found.
func findPrivateKeyFooter(pem []byte) int {
	re := regexp.MustCompile(`-----END (?:RSA |EC |DSA |OPENSSH |ENCRYPTED |PGP |)PRIVATE KEY-----`)
	loc := re.FindIndex(pem)
	if loc == nil {
		return -1
	}
	return loc[0]
}

// exactFooterEnd returns the offset just past the footer line's newline (or
// end-of-input if no trailing newline). Returns -1 if no footer is found.
func exactFooterEnd(pem []byte) int {
	re := regexp.MustCompile(`-----END (?:RSA |EC |DSA |OPENSSH |ENCRYPTED |PGP |)PRIVATE KEY-----\r?\n?`)
	loc := re.FindIndex(pem)
	if loc == nil {
		return -1
	}
	return loc[1]
}

// Sentinel to silence the "imported and not used" check if a future
// refactor removes all consumers of errors. Currently errors is imported
// only transitively, so keep this var to anchor the package.
var _ = errors.New
