package webui

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

	"mailgoserver/internal/pgp"
)

// parseRawAsPGPEntity is a test-only helper that reads a stored raw RFC822
// message's Content-Type header and body into a pgp.Entity — mirrors
// parseRawAsEntity (webmail_smime_compose_test.go) for the PGP side.
func parseRawAsPGPEntity(t *testing.T, raw []byte) pgp.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 := pgp.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
}

// genPGPIdentity generates and stores a passphrase-protected PGP identity for a
// mailbox directly (bypassing HTTP — the identity-management HTTP flow itself is
// covered in webmail_pgp_test.go), returning its ID.
func genPGPIdentity(t *testing.T, app *App, mailboxID int64, email, passphrase string) int64 {
	t.Helper()
	pubArmor, privArmor, err := pgp.GenerateKeyPair(email, passphrase)
	if err != nil {
		t.Fatal(err)
	}
	if err := app.storePGPIdentity(mailboxID, "", email, pubArmor, privArmor); err != nil {
		t.Fatal(err)
	}
	identities, err := app.DB.ListPGPIdentities(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
}

// TestWebmailComposePGPEncryptSend confirms checking "Encrypt" alone — no PGP key
// unlocked, no passphrase submitted — still works, since encrypting only ever needs
// public keys. The message only the recipient (or the sender's own Sent copy) can
// decrypt, and plaintext never appears on the wire.
func TestWebmailComposePGPEncryptSend(t *testing.T) {
	app := newTestApp(t)
	mux := app.Mux()
	domains, _ := app.DB.ListDomains()
	domainID := domains[0].ID

	senderID := createTestMailboxWithPassword(t, app, "penc-sender@example.com", domainID, "sender-password-1!")
	recipientID := createTestMailboxWithPassword(t, app, "penc-recip@example.com", domainID, "recip-password-1!")
	senderIdentityID := genPGPIdentity(t, app, senderID, "penc-sender@example.com", "sender passphrase")
	recipIdentityID := genPGPIdentity(t, app, recipientID, "penc-recip@example.com", "recipient passphrase")

	senderIdentity, err := app.DB.GetPGPIdentity(senderID, senderIdentityID)
	if err != nil || senderIdentity == nil {
		t.Fatal(err)
	}
	recipIdentity, err := app.DB.GetPGPIdentity(recipientID, recipIdentityID)
	if err != nil || recipIdentity == nil {
		t.Fatal(err)
	}
	if err := app.DB.UpsertPGPContact(senderID, "penc-recip@example.com", "", recipIdentity.Fingerprint, recipIdentity.PublicKeyArmor); err != nil {
		t.Fatal(err)
	}
	recipContact, err := app.DB.GetPGPContact(senderID, "penc-recip@example.com")
	if err != nil || recipContact == nil {
		t.Fatal(err)
	}

	cookie := webmailLoginSession(t, app, senderID)
	form := url.Values{
		"to": {"penc-recip@example.com"}, "subject": {"Secret"}, "body_html": {"the launch code is 1234"},
		"pgp_encrypt": {"1"}, "pgp_recipient_id": {strconv.FormatInt(recipContact.ID, 10)},
	}
	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)
	}
	if bytes.Contains(raw, []byte("launch code")) {
		t.Fatal("plaintext leaked into the stored encrypted message")
	}

	entity := parseRawAsPGPEntity(t, raw)
	recipParsedIdentity, err := pgp.ParsePrivateKey([]byte(recipIdentity.PrivateKeyArmor))
	if err != nil {
		t.Fatal(err)
	}
	if err := pgp.UnlockPrivateKey(recipParsedIdentity, "recipient passphrase"); err != nil {
		t.Fatal(err)
	}
	decrypted, err := pgp.DecryptEntity(entity, recipParsedIdentity)
	if err != nil {
		t.Fatalf("recipient DecryptEntity: %v", err)
	}
	if !bytes.Contains(decrypted.Body, []byte("launch code")) {
		t.Fatalf("expected plaintext after decrypt, got %q", decrypted.Body)
	}

	// Sender's own Sent copy must also decrypt, with the sender's own key — proving
	// encrypt-only (no passphrase involved at compose time) still included the
	// sender's own public key as a recipient.
	sentMsgs, err := app.DB.ListMessagesInFolder(senderID, "Sent")
	if err != nil || len(sentMsgs) != 1 {
		t.Fatalf("expected 1 sent message, got %d (err=%v)", len(sentMsgs), err)
	}
	sentRaw, err := app.Mailstore.FetchMessage(senderID, sentMsgs[0].ID)
	if err != nil {
		t.Fatal(err)
	}
	senderParsedIdentity, err := pgp.ParsePrivateKey([]byte(senderIdentity.PrivateKeyArmor))
	if err != nil {
		t.Fatal(err)
	}
	if err := pgp.UnlockPrivateKey(senderParsedIdentity, "sender passphrase"); err != nil {
		t.Fatal(err)
	}
	sentEntity := parseRawAsPGPEntity(t, sentRaw)
	sentDecrypted, err := pgp.DecryptEntity(sentEntity, senderParsedIdentity)
	if err != nil {
		t.Fatalf("sender DecryptEntity of own Sent copy: %v", err)
	}
	if !bytes.Contains(sentDecrypted.Body, []byte("launch code")) {
		t.Fatal("sender's own Sent copy did not decrypt to the original body")
	}
}

