package smtpserver

import (
	"net/smtp"
	"testing"

	"mailgoserver/internal/db"
	"mailgoserver/internal/mailstore"
)

// createTestMailboxWithQuota mirrors newTestBackendWithMailbox's inline mailbox setup,
// parameterized by quota so this file can create both a normal and an
// effectively-always-full mailbox.
func createTestMailboxWithQuota(t *testing.T, backend *Backend, store *mailstore.Store, email string, quotaBytes int64) int64 {
	t.Helper()
	dek := mailstore.GenerateDEK()
	wrapped, nonce, err := store.WrapDEK(dek)
	if err != nil {
		t.Fatal(err)
	}
	hash, err := db.HashPassword("portal-password-unused")
	if err != nil {
		t.Fatal(err)
	}
	mailboxID, err := backend.DB.CreateMailbox(email, hash, 1, quotaBytes, wrapped, nonce)
	if err != nil {
		t.Fatal(err)
	}
	return mailboxID
}

// TestPartialLocalDeliveryFailureBouncesAndAccepts confirms a multi-recipient
// transaction where one local mailbox accepts the message and another can't (quota
// exceeded, discovered only during DATA — RCPT can't catch it) is accepted (250, not
// 550 — the successful recipient already has it, so the client must not retry the
// whole transaction) and that the sender gets a bounce in their own mailbox describing
// the recipient that failed.
func TestPartialLocalDeliveryFailureBouncesAndAccepts(t *testing.T) {
	backend, okMailboxID := newTestBackendWithMailbox(t)
	store := backend.Mailstore

	fullMailboxID := createTestMailboxWithQuota(t, backend, store, "full@example.com", 1)
	senderMailboxID := createTestMailboxWithQuota(t, backend, store, "test@example.com", 5*1024*1024*1024)

	addr := startTestServer(t, backend)
	c, err := smtp.Dial(addr)
	if err != nil {
		t.Fatal(err)
	}
	defer c.Close()

	if err := c.Auth(smtp.PlainAuth("", "test@example.com", "testpass123", "127.0.0.1")); err != nil {
		t.Fatalf("auth: %v", err)
	}
	if err := c.Mail("test@example.com"); err != nil {
		t.Fatalf("MAIL FROM: %v", err)
	}
	if err := c.Rcpt("inbox@example.com"); err != nil {
		t.Fatalf("RCPT (ok mailbox): %v", err)
	}
	if err := c.Rcpt("full@example.com"); err != nil {
		t.Fatalf("RCPT (over-quota mailbox, still accepted at RCPT time): %v", err)
	}
	w, err := c.Data()
	if err != nil {
		t.Fatal(err)
	}
	if _, err := w.Write([]byte("Subject: hello\r\n\r\nhi there")); err != nil {
		t.Fatal(err)
	}
	if err := w.Close(); err != nil {
		t.Fatalf("expected DATA to succeed (250, partial success) despite one recipient failing, got: %v", err)
	}

	okMsgs, err := backend.DB.ListMessagesInFolder(okMailboxID, "INBOX")
	if err != nil {
		t.Fatal(err)
	}
	if len(okMsgs) != 1 {
		t.Fatalf("expected the message delivered to inbox@example.com, got %d messages", len(okMsgs))
	}

	fullMsgs, err := backend.DB.ListMessagesInFolder(fullMailboxID, "INBOX")
	if err != nil {
		t.Fatal(err)
	}
	if len(fullMsgs) != 0 {
		t.Fatalf("expected no message delivered to the over-quota mailbox, got %d", len(fullMsgs))
	}

	bounces, err := backend.DB.ListMessagesInFolder(senderMailboxID, "INBOX")
	if err != nil {
		t.Fatal(err)
	}
	if len(bounces) != 1 {
		t.Fatalf("expected 1 bounce message in the sender's own mailbox, got %d", len(bounces))
	}
	if bounces[0].CachedSubject != "Undelivered Mail Returned to Sender" {
		t.Errorf("bounce subject = %q", bounces[0].CachedSubject)
	}
}
