package webui

import (
	"net/http"
	"net/http/httptest"
	"net/url"
	"strconv"
	"strings"
	"testing"
)

// TestWebmailReadPGPEncryptedMessageRequiresUnlockThenDecrypts confirms the full
// decrypt-then-view flow: opening a PGP-encrypted message the recipient hasn't
// unlocked yet shows a passphrase prompt (not the content), and submitting the
// actual /pgp/unlock form makes the same message decrypt and render on the next
// view.
func TestWebmailReadPGPEncryptedMessageRequiresUnlockThenDecrypts(t *testing.T) {
	app := newTestApp(t)
	mux := app.Mux()
	domains, _ := app.DB.ListDomains()
	domainID := domains[0].ID

	senderID := createTestMailboxWithPassword(t, app, "pread-enc-sender@example.com", domainID, "sender-password-1!")
	recipientID := createTestMailboxWithPassword(t, app, "pread-enc-recip@example.com", domainID, "recip-password-1!")
	genPGPIdentity(t, app, senderID, "pread-enc-sender@example.com", "sender passphrase")
	recipIdentityID := genPGPIdentity(t, app, recipientID, "pread-enc-recip@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, "pread-enc-recip@example.com", "", recipIdentity.Fingerprint, recipIdentity.PublicKeyArmor); err != nil {
		t.Fatal(err)
	}
	recipContact, err := app.DB.GetPGPContact(senderID, "pread-enc-recip@example.com")
	if err != nil || recipContact == nil {
		t.Fatal(err)
	}

	senderCookie := webmailLoginSession(t, app, senderID)
	form := url.Values{
		"to": {"pread-enc-recip@example.com"}, "subject": {"Encrypted read test"}, "body_html": {"the vault code is 9999"},
		"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(senderCookie)
	mux.ServeHTTP(httptest.NewRecorder(), req)
	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)
	}
	messageURL := MailboxPrefix + "/mail/INBOX/" + strconv.FormatInt(msgs[0].ID, 10)

	recipientCookie := webmailLoginSession(t, app, recipientID)

	// First view: nothing unlocked yet — should prompt, not decrypt.
	viewReq := httptest.NewRequest(http.MethodGet, messageURL, 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, "enter your passphrase to decrypt") {
		t.Fatalf("expected a needs-unlock prompt on first view, got body: %s", body)
	}
	if strings.Contains(body, "vault code") {
		t.Fatal("plaintext should not render before unlocking")
	}

	// Unlock via the real endpoint, exactly as the rendered form would submit.
	unlockForm := url.Values{"identity_id": {strconv.FormatInt(recipIdentityID, 10)}, "passphrase": {"recipient passphrase"}, "next": {messageURL}}
	unlockReq := httptest.NewRequest(http.MethodPost, MailboxPrefix+"/pgp/unlock", strings.NewReader(unlockForm.Encode()))
	unlockReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	unlockReq.AddCookie(recipientCookie)
	unlockRec := httptest.NewRecorder()
	mux.ServeHTTP(unlockRec, unlockReq)
	if unlockRec.Code != http.StatusFound || unlockRec.Header().Get("Location") != messageURL {
		t.Fatalf("unlock: status=%d location=%q", unlockRec.Code, unlockRec.Header().Get("Location"))
	}

	// Second view: now decrypts.
	viewReq2 := httptest.NewRequest(http.MethodGet, messageURL, nil)
	viewReq2.AddCookie(recipientCookie)
	viewRec2 := httptest.NewRecorder()
	mux.ServeHTTP(viewRec2, viewReq2)
	if viewRec2.Code != http.StatusOK {
		t.Fatalf("view after unlock: status=%d", viewRec2.Code)
	}
	body2 := viewRec2.Body.String()
	if !strings.Contains(body2, "PGP encrypted &amp; decrypted") {
		t.Fatalf("expected a decrypted badge after unlock, got body: %s", body2)
	}
	if !strings.Contains(body2, "the vault code is 9999") {
		t.Fatal("expected the decrypted body rendered after unlock")
	}
}

// TestWebmailReadSMIMESignAndPGPEncryptTogether confirms a message both S/MIME-signed
// and PGP-encrypted unwraps correctly on read once unlocked — the trickiest path
// through unwrapCrypto's loop, since it must peel two DIFFERENT protocols' layers in
// the right order.
func TestWebmailReadSMIMESignAndPGPEncryptTogether(t *testing.T) {
	app := newTestApp(t)
	mux := app.Mux()
	domains, _ := app.DB.ListDomains()
	domainID := domains[0].ID

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

	senderCookie := webmailLoginSession(t, app, senderID)
	form := url.Values{
		"to": {"pboth-recip@example.com"}, "subject": {"Sign and encrypt"}, "body_html": {"both protections applied"},
		"smime_sign": {"1"}, "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(senderCookie)
	sendRec := httptest.NewRecorder()
	mux.ServeHTTP(sendRec, req)
	if sendRec.Code != http.StatusFound {
		t.Fatalf("compose send: status=%d body=%s", sendRec.Code, sendRec.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)
	}
	messageURL := MailboxPrefix + "/mail/INBOX/" + strconv.FormatInt(msgs[0].ID, 10)

	recipientCookie := webmailLoginSession(t, app, recipientID)
	unlockForm := url.Values{"identity_id": {strconv.FormatInt(recipIdentityID, 10)}, "passphrase": {"pgp recipient passphrase"}}
	unlockReq := httptest.NewRequest(http.MethodPost, MailboxPrefix+"/pgp/unlock", strings.NewReader(unlockForm.Encode()))
	unlockReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	unlockReq.AddCookie(recipientCookie)
	mux.ServeHTTP(httptest.NewRecorder(), unlockReq)

	viewReq := httptest.NewRequest(http.MethodGet, messageURL, 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, "PGP encrypted &amp; decrypted") {
		t.Fatalf("expected a decrypted PGP badge, got body: %s", body)
	}
	if !strings.Contains(body, "Signature verified") {
		t.Fatalf("expected a verified S/MIME signature badge, got body: %s", body)
	}
	if !strings.Contains(body, "both protections applied") {
		t.Fatal("expected the plaintext rendered after unwrapping both layers")
	}
}

// TestWebmailReadPGPEncryptedMessageNoIdentityShowsUndecryptable confirms a
// recipient with no PGP key at all sees an honest "could not decrypt" badge rather
// than a needs-unlock prompt (there's nothing to unlock) or a crash.
func TestWebmailReadPGPEncryptedMessageNoIdentityShowsUndecryptable(t *testing.T) {
	app := newTestApp(t)
	mux := app.Mux()
	domains, _ := app.DB.ListDomains()
	domainID := domains[0].ID

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

	senderCookie := webmailLoginSession(t, app, senderID)
	form := url.Values{
		"to": {"pread-enc-recip2@example.com"}, "subject": {"x"}, "body_html": {"secret"},
		"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(senderCookie)
	mux.ServeHTTP(httptest.NewRecorder(), req)
	msgs, _ := app.DB.ListMessagesInFolder(recipientID, "INBOX")
	if len(msgs) != 1 {
		t.Fatalf("expected 1 message, got %d", len(msgs))
	}

	// Recipient loses their PGP key (e.g. removed it) before reading.
	if err := app.DB.DeletePGPIdentity(recipientID, recipIdentityID); err != nil {
		t.Fatal(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, "could not decrypt") {
		t.Fatalf("expected an undecryptable badge, got body: %s", body)
	}
	if strings.Contains(body, "secret") {
		t.Fatal("plaintext should not have leaked without successful decryption")
	}
}