// TestWebmailComposePGPEncryptDoesNotLogPlaintext confirms the admin-visible email
// log doesn't capture the plaintext body of a PGP-encrypted send.
func TestWebmailComposePGPEncryptDoesNotLogPlaintext(t *testing.T) {
	app := newTestApp(t)
	mux := app.Mux()
	domains, _ := app.DB.ListDomains()
	domainID := domains[0].ID

	senderID := createTestMailboxWithPassword(t, app, "penc-sender3@example.com", domainID, "sender-password-1!")
	recipientID := createTestMailboxWithPassword(t, app, "penc-recip3@example.com", domainID, "recip-password-1!")
	genPGPIdentity(t, app, senderID, "penc-sender3@example.com", "sender passphrase")
	recipIdentityID := genPGPIdentity(t, app, recipientID, "penc-recip3@example.com", "recipient passphrase")
	recipIdentity, err := app.DB.GetPGPIdentity(recipientID, recipIdentityID)
	if err != nil || recipIdentity == nil {
		t.Fatal(err)
	}
	if err := app.DB.UpsertPGPContact(senderID, "penc-recip3@example.com", "", recipIdentity.Fingerprint, recipIdentity.PublicKeyArmor); err != nil {
		t.Fatal(err)
	}
	recipContact, err := app.DB.GetPGPContact(senderID, "penc-recip3@example.com")
	if err != nil || recipContact == nil {
		t.Fatal(err)
	}

	cookie := webmailLoginSession(t, app, senderID)
	const secretPhrase = "nuclear launch codes are 00000000"
	form := url.Values{
		"to": {"penc-recip3@example.com"}, "subject": {"Top secret"}, "body_html": {secretPhrase},
		"pgp_encrypt": {"1"}, "pgp_recipient_id": {strconv.FormatInt(recipContact.ID, 10)},
	}
	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())
	}

	logs, err := app.DB.ListEmailLogsPage(0, 10)
	if err != nil {
		t.Fatal(err)
	}
	found := false
	for _, l := range logs {
		if l.Subject != "Top secret" {
			continue
		}
		found = true
		if strings.Contains(l.MessageBody, secretPhrase) {
			t.Fatalf("plaintext leaked into the admin email log: %q", l.MessageBody)
		}
	}
	if !found {
		t.Fatal("expected the send to appear in the email log (just without the plaintext body)")
	}
}

