package smime

import (
	"crypto"
	"crypto/x509"
	"errors"
	"fmt"
	"mime"
	"strings"

	"go.mozilla.org/pkcs7"
)

// Encrypt wraps entity's canonical bytes as CMS EnvelopedData addressed to
// recipients, per RFC 8551 application/pkcs7-mime; smime-type=enveloped-data. Pass
// every recipient's certificate, including the sender's own, so a copy kept in Sent
// stays readable.
func Encrypt(entity Entity, recipients []*x509.Certificate) (Entity, error) {
	if len(recipients) == 0 {
		return Entity{}, errors.New("smime: no recipient certificates provided")
	}
	envelopedDER, err := pkcs7.Encrypt(entity.bytes(), recipients)
	if err != nil {
		return Entity{}, fmt.Errorf("smime: encrypt: %w", err)
	}
	return Entity{
		Headers: []string{
			`Content-Type: application/pkcs7-mime; smime-type=enveloped-data; name="smime.p7m"`,
			"Content-Transfer-Encoding: base64",
			`Content-Disposition: attachment; filename="smime.p7m"`,
		},
		Body: []byte(wrapBase64(envelopedDER)),
	}, nil
}

// Decrypt reverses Encrypt, returning the inner MIME entity that was originally
// wrapped.
func Decrypt(entity Entity, cert *x509.Certificate, key crypto.PrivateKey) (Entity, error) {
	ct := HeaderValue(entity.Headers, "Content-Type")
	mediaType, params, err := mime.ParseMediaType(ct)
	if err != nil || mediaType != "application/pkcs7-mime" {
		return Entity{}, errors.New("smime: not an application/pkcs7-mime message")
	}
	if st := params["smime-type"]; st != "" && !strings.EqualFold(st, "enveloped-data") {
		return Entity{}, fmt.Errorf("smime: unsupported smime-type %q", st)
	}

	raw := entity.Body
	if isBase64CTE(HeaderValue(entity.Headers, "Content-Transfer-Encoding")) {
		if raw, err = decodeBase64(entity.Body); err != nil {
			return Entity{}, fmt.Errorf("smime: decode envelope: %w", err)
		}
	}

	p7, err := pkcs7.Parse(raw)
	if err != nil {
		return Entity{}, fmt.Errorf("smime: parse envelope: %w", err)
	}
	plaintext, err := p7.Decrypt(cert, key)
	if err != nil {
		return Entity{}, fmt.Errorf("smime: decrypt: %w", err)
	}
	return parseEntity(plaintext)
}
