package smime

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

	"go.mozilla.org/pkcs7"
)

// Sign wraps entity in RFC 8551 multipart/signed: part 1 is the entity's own
// canonical bytes (unmodified — this is what the signature covers), part 2 is a
// detached CMS SignedData over those same bytes.
func Sign(entity Entity, cert *x509.Certificate, key crypto.PrivateKey) (Entity, error) {
	content := entity.bytes()

	sd, err := pkcs7.NewSignedData(content)
	if err != nil {
		return Entity{}, fmt.Errorf("smime: sign: %w", err)
	}
	sd.SetDigestAlgorithm(pkcs7.OIDDigestAlgorithmSHA256)
	if err := sd.AddSigner(cert, key, pkcs7.SignerInfoConfig{}); err != nil {
		return Entity{}, fmt.Errorf("smime: sign: %w", err)
	}
	sd.Detach()
	sigDER, err := sd.Finish()
	if err != nil {
		return Entity{}, fmt.Errorf("smime: sign: %w", err)
	}

	boundary := newBoundary()
	body := make([]byte, 0, len(content)+len(sigDER)*2)
	body = append(body, []byte("--"+boundary+"\r\n")...)
	body = append(body, content...)
	body = append(body, []byte("\r\n--"+boundary+"\r\n")...)
	body = append(body, []byte("Content-Type: application/pkcs7-signature; name=\"smime.p7s\"\r\n")...)
	body = append(body, []byte("Content-Transfer-Encoding: base64\r\n")...)
	body = append(body, []byte("Content-Disposition: attachment; filename=\"smime.p7s\"\r\n\r\n")...)
	body = append(body, []byte(wrapBase64(sigDER))...)
	body = append(body, []byte("\r\n--"+boundary+"--\r\n")...)

	return Entity{
		Headers: []string{
			fmt.Sprintf(`Content-Type: multipart/signed; protocol="application/pkcs7-signature"; micalg=sha-256; boundary="%s"`, boundary),
		},
		Body: body,
	}, nil
}

// VerifySigned parses a multipart/signed entity produced by Sign (or any RFC
// 8551-compliant sender), checks the detached signature against the exact original
// bytes of part 1, and returns that inner entity plus the signer's certificate. On a
// signature mismatch it still returns the inner entity — so a tampered or
// unverifiable message can be shown with a warning rather than hidden — alongside a
// non-nil error and a nil signer.
func VerifySigned(entity Entity) (inner Entity, signer *x509.Certificate, err error) {
	ct := HeaderValue(entity.Headers, "Content-Type")
	mediaType, params, err := mime.ParseMediaType(ct)
	if err != nil || mediaType != "multipart/signed" {
		return Entity{}, nil, errors.New("smime: not a multipart/signed message")
	}
	boundary := params["boundary"]
	if boundary == "" {
		return Entity{}, nil, errors.New("smime: missing multipart boundary")
	}
	parts, err := splitMultipartRaw(entity.Body, boundary)
	if err != nil || len(parts) < 2 {
		return Entity{}, nil, errors.New("smime: malformed signed message")
	}
	signedContent := parts[0]

	sigPart, err := parseEntity(parts[1])
	if err != nil {
		return Entity{}, nil, err
	}
	sigDER := sigPart.Body
	if isBase64CTE(HeaderValue(sigPart.Headers, "Content-Transfer-Encoding")) {
		if sigDER, err = decodeBase64(sigPart.Body); err != nil {
			return Entity{}, nil, fmt.Errorf("smime: decode signature: %w", err)
		}
	}

	p7, err := pkcs7.Parse(sigDER)
	if err != nil {
		return Entity{}, nil, fmt.Errorf("smime: parse signature: %w", err)
	}
	p7.Content = signedContent

	inner, perr := parseEntity(signedContent)
	if perr != nil {
		return Entity{}, nil, perr
	}
	if err := p7.Verify(); err != nil {
		return inner, nil, fmt.Errorf("smime: signature verification failed: %w", err)
	}
	return inner, p7.GetOnlySigner(), nil
}
