package dkim
import (
	"errors"
	"fmt"
	"regexp"
	"strings"
)
var (
	errNoBody = errors.New("no body found")
	errUnknownCanonicalization = errors.New("unknown canonicalization")
)
type canonicalization string
var (
	simpleCanonicalization  canonicalization = "simple"
	relaxedCanonicalization canonicalization = "relaxed"
)
func (c canonicalization) body(b string) string {
	switch c {
	case simpleCanonicalization:
		return simpleBody(b)
	case relaxedCanonicalization:
		return relaxBody(b)
	default:
		panic("unknown canonicalization")
	}
}
func (c canonicalization) headers(hs headers) headers {
	switch c {
	case simpleCanonicalization:
		return hs
	case relaxedCanonicalization:
		return relaxHeaders(hs)
	default:
		panic("unknown canonicalization")
	}
}
func (c canonicalization) header(h header) header {
	switch c {
	case simpleCanonicalization:
		return h
	case relaxedCanonicalization:
		return relaxHeader(h)
	default:
		panic("unknown canonicalization")
	}
}
func stringToCanonicalization(s string) (canonicalization, error) {
	switch s {
	case "simple":
		return simpleCanonicalization, nil
	case "relaxed":
		return relaxedCanonicalization, nil
	default:
		return "", fmt.Errorf("%w: %s", errUnknownCanonicalization, s)
	}
}
// Notes on whitespace reduction:
// https://datatracker.ietf.org/doc/html/rfc6376#section-2.8
// There are only 3 forms of whitespace:
//  - WSP  =  SP / HTAB
//    Simple whitespace: space or tab.
//  - LWSP =  *(WSP / CRLF WSP)
//    Linear whitespace: any number of { simple whitespace OR CRLF followed by
//    simple whitespace }.
//  - FWS  =  [*WSP CRLF] 1*WSP
//    Folding whitespace: optional { simple whitespace OR CRLF } followed by
//    one or more simple whitespace.
func simpleBody(body string) string {
	// https://datatracker.ietf.org/doc/html/rfc6376#section-3.4.3
	// Replace repeated CRLF at the end of the body with a single CRLF.
	body = repeatedCRLFAtTheEnd.ReplaceAllString(body, "\r\n")
	// Ensure a non-empty body ends with a single CRLF.
	// All bodies (including an empty one) must end with a CRLF.
	if !strings.HasSuffix(body, "\r\n") {
		body += "\r\n"
	}
	return body
}
var (
	// Continued header: WSP after CRLF.
	continuedHeader = regexp.MustCompile(`\r\n[ \t]+`)
	// WSP before CRLF.
	wspBeforeCRLF = regexp.MustCompile(`[ \t]+\r\n`)
	// Repeated WSP.
	repeatedWSP = regexp.MustCompile(`[ \t]+`)
	// Empty lines at the end of the body.
	repeatedCRLFAtTheEnd = regexp.MustCompile(`(\r\n)+$`)
)
func relaxBody(body string) string {
	// https://datatracker.ietf.org/doc/html/rfc6376#section-3.4.4
	body = wspBeforeCRLF.ReplaceAllLiteralString(body, "\r\n")
	body = repeatedWSP.ReplaceAllLiteralString(body, " ")
	body = repeatedCRLFAtTheEnd.ReplaceAllLiteralString(body, "\r\n")
	// Ensure a non-empty body ends with a single CRLF.
	if len(body) >= 1 && !strings.HasSuffix(body, "\r\n") {
		body += "\r\n"
	}
	return body
}
func relaxHeader(h header) header {
	// https://datatracker.ietf.org/doc/html/rfc6376#section-3.4.2
	// Convert all header field names to lowercase.
	name := strings.ToLower(h.Name)
	// Remove WSP before the ":" separating the name and value.
	name = strings.TrimRight(name, " \t")
	// Unfold continuation lines in values.
	value := continuedHeader.ReplaceAllString(h.Value, " ")
	// Reduce all sequences of WSP to a single SP.
	value = repeatedWSP.ReplaceAllLiteralString(value, " ")
	// Delete all WSP at the end of each unfolded header field value.
	value = strings.TrimRight(value, " \t")
	// Remove WSP after the ":" separating the name and value.
	value = strings.TrimLeft(value, " \t")
	return header{
		Name:  name,
		Value: value,
		// The "source" is the relaxed field: name, colon, and value (with
		// no space around the colon).
		Source: name + ":" + value,
	}
}
func relaxHeaders(hs headers) headers {
	rh := make(headers, 0, len(hs))
	for _, h := range hs {
		rh = append(rh, relaxHeader(h))
	}
	return rh
}