// TestWebmailComposePGPEncryptNoRecipientPickedFails confirms encryption is refused
// (not silently skipped) when "Encrypt" is checked but no recipient key was picked
// from the dropdown — the picker replaced address-based auto-matching, so there's no
// implicit recipient to fall back to.
func TestWebmailComposePGPEncryptNoRecipientPickedFails(t *testing.T) {
	app := newTestApp(t)
	mux := app.Mux()
	domains, _ := app.DB.ListDomains()
	domainID := domains[0].ID

	senderID := createTestMailboxWithPassword(t, app, "penc-sender2@example.com", domainID, "sender-password-1!")
	recipientID := createTestMailboxWithPassword(t, app, "penc-recip2@example.com", domainID, "recip-password-1!")
	genPGPIdentity(t, app, senderID, "penc-sender2@example.com", "sender passphrase")
	// No contact key added, and no pgp_recipient_id submitted either.

	cookie := webmailLoginSession(t, app, senderID)
	form := url.Values{
		"to": {"penc-recip2@example.com"}, "subject": {"x"}, "body_html": {"x"},
		"pgp_encrypt": {"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.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)
	}
}

// TestWebmailComposePGPEncryptPickedContactFromAnotherMailboxFails confirms a
// mailbox can't encrypt to a contact ID it doesn't own — GetPGPContactByID is scoped
// per mailbox the same way every other identity/contact lookup is.
func TestWebmailComposePGPEncryptPickedContactFromAnotherMailboxFails(t *testing.T) {
	app := newTestApp(t)
	mux := app.Mux()
	domains, _ := app.DB.ListDomains()
	domainID := domains[0].ID

	senderID := createTestMailboxWithPassword(t, app, "penc-sender5@example.com", domainID, "sender-password-1!")
	otherMailboxID := createTestMailboxWithPassword(t, app, "penc-other5@example.com", domainID, "other-password-1!")
	recipientID := createTestMailboxWithPassword(t, app, "penc-recip5@example.com", domainID, "recip-password-1!")
	genPGPIdentity(t, app, senderID, "penc-sender5@example.com", "sender passphrase")
	recipIdentityID := genPGPIdentity(t, app, recipientID, "penc-recip5@example.com", "recipient passphrase")
	recipIdentity, err := app.DB.GetPGPIdentity(recipientID, recipIdentityID)
	if err != nil || recipIdentity == nil {
		t.Fatal(err)
	}
	// The contact is filed under a different mailbox than the sender.
	if err := app.DB.UpsertPGPContact(otherMailboxID, "penc-recip5@example.com", "", recipIdentity.Fingerprint, recipIdentity.PublicKeyArmor); err != nil {
		t.Fatal(err)
	}
	otherContact, err := app.DB.GetPGPContact(otherMailboxID, "penc-recip5@example.com")
	if err != nil || otherContact == nil {
		t.Fatal(err)
	}

	cookie := webmailLoginSession(t, app, senderID)
	form := url.Values{
		"to": {"penc-recip5@example.com"}, "subject": {"x"}, "body_html": {"x"},
		"pgp_encrypt": {"1"}, "pgp_recipient_id": {strconv.FormatInt(otherContact.ID, 10)},
	}
	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.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)
	}
}

// TestWebmailComposePGPEncryptWithoutOwnKeyFails confirms encrypting requires the
// sender's own PGP key too (so the Sent copy stays readable) — not just the
// recipient's.
func TestWebmailComposePGPEncryptWithoutOwnKeyFails(t *testing.T) {
	app := newTestApp(t)
	mux := app.Mux()
	domains, _ := app.DB.ListDomains()
	domainID := domains[0].ID

	senderID := createTestMailboxWithPassword(t, app, "penc-sender4@example.com", domainID, "sender-password-1!")
	recipientID := createTestMailboxWithPassword(t, app, "penc-recip4@example.com", domainID, "recip-password-1!")
	recipIdentityID := genPGPIdentity(t, app, recipientID, "penc-recip4@example.com", "recipient passphrase")
	recipIdentity, err := app.DB.GetPGPIdentity(recipientID, recipIdentityID)
	if err != nil || recipIdentity == nil {
		t.Fatal(err)
	}
	if err := app.DB.UpsertPGPContact(senderID, "penc-recip4@example.com", "", recipIdentity.Fingerprint, recipIdentity.PublicKeyArmor); err != nil {
		t.Fatal(err)
	}
	// Sender has no PGP identity of their own.

	cookie := webmailLoginSession(t, app, senderID)
	form := url.Values{
		"to": {"penc-recip4@example.com"}, "subject": {"x"}, "body_html": {"x"},
		"pgp_encrypt": {"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.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)
	}
}
