package smtpserver

import (
	"net/smtp"
	"strings"
	"testing"

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

// newTestBackendWithMailbox extends newTestBackend with a live Mailstore and one
// mailbox, inbox@example.com, on the same verified example.com domain used by the
// rest of this package's tests.
func newTestBackendWithMailbox(t *testing.T) (*Backend, int64) {
	t.Helper()
	backend := newTestBackend(t)

	store := mailstore.New(backend.DB, mailstore.GenerateDEK(), t.TempDir())
	backend.Mailstore = store
	backend.Relay.Mailstore = store // mirrors main.go's wiring, needed for SendBounce's local-delivery shortcut
	// Spam/SPF/DNSBL checks make live DNS calls (see internal/mailstore) — deliberately
	// so in production, but that makes their exact score environment-dependent (e.g. a
	// resolver that hijacks NXDOMAIN, or a real SPF record on the test domain). These
	// tests exercise local-delivery wiring, not spam-scoring accuracy (see
	// internal/mailstore's own tests for that), so disable rejection entirely here.
	backend.Cfg.Section("Mailstore").Key("spam_reject_score").SetValue("1000000")

	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("inbox@example.com", hash, 1, 5*1024*1024*1024, wrapped, nonce)
	if err != nil {
		t.Fatal(err)
	}
	return backend, mailboxID
}

func TestLocalDeliveryToKnownMailbox(t *testing.T) {
	backend, mailboxID := newTestBackendWithMailbox(t)
	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("expected RCPT to a real local mailbox to succeed, got: %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 for local delivery, got: %v", err)
	}

	mbox, err := backend.DB.GetMailboxByID(mailboxID)
	if err != nil {
		t.Fatal(err)
	}
	if mbox.UsedBytes == 0 {
		t.Fatal("expected mailbox used_bytes to increase after local delivery")
	}
}

// TestLocalDeliveryCachesFromHeaderDisplayName confirms cached_from is the message's
// own From: header (e.g. "Bob Marley <bob@example.com>"), not the bare SMTP envelope
// address — the envelope rarely carries a display name, but the header usually does,
// and webmail's folder list wants the display name to show.
func TestLocalDeliveryCachesFromHeaderDisplayName(t *testing.T) {
	backend, mailboxID := newTestBackendWithMailbox(t)
	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: %v", err)
	}
	w, err := c.Data()
	if err != nil {
		t.Fatal(err)
	}
	if _, err := w.Write([]byte("From: Bob Marley <test@example.com>\r\nSubject: hello\r\n\r\nhi there")); err != nil {
		t.Fatal(err)
	}
	if err := w.Close(); err != nil {
		t.Fatalf("DATA: %v", err)
	}

	msgs, err := backend.DB.ListMessagesInFolder(mailboxID, "INBOX")
	if err != nil {
		t.Fatal(err)
	}
	if len(msgs) != 1 {
		t.Fatalf("expected 1 message, got %d", len(msgs))
	}
	if msgs[0].CachedFrom != "Bob Marley <test@example.com>" {
		t.Errorf("cached_from = %q, want the From: header value with display name", msgs[0].CachedFrom)
	}
}

func TestLocalDeliveryUnknownMailboxRejected(t *testing.T) {
	backend, _ := newTestBackendWithMailbox(t)
	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)
	}
	err = c.Rcpt("nobody@example.com")
	if err == nil {
		t.Fatal("expected RCPT to an unknown address on a locally-configured domain to be rejected")
	}
	if !strings.Contains(err.Error(), "550") {
		t.Fatalf("expected 550 response, got: %v", err)
	}
}

func TestExternalSenderCanOnlyDeliverLocally(t *testing.T) {
	backend, _ := newTestBackendWithMailbox(t)
	addr := startTestServer(t, backend)

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

	// No AUTH, no whitelist match: MAIL FROM an entirely unconfigured external
	// domain must be provisionally accepted (Mailstore is enabled) ...
	if err := c.Mail("someone@external.example"); err != nil {
		t.Fatalf("expected MAIL FROM from an external domain to be provisionally accepted, got: %v", err)
	}
	// ... but RCPT to an external address must still be denied (not an open relay).
	err = c.Rcpt("other@somewhere-else.example")
	if err == nil {
		t.Fatal("expected RCPT to an external address to be denied for an unauthorized sender")
	}
	if !strings.Contains(err.Error(), "550") {
		t.Fatalf("expected 550 response, got: %v", err)
	}

	// RCPT to our local mailbox must still succeed for the same provisionally
	// accepted sender — this is the whole point of accepting it.
	if err := c.Rcpt("inbox@example.com"); err != nil {
		t.Fatalf("expected RCPT to a local mailbox to succeed for an external sender, got: %v", err)
	}
}

func TestAuthorizedSenderStillRelaysExternallyWithMailstoreEnabled(t *testing.T) {
	backend, _ := newTestBackendWithMailbox(t)
	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("someone@elsewhere.example"); err != nil {
		t.Fatalf("expected an authorized sender's relay RCPT to still be accepted with Mailstore enabled, got: %v", err)
	}
}
