package outboundintentv1

import (
	"bytes"
	"encoding/json"
	"strings"
)

const (
	MailPayloadFormatV2  uint32 = 2
	MaxMailHTMLBodyBytes        = 1024 * 1024
)

// MailDeliveryV2 extends the immutable provider-neutral mail payload with
// recipient classes and an optional HTML alternative. V1 remains unchanged so
// already accepted intents continue to decode and replay byte-for-byte.
type MailDeliveryV2 struct {
	Version           uint32   `json:"version"`
	AuthorizedActorID string   `json:"authorizedActorId"`
	ToAddresses       []string `json:"toAddresses"`
	CcAddresses       []string `json:"ccAddresses,omitempty"`
	BccAddresses      []string `json:"bccAddresses,omitempty"`
	Subject           string   `json:"subject"`
	TextBody          string   `json:"textBody"`
	HTMLBody          string   `json:"htmlBody,omitempty"`
}

func DecodeMailDeliveryV2(intent Intent) (MailDeliveryV2, error) {
	if err := validateIntent(intent); err != nil || intent.Operation != OperationSendMail ||
		intent.PayloadFormatVersion != MailPayloadFormatV2 {
		return MailDeliveryV2{}, invalid()
	}
	decoder := json.NewDecoder(bytes.NewReader(intent.CanonicalPayload))
	decoder.DisallowUnknownFields()
	var payload MailDeliveryV2
	if decoder.Decode(&payload) != nil || requireJSONEOF(decoder) != nil ||
		validateMailDeliveryV2(payload) != nil {
		return MailDeliveryV2{}, invalid()
	}
	canonical, err := json.Marshal(payload)
	if err != nil || !bytes.Equal(canonical, intent.CanonicalPayload) {
		return MailDeliveryV2{}, invalid()
	}
	return payload, nil
}

func validateMailDeliveryV2(payload MailDeliveryV2) error {
	if payload.Version != MailPayloadFormatV2 || identifier("authorized actor ID", payload.AuthorizedActorID) != nil ||
		len(payload.ToAddresses) == 0 ||
		len(payload.ToAddresses)+len(payload.CcAddresses)+len(payload.BccAddresses) > MaxMailRecipients ||
		!boundedText(payload.Subject, MaxMailSubjectBytes, false) ||
		!boundedText(payload.TextBody, MaxMailTextBodyBytes, true) || payload.TextBody == "" ||
		(payload.HTMLBody != "" && !boundedText(payload.HTMLBody, MaxMailHTMLBodyBytes, true)) {
		return invalid()
	}
	seen := make(map[string]struct{}, len(payload.ToAddresses)+len(payload.CcAddresses)+len(payload.BccAddresses))
	for _, addresses := range [][]string{payload.ToAddresses, payload.CcAddresses, payload.BccAddresses} {
		for _, address := range addresses {
			if !bareMailAddress(address) {
				return invalid()
			}
			key := strings.ToLower(address)
			if _, exists := seen[key]; exists {
				return invalid()
			}
			seen[key] = struct{}{}
		}
	}
	return nil
}
