package smtpserver

import (
	"strings"
	"testing"
)

func TestEnsureRequiredHeadersFixedOrder(t *testing.T) {
	raw := "Subject: hi\r\nX-Custom: drop-me\r\n\r\nbody text"
	out := ensureRequiredHeaders(raw, "msg123@host", []string{"rcpt@example.com"}, "from@example.com", nil)

	headerBlock, body := splitHeadersBody(out)
	var names []string
	for _, line := range strings.Split(headerBlock, "\r\n") {
		if line == "" {
			continue
		}
		names = append(names, strings.SplitN(line, ":", 2)[0])
	}
	want := []string{"Message-ID", "Date", "MIME-Version", "To", "From", "Subject", "Content-Type", "Content-Transfer-Encoding"}
	if len(names) != len(want) {
		t.Fatalf("header names = %v, want %v", names, want)
	}
	for i := range want {
		if names[i] != want[i] {
			t.Errorf("header[%d] = %q, want %q", i, names[i], want[i])
		}
	}
	if strings.Contains(headerBlock, "X-Custom") {
		t.Error("unwhitelisted header X-Custom should have been dropped, not carried through")
	}
	if !strings.Contains(headerBlock, "To: rcpt@example.com") {
		t.Error("missing To header should be synthesized from envelope recipients")
	}
	if body != "body text" {
		t.Errorf("body = %q, want %q", body, "body text")
	}
}

// TestEnsureRequiredHeadersKeepsFirstDuplicateContentType guards against a regression
// where a duplicated Content-Type header (e.g. swaks emitting its own
// "multipart/mixed; boundary=..." for an --attach message, followed by a
// user-supplied "--add-header Content-Type: text/html") had its *last* occurrence win
// instead of its first. Losing the multipart boundary here meant the raw multipart
// body — untouched by this rebuild — got delivered under a plain, non-multipart
// Content-Type, so the recipient's client rendered the boundary markers and base64
// attachment text as literal body content instead of a real attachment.
func TestEnsureRequiredHeadersKeepsFirstDuplicateContentType(t *testing.T) {
	raw := "Subject: hi\r\n" +
		"MIME-Version: 1.0\r\n" +
		"Content-Type: multipart/mixed; boundary=\"BOUND\"\r\n" +
		"Content-Type: text/html\r\n" +
		"\r\n" +
		"--BOUND\r\nContent-Type: text/plain\r\n\r\nbody\r\n--BOUND--\r\n"
	out := ensureRequiredHeaders(raw, "msg123@host", []string{"rcpt@example.com"}, "from@example.com", nil)

	headerBlock, _ := splitHeadersBody(out)
	if !strings.Contains(headerBlock, `Content-Type: multipart/mixed; boundary="BOUND"`) {
		t.Errorf("expected the first (multipart/boundary) Content-Type to win, got header block:\n%s", headerBlock)
	}
	if strings.Contains(headerBlock, "Content-Type: text/html") {
		t.Errorf("the later duplicate Content-Type: text/html should have been dropped, got header block:\n%s", headerBlock)
	}
}

func TestExtractMessageIDDoesNotCrashOnMalformedHeader(t *testing.T) {
	// No "@" in the Message-ID value — the fixed bug: Python's original crashes
	// here (UnboundLocalError); the Go port must fall back to a generated ID.
	raw := "Message-ID: not-an-id\r\nSubject: x\r\n\r\nbody"
	id := extractMessageID(raw, "mail.example.com")
	if id == "" {
		t.Fatal("expected a generated fallback Message-ID, got empty string")
	}
	if !strings.HasSuffix(id, "@mail.example.com") {
		t.Errorf("fallback Message-ID = %q, want suffix @mail.example.com", id)
	}
}

func TestExtractMessageIDRehostsOnHostnameMismatch(t *testing.T) {
	raw := "Message-ID: <abc123@other-host.com>\r\nSubject: x\r\n\r\nbody"
	id := extractMessageID(raw, "mail.example.com")
	if id != "abc123@mail.example.com" {
		t.Errorf("id = %q, want rehosted to mail.example.com", id)
	}
}

func TestExtractMessageIDKeepsMatchingHostname(t *testing.T) {
	raw := "Message-ID: <abc123@mail.example.com>\r\nSubject: x\r\n\r\nbody"
	id := extractMessageID(raw, "mail.example.com")
	if id != "abc123@mail.example.com" {
		t.Errorf("id = %q, want unchanged", id)
	}
}
