package webui

import (
	"bytes"
	"io"
	"mime/multipart"
	"net/http"
	"net/http/httptest"
	"net/mail"
	"net/url"
	"strconv"
	"strings"
	"testing"

	"mailgoserver/internal/smime"
)

// parseRawAsEntity is a test-only helper that reads a stored raw RFC822 message's
// Content-Type header and body into a smime.Entity — enough to feed into
// smime.VerifySigned/Decrypt without needing to export parseEntity from the smime
// package just for tests.
func parseRawAsEntity(t *testing.T, raw []byte) smime.Entity {
	t.Helper()
	msg, err := mail.ReadMessage(bytes.NewReader(raw))
	if err != nil {
		t.Fatalf("parse raw message: %v", err)
	}
	body, err := io.ReadAll(msg.Body)
	if err != nil {
		t.Fatalf("read body: %v", err)
	}
	entity := smime.Entity{Headers: []string{"Content-Type: " + msg.Header.Get("Content-Type")}, Body: body}
	if cte := msg.Header.Get("Content-Transfer-Encoding"); cte != "" {
		entity.Headers = append(entity.Headers, "Content-Transfer-Encoding: "+cte)
	}
	return entity
}

// genIdentity generates and stores an S/MIME identity for a mailbox directly
// (bypassing HTTP — the identity-management HTTP flow itself is covered separately
// in webmail_smime_test.go), returning its ID.
func genIdentity(t *testing.T, app *App, mailboxID int64, email string) int64 {
	t.Helper()
	certPEM, keyPEM, err := smime.GenerateSelfSigned(email, smime.DefaultValidity)
	if err != nil {
		t.Fatal(err)
	}
	if err := app.storeIdentity(mailboxID, certPEM, keyPEM); err != nil {
		t.Fatal(err)
	}
	identities, err := app.DB.ListSMIMEIdentities(mailboxID)
	if err != nil || len(identities) == 0 {
		t.Fatalf("expected the identity to be stored, err=%v", err)
	}
	return identities[0].ID // most recently created
}

// TestWebmailComposeSignSend confirms checking "Sign" produces a message the
// recipient (or anyone) can verify as genuinely from the sender — no passphrase
// involved, since S/MIME keys are stored plain in this codebase.
func TestWebmailComposeSignSend(t *testing.T) {
	app := newTestApp(t)
	mux := app.Mux()
	domains, _ := app.DB.ListDomains()
	domainID := domains[0].ID

	senderID := createTestMailboxWithPassword(t, app, "signer@example.com", domainID, "signer-password-1!")
	recipientID := createTestMailboxWithPassword(t, app, "signee@example.com", domainID, "signee-password-1!")
	genIdentity(t, app, senderID, "signer@example.com")
	cookie := webmailLoginSession(t, app, senderID)

	form := url.Values{
		"to": {"signee@example.com"}, "subject": {"Signed"}, "body_html": {"trust me"},
		"smime_sign": {"1"},
	}
	req := httptest.NewRequest(http.MethodPost, MailboxPrefix+"/mail/compose", strings.NewReader(form.Encode()))
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.AddCookie(cookie)
	rec := httptest.NewRecorder()
	mux.ServeHTTP(rec, req)
	if rec.Code != http.StatusFound {
		t.Fatalf("compose send: status=%d body=%s", rec.Code, rec.Body.String())
	}

	msgs, err := app.DB.ListMessagesInFolder(recipientID, "INBOX")
	if err != nil || len(msgs) != 1 {
		t.Fatalf("expected 1 message, got %d (err=%v)", len(msgs), err)
	}
	raw, err := app.Mailstore.FetchMessage(recipientID, msgs[0].ID)
	if err != nil {
		t.Fatal(err)
	}
	entity := parseRawAsEntity(t, raw)
	inner, signer, err := smime.VerifySigned(entity)
	if err != nil {
		t.Fatalf("VerifySigned: %v", err)
	}
	if signer.EmailAddresses[0] != "signer@example.com" {
		t.Fatalf("unexpected signer: %v", signer.EmailAddresses)
	}
	if !bytes.Contains(inner.Body, []byte("trust me")) {
		t.Fatalf("expected the body preserved inside the signed entity, got %q", inner.Body)
	}
}

