package relay

import (
	"fmt"
	"strings"
	"time"

	"mailgoserver/internal/mailstore"
	"mailgoserver/internal/toolbox"
)

// buildBounceMessage renders a simplified delivery-status notification: plain
// text/plain, not the full RFC 3464 multipart/report shape (a machine-readable
// message/delivery-status part), since every real mail client just shows the
// human-readable part anyway and this avoids a second MIME structure to maintain for
// something informational, not delivery-critical.
func buildBounceMessage(hostname, to, originalSubject, originalMessageID string, failed []Result) (content, messageID string) {
	messageID = toolbox.GenerateMessageID(hostname)
	var body strings.Builder
	body.WriteString("This is an automatically generated Delivery Status Notification.\r\n\r\n")
	body.WriteString("Delivery to the following recipient(s) failed permanently:\r\n\r\n")
	for _, f := range failed {
		reason := f.ErrorMessage
		if reason == "" {
			reason = f.ServerResponse
		}
		if reason == "" {
			reason = "unknown error"
		}
		body.WriteString(fmt.Sprintf("  - %s\r\n", f.Recipient))
		if f.ErrorCode != "" {
			body.WriteString(fmt.Sprintf("    Reason: %s (%s)\r\n\r\n", reason, f.ErrorCode))
		} else {
			body.WriteString(fmt.Sprintf("    Reason: %s\r\n\r\n", reason))
		}
	}
	body.WriteString("----- Original message -----\r\n")
	if originalSubject != "" {
		body.WriteString("Subject: " + originalSubject + "\r\n")
	}
	if originalMessageID != "" {
		body.WriteString("Message-ID: <" + originalMessageID + ">\r\n")
	}
	body.WriteString("\r\nThis is an automated message from " + hostname + " — please do not reply.\r\n")

	headers := []string{
		"Message-ID: <" + messageID + ">",
		"Date: " + time.Now().Format(time.RFC1123Z),
		"From: Mail Delivery System <mailer-daemon@" + hostname + ">",
		"To: " + to,
		"Subject: Undelivered Mail Returned to Sender",
		// Marks this as an automated notification (RFC 3834) so any auto-responder or
		// bounce-of-a-bounce logic on the receiving end knows not to reply to it —
		// same anti-loop convention SendBounce itself relies on for its own delivery.
		"Auto-Submitted: auto-replied",
		`Content-Type: text/plain; charset="UTF-8"`,
		"Content-Transfer-Encoding: 8bit",
		"MIME-Version: 1.0",
	}
	return strings.Join(headers, "\r\n") + "\r\n\r\n" + body.String(), messageID
}

// SendBounce notifies to that delivery failed for the recipients in failed, mirroring
// what a real MTA does when it can't express "delivered to some, not others" as a
// single SMTP response and has to accept-then-notify instead of reject-and-let-the-
// client's-own-MTA-bounce-it. A no-op if to is empty (this itself would be responding
// to a null-sender/already-bounced message — replying to those is the classic bounce-
// loop bug, so it's refused unconditionally, not left to the caller to remember) or if
// there's nothing failed to report.
//
// Delivery is local (straight into to's own INBOX, no SMTP round-trip) when to
// resolves to a mailbox this server hosts, otherwise it's relayed out exactly like any
// other outbound message — using a null reverse-path (empty MAIL FROM, i.e. the wire
// form "MAIL FROM:<>") so a failure bouncing the bounce itself can never recurse.
func (r *Relay) SendBounce(to, originalSubject, originalMessageID string, failed []Result) error {
	if to == "" || len(failed) == 0 {
		return nil
	}

	content, bounceMessageID := buildBounceMessage(r.Hostname, to, originalSubject, originalMessageID, failed)

	if r.Mailstore != nil {
		if mbox, err := r.Mailstore.ResolveRecipient(to); err == nil && mbox != nil {
			_, err := r.Mailstore.StoreMessage(mbox.ID, "INBOX", []byte(content), bounceMessageID,
				"Mail Delivery System <mailer-daemon@"+r.Hostname+">", "Undelivered Mail Returned to Sender")
			if err == mailstore.ErrQuotaExceeded {
				// Nothing sensible to do — the mailbox that would receive the bounce
				// is itself over quota. Drop it; the sender already saw an inline
				// error (webmail) or their own MTA is retrying (SMTP), so this isn't
				// the only signal they have.
				return nil
			}
			return err
		}
	}

	results := r.RelayEmailAsync("", []string{to}, content, []string{"to"})
	if len(results) > 0 && results[0].Status != "success" {
		return fmt.Errorf("bounce to %s: %s", to, results[0].ErrorMessage)
	}
	return nil
}
