package smime

import (
	"bytes"
	"crypto/rand"
	"encoding/base64"
	"encoding/hex"
	"errors"
	"strings"
)

// Entity is a MIME entity: its own part-level headers (Content-Type,
// Content-Transfer-Encoding, Content-Disposition — never envelope headers like
// From/To/Subject/Date) plus its body. Sign/Encrypt/Decrypt/VerifySigned all operate
// on an Entity, not a flat raw RFC822 message — the caller (webui's compose/read
// handlers) is responsible for keeping envelope headers separate, since S/MIME only
// ever transforms the message body's own MIME entity, never the envelope.
type Entity struct {
	Headers []string
	Body    []byte
}

// bytes renders the entity as it would appear on the wire: headers, a blank line,
// then the body normalized to CRLF line endings — MIME's canonical form, which is
// what gets hashed/signed/encrypted. Both Sign and Encrypt must operate on exactly
// this rendering so a receiving client's own canonicalization matches ours.
func (e Entity) bytes() []byte {
	var buf bytes.Buffer
	for _, h := range e.Headers {
		buf.WriteString(h)
		buf.WriteString("\r\n")
	}
	buf.WriteString("\r\n")
	buf.Write(toCRLF(e.Body))
	return buf.Bytes()
}

// parseEntity splits raw bytes (headers, a blank line, then body) back into an
// Entity — used to recover the inner MIME entity after Decrypt or the signed part
// after VerifySigned, both of which hand back a full "headers+body" byte blob.
func parseEntity(raw []byte) (Entity, error) {
	idx := bytes.Index(raw, []byte("\r\n\r\n"))
	sep := 4
	if idx < 0 {
		idx = bytes.Index(raw, []byte("\n\n"))
		sep = 2
	}
	if idx < 0 {
		return Entity{Body: raw}, nil
	}
	var headers []string
	for _, line := range strings.Split(string(raw[:idx]), "\n") {
		line = strings.TrimRight(line, "\r")
		if line == "" {
			continue
		}
		headers = append(headers, line)
	}
	return Entity{Headers: headers, Body: raw[idx+sep:]}, nil
}

// toCRLF normalizes line endings to CRLF — first collapsing any existing CRLF to a
// bare LF so a mixed or already-CRLF input doesn't end up double-terminated.
func toCRLF(b []byte) []byte {
	b = bytes.ReplaceAll(b, []byte("\r\n"), []byte("\n"))
	return bytes.ReplaceAll(b, []byte("\n"), []byte("\r\n"))
}

// HeaderValue is a case-insensitive lookup over a MIME entity's header lines —
// exported so callers outside this package (e.g. webui's read-integration, which
// must inspect a message's Content-Type before deciding whether to unwrap it) don't
// need to reimplement it.
func HeaderValue(headers []string, name string) string {
	for _, h := range headers {
		idx := strings.Index(h, ":")
		if idx < 0 {
			continue
		}
		if strings.EqualFold(strings.TrimSpace(h[:idx]), name) {
			return strings.TrimSpace(h[idx+1:])
		}
	}
	return ""
}

func isBase64CTE(cte string) bool {
	return strings.EqualFold(strings.TrimSpace(cte), "base64")
}

func decodeBase64(data []byte) ([]byte, error) {
	return base64.StdEncoding.DecodeString(stripWhitespace(string(data)))
}

func stripWhitespace(s string) string {
	var b strings.Builder
	for _, r := range s {
		switch r {
		case ' ', '\t', '\r', '\n':
			continue
		default:
			b.WriteRune(r)
		}
	}
	return b.String()
}

func newBoundary() string {
	b := make([]byte, 16)
	rand.Read(b)
	return "----=_SMIME_" + hex.EncodeToString(b)
}

// wrapBase64 base64-encodes data at the RFC 2045-recommended 76 characters per line —
// cosmetic (a decoder doesn't care), but matches what every real MTA/MUA produces.
func wrapBase64(data []byte) string {
	encoded := base64.StdEncoding.EncodeToString(data)
	var b strings.Builder
	for i := 0; i < len(encoded); i += 76 {
		end := min(i+76, len(encoded))
		b.WriteString(encoded[i:end])
		b.WriteString("\r\n")
	}
	return strings.TrimRight(b.String(), "\r\n")
}

// splitMultipartRaw extracts each part's *exact* original bytes between boundary
// delimiters — deliberately not using mime/multipart.Reader, whose Part API parses
// headers away from the raw body and would require re-serializing them to recover
// signable bytes. A detached S/MIME signature covers the literal octets of the
// signed part (RFC 8551 §3.4.3), so reconstruction-from-parsed-headers risks a
// byte-for-byte mismatch (header order, casing, whitespace) that breaks verification
// even for semantically-identical content. Real S/MIME implementations extract raw
// byte ranges for exactly this reason.
func splitMultipartRaw(body []byte, boundary string) ([][]byte, error) {
	delim := []byte("--" + boundary)
	segments := bytes.Split(body, delim)
	if len(segments) < 3 {
		return nil, errors.New("smime: malformed multipart body")
	}
	// segments[0] is the preamble (ignored); the last segment starts with "--" (the
	// closing delimiter) and anything after is the epilogue (ignored). Everything in
	// between is one part, each still wrapped in the CRLF that separated it from its
	// boundary line.
	parts := make([][]byte, 0, len(segments)-2)
	for _, seg := range segments[1 : len(segments)-1] {
		seg = bytes.TrimPrefix(seg, []byte("\r\n"))
		seg = bytes.TrimSuffix(seg, []byte("\r\n"))
		parts = append(parts, seg)
	}
	return parts, nil
}