// TestWebmailComposeSignedMessageWithAttachmentsShowsBody confirms a real body
// message text still renders on read alongside its attachments when the message is
// also S/MIME-signed — the exact combination (signed + multiple attachments + a
// typed body) that was never actually exercised by any test before this one
// (TestWebmailComposeSendMultipleAttachments checked attachments but not signing or
// the body text; the sign-only tests never included attachments).
func TestWebmailComposeSignedMessageWithAttachmentsShowsBody(t *testing.T) {
	app := newTestApp(t)
	mux := app.Mux()
	domains, _ := app.DB.ListDomains()
	domainID := domains[0].ID

	senderID := createTestMailboxWithPassword(t, app, "signedattach-sender@example.com", domainID, "sender-password-1!")
	recipientID := createTestMailboxWithPassword(t, app, "signedattach-recip@example.com", domainID, "recip-password-1!")
	genIdentity(t, app, senderID, "signedattach-sender@example.com")
	cookie := webmailLoginSession(t, app, senderID)

	var buf bytes.Buffer
	mw := multipart.NewWriter(&buf)
	mw.WriteField("to", "signedattach-recip@example.com")
	mw.WriteField("subject", "test message")
	mw.WriteField("body_html", "<p>this is my real message text</p>")
	mw.WriteField("smime_sign", "1")
	for _, name := range []string{"test.csv", "notes.md", "LICENSE"} {
		fw, err := mw.CreateFormFile("attachments", name)
		if err != nil {
			t.Fatal(err)
		}
		fw.Write([]byte("contents of " + name))
	}
	mw.Close()

	req := httptest.NewRequest(http.MethodPost, MailboxPrefix+"/mail/compose", &buf)
	req.Header.Set("Content-Type", mw.FormDataContentType())
	req.AddCookie(cookie)
	rec := httptest.NewRecorder()
	mux.ServeHTTP(rec, req)
	if rec.Code != http.StatusFound {
		t.Fatalf("compose send: status=%d body=%s", rec.Code, rec.Body.String())
	}

	msgs, err := app.DB.ListMessagesInFolder(recipientID, "INBOX")
	if err != nil || len(msgs) != 1 {
		t.Fatalf("expected 1 message, got %d (err=%v)", len(msgs), err)
	}

	recipientCookie := webmailLoginSession(t, app, recipientID)
	viewReq := httptest.NewRequest(http.MethodGet, MailboxPrefix+"/mail/INBOX/"+strconv.FormatInt(msgs[0].ID, 10), nil)
	viewReq.AddCookie(recipientCookie)
	viewRec := httptest.NewRecorder()
	mux.ServeHTTP(viewRec, viewReq)
	if viewRec.Code != http.StatusOK {
		t.Fatalf("view: status=%d body=%s", viewRec.Code, viewRec.Body.String())
	}
	body := viewRec.Body.String()
	if !strings.Contains(body, "this is my real message text") {
		t.Fatalf("expected the message body rendered, got: %s", body)
	}
	if strings.Contains(body, "empty message body") {
		t.Fatal("expected the body NOT reported as empty when real text was sent")
	}
	for _, name := range []string{"test.csv", "notes.md", "LICENSE"} {
		if !strings.Contains(body, name) {
			t.Errorf("expected attachment %q listed alongside the body", name)
		}
	}
}

// TestWebmailComposeSignWithoutIdentityFails confirms checking "Sign" with no
// identity on file fails cleanly instead of sending unsigned.
func TestWebmailComposeSignWithoutIdentityFails(t *testing.T) {
	app := newTestApp(t)
	mux := app.Mux()
	domains, _ := app.DB.ListDomains()
	domainID := domains[0].ID

	senderID := createTestMailboxWithPassword(t, app, "nokey@example.com", domainID, "nokey-password-1!")
	recipientID := createTestMailboxWithPassword(t, app, "recip4@example.com", domainID, "recip4-password-1!")
	cookie := webmailLoginSession(t, app, senderID)

	form := url.Values{
		"to": {"recip4@example.com"}, "subject": {"x"}, "body_html": {"x"},
		"smime_sign": {"1"},
	}
	req := httptest.NewRequest(http.MethodPost, MailboxPrefix+"/mail/compose", strings.NewReader(form.Encode()))
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.AddCookie(cookie)
	rec := httptest.NewRecorder()
	mux.ServeHTTP(rec, req)
	if rec.Code != http.StatusFound && rec.Code != http.StatusOK {
		t.Fatalf("status=%d", rec.Code)
	}

	msgs, err := app.DB.ListMessagesInFolder(recipientID, "INBOX")
	if err != nil || len(msgs) != 0 {
		t.Fatalf("expected no message delivered, got %d (err=%v)", len(msgs), err)
	}
}
