// Package smime implements S/MIME certificate-based email signing and encryption:
// generating or importing a mailbox's own identity (certificate + private key),
// signing outbound mail (RFC 8551 multipart/signed, detached CMS SignedData),
// verifying a signature, encrypting outbound mail (application/pkcs7-mime, CMS
// EnvelopedData), and decrypting it again.
//
// This package is deliberately certificate-chain-agnostic: it does not validate a
// certificate against any CA trust store. A verified signature here means "this
// message was cryptographically signed by the private key matching this exact
// certificate," not "this certificate is trusted by a PKI" — the same posture this
// codebase's own self-signed TLS certificate already has. Callers that want to
// display a warning for unrecognized signers should compare the signer's certificate
// against their own address book (see the mailbox_smime_contacts table in
// internal/db), not chain validation.
package smime

import (
	"crypto"
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"errors"
	"fmt"
	"math/big"
	"time"

	"go.mozilla.org/pkcs7"
	pkcs12 "software.sslmate.com/src/go-pkcs12"
)

func init() {
	// go.mozilla.org/pkcs7 defaults ContentEncryptionAlgorithm to legacy DES-CBC for
	// backward compatibility with old clients — AES-256-GCM is the only acceptable
	// choice for anything generated here.
	pkcs7.ContentEncryptionAlgorithm = pkcs7.EncryptionAlgorithmAES256GCM
}

// DefaultValidity mirrors a typical S/MIME certificate lifetime (1 year), matching
// what most CAs issue for individual email certificates.
const DefaultValidity = 365 * 24 * time.Hour

// GenerateSelfSigned creates a fresh RSA-2048 keypair and a self-signed certificate
// scoped to email — same key size internal/tlsutil already uses for the server's own
// TLS certificate. KeyUsage/ExtKeyUsage/EmailAddresses are set per RFC 8551 so
// mainstream mail clients recognize it as a valid S/MIME certificate.
func GenerateSelfSigned(email string, validity time.Duration) (certPEM, keyPEM []byte, err error) {
	priv, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		return nil, nil, fmt.Errorf("smime: generate key: %w", err)
	}
	serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
	if err != nil {
		return nil, nil, fmt.Errorf("smime: generate serial: %w", err)
	}
	subject := pkix.Name{CommonName: email}
	template := x509.Certificate{
		SerialNumber:          serial,
		Subject:               subject,
		Issuer:                subject,
		NotBefore:             time.Now(),
		NotAfter:              time.Now().Add(validity),
		SignatureAlgorithm:    x509.SHA256WithRSA,
		KeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageEmailProtection},
		EmailAddresses:        []string{email},
		BasicConstraintsValid: true,
	}
	certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
	if err != nil {
		return nil, nil, fmt.Errorf("smime: create certificate: %w", err)
	}
	keyDER, err := x509.MarshalPKCS8PrivateKey(priv)
	if err != nil {
		return nil, nil, fmt.Errorf("smime: marshal private key: %w", err)
	}
	certPEM = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
	keyPEM = pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyDER})
	return certPEM, keyPEM, nil
}

// ImportPKCS12 parses a .p12/.pfx bundle — the usual export format from a CA or
// another mail client — into a certificate + private key. Only RSA keys are
// supported (the only key type go.mozilla.org/pkcs7's encrypt/decrypt operations
// actually support for key transport).
func ImportPKCS12(data []byte, password string) (certPEM, keyPEM []byte, err error) {
	priv, cert, err := pkcs12.Decode(data, password)
	if err != nil {
		return nil, nil, fmt.Errorf("smime: decode PKCS#12: %w", err)
	}
	rsaKey, ok := priv.(*rsa.PrivateKey)
	if !ok {
		return nil, nil, errors.New("smime: only RSA keys are supported")
	}
	keyDER, err := x509.MarshalPKCS8PrivateKey(rsaKey)
	if err != nil {
		return nil, nil, fmt.Errorf("smime: marshal private key: %w", err)
	}
	certPEM = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})
	keyPEM = pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyDER})
	return certPEM, keyPEM, nil
}

// ParseCertPEM decodes a stored certificate back into a usable *x509.Certificate.
func ParseCertPEM(certPEM []byte) (*x509.Certificate, error) {
	block, _ := pem.Decode(certPEM)
	if block == nil {
		return nil, errors.New("smime: invalid certificate PEM")
	}
	return x509.ParseCertificate(block.Bytes)
}

// ParseKeyPEM decodes a stored private key back into a usable crypto.PrivateKey.
// Tries PKCS#8 first (what GenerateSelfSigned/ImportPKCS12 both produce), falling
// back to PKCS#1 for a hand-imported PEM that used the older format.
func ParseKeyPEM(keyPEM []byte) (crypto.PrivateKey, error) {
	block, _ := pem.Decode(keyPEM)
	if block == nil {
		return nil, errors.New("smime: invalid private key PEM")
	}
	if key, err := x509.ParsePKCS8PrivateKey(block.Bytes); err == nil {
		return key, nil
	}
	if key, err := x509.ParsePKCS1PrivateKey(block.Bytes); err == nil {
		return key, nil
	}
	return nil, errors.New("smime: unrecognized private key format")
}
