package smtpserver

import (
	"fmt"

	"github.com/mjl-/mox/message"
)

// ../rfc/8601:577

// Authentication-Results header, see RFC 8601.
type AuthResults struct {
	Hostname string
	Comment  string // If not empty, header comment without "()", added after Hostname.
	Methods  []AuthMethod
}

// ../rfc/8601:598

// AuthMethod is a result for one authentication method.
//
// Example encoding in the header: "spf=pass smtp.mailfrom=example.net".
type AuthMethod struct {
	// E.g. "dkim", "spf", "iprev", "auth".
	Method  string
	Result  string // Each method has a set of known values, e.g. "pass", "temperror", etc.
	Comment string // Optional, message header comment.
	Reason  string // Optional.
	Props   []AuthProp
}

// ../rfc/8601:606

// AuthProp describes properties for an authentication method.
// Each method has a set of known properties.
// Encoded in the header as "type.property=value", e.g. "smtp.mailfrom=example.net"
// for spf.
type AuthProp struct {
	// Valid values maintained at https://www.iana.org/assignments/email-auth/email-auth.xhtml
	Type     string
	Property string
	Value    string
	// Whether value is address-like (localpart@domain, or domain). Or another value,
	// which is subject to escaping.
	IsAddrLike bool
	Comment    string // If not empty, header comment withtout "()", added after Value.
}

// todo future: we could store fields as dns.Domain, and when we encode as non-ascii also add the ascii version as a comment.

// Header returns an Authentication-Results header, possibly spanning multiple
// lines, always ending in crlf.
func (h AuthResults) Header() string {
	// Escaping of values: ../rfc/8601:684 ../rfc/2045:661

	optComment := func(s string) string {
		if s != "" {
			return " (" + s + ")"
		}
		return s
	}

	w := &message.HeaderWriter{}
	w.Add("", "Authentication-Results:"+optComment(h.Comment)+" "+value(h.Hostname)+";")
	for i, m := range h.Methods {
		tokens := []string{}
		addf := func(format string, args ...any) {
			s := fmt.Sprintf(format, args...)
			tokens = append(tokens, s)
		}
		addf("%s=%s", m.Method, m.Result)
		if m.Comment != "" && (m.Reason != "" || len(m.Props) > 0) {
			addf("(%s)", m.Comment)
		}
		if m.Reason != "" {
			addf("reason=%s", value(m.Reason))
		}
		for _, p := range m.Props {
			v := p.Value
			if !p.IsAddrLike {
				v = value(v)
			}
			addf("%s.%s=%s%s", p.Type, p.Property, v, optComment(p.Comment))
		}
		for j, t := range tokens {
			if j == len(tokens)-1 && i < len(h.Methods)-1 {
				t += ";"
			}
			w.Add(" ", t)
		}
	}
	return w.String()
}

func value(s string) string {
	quote := s == ""
	for _, c := range s {
		// utf-8 does not have to be quoted. ../rfc/6532:242
		if c == '"' || c == '\\' || c <= ' ' || c == 0x7f {
			quote = true
			break
		}
	}
	if !quote {
		return s
	}
	r := `"`
	for _, c := range s {
		if c == '"' || c == '\\' {
			r += "\\"
		}
		r += string(c)
	}
	r += `"`
	return r
}
