package webui

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

	"mailgoserver/internal/pgp"
)

// TestWebmailPGPGenerateAndDownload confirms a mailbox owner can generate a
// passphrase-protected PGP key, see it reflected on the Certs page, and download
// the public key.
func TestWebmailPGPGenerateAndDownload(t *testing.T) {
	app := newTestApp(t)
	mux := app.Mux()
	domains, _ := app.DB.ListDomains()
	mailboxID := createTestMailboxWithPassword(t, app, "pgp1@example.com", domains[0].ID, "pgp-password-1!")
	cookie := webmailLoginSession(t, app, mailboxID)

	form := url.Values{"label": {"Work key"}, "passphrase": {"correct horse battery staple"}, "passphrase_confirm": {"correct horse battery staple"}}
	req := httptest.NewRequest(http.MethodPost, MailboxPrefix+"/pgp/identity/generate", 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("generate: status=%d body=%s", rec.Code, rec.Body.String())
	}

	identities, err := app.DB.ListPGPIdentities(mailboxID)
	if err != nil || len(identities) != 1 {
		t.Fatalf("expected 1 PGP identity, got %d (err=%v)", len(identities), err)
	}
	identity := identities[0]
	if identity.Label != "Work key" {
		t.Errorf("label = %q, want %q", identity.Label, "Work key")
	}
	if identity.Fingerprint == "" {
		t.Error("expected a non-empty fingerprint")
	}
	if bytes.Contains([]byte(identity.PrivateKeyArmor), []byte("correct horse")) {
		t.Fatal("stored private key armor should not contain the plaintext passphrase")
	}

	pageReq := httptest.NewRequest(http.MethodGet, MailboxPrefix+"/certs", nil)
	pageReq.AddCookie(cookie)
	pageRec := httptest.NewRecorder()
	mux.ServeHTTP(pageRec, pageReq)
	if pageRec.Code != http.StatusOK || !strings.Contains(pageRec.Body.String(), "Work key") {
		t.Fatalf("expected the Certs page to show the PGP identity, status=%d body=%s", pageRec.Code, pageRec.Body.String())
	}
	if !strings.Contains(pageRec.Body.String(), "Locked") {
		t.Fatal("expected the PGP identity to show as locked before any unlock")
	}

	dlReq := httptest.NewRequest(http.MethodGet, MailboxPrefix+"/pgp/identity/"+strconv.FormatInt(identity.ID, 10)+"/download", nil)
	dlReq.AddCookie(cookie)
	dlRec := httptest.NewRecorder()
	mux.ServeHTTP(dlRec, dlReq)
	if dlRec.Code != http.StatusOK || dlRec.Body.String() != identity.PublicKeyArmor {
		t.Fatalf("expected downloaded key to match stored public key, status=%d", dlRec.Code)
	}
}

// TestWebmailPGPUnlockWrongPassphraseRejected confirms the unlock endpoint rejects
// an incorrect passphrase and caches nothing.
func TestWebmailPGPUnlockWrongPassphraseRejected(t *testing.T) {
	app := newTestApp(t)
	mux := app.Mux()
	domains, _ := app.DB.ListDomains()
	mailboxID := createTestMailboxWithPassword(t, app, "pgp2@example.com", domains[0].ID, "pgp-password-1!")
	cookie := webmailLoginSession(t, app, mailboxID)

	form := url.Values{"passphrase": {"the real passphrase"}, "passphrase_confirm": {"the real passphrase"}}
	req := httptest.NewRequest(http.MethodPost, MailboxPrefix+"/pgp/identity/generate", strings.NewReader(form.Encode()))
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.AddCookie(cookie)
	mux.ServeHTTP(httptest.NewRecorder(), req)
	identities, _ := app.DB.ListPGPIdentities(mailboxID)
	if len(identities) != 1 {
		t.Fatalf("expected 1 identity, got %d", len(identities))
	}

	unlock := func(passphrase string) *httptest.ResponseRecorder {
		f := url.Values{"identity_id": {strconv.FormatInt(identities[0].ID, 10)}, "passphrase": {passphrase}}
		r := httptest.NewRequest(http.MethodPost, MailboxPrefix+"/pgp/unlock", strings.NewReader(f.Encode()))
		r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
		r.AddCookie(cookie)
		rec := httptest.NewRecorder()
		mux.ServeHTTP(rec, r)
		return rec
	}

	if rec := unlock("wrong passphrase"); rec.Code != http.StatusFound {
		t.Fatalf("status=%d", rec.Code)
	}
	if _, ok := app.pgpKeys.get(sessionTokenFromCookie(cookie), identities[0].ID); ok {
		t.Fatal("expected no key cached after a wrong-passphrase unlock attempt")
	}

	if rec := unlock("the real passphrase"); rec.Code != http.StatusFound {
		t.Fatalf("status=%d", rec.Code)
	}
	if _, ok := app.pgpKeys.get(sessionTokenFromCookie(cookie), identities[0].ID); !ok {
		t.Fatal("expected the key cached after the correct passphrase")
	}
}

// TestWebmailPGPContactAddAndRemove confirms a contact's public key can be added
// (validated as a real key), listed, and removed again.
func TestWebmailPGPContactAddAndRemove(t *testing.T) {
	app := newTestApp(t)
	mux := app.Mux()
	domains, _ := app.DB.ListDomains()
	mailboxID := createTestMailboxWithPassword(t, app, "pgp3@example.com", domains[0].ID, "pgp-password-1!")
	cookie := webmailLoginSession(t, app, mailboxID)

	contactPub, _, err := pgp.GenerateKeyPair("contact@other.example", "contact passphrase")
	if err != nil {
		t.Fatal(err)
	}

	var buf bytes.Buffer
	mw := multipart.NewWriter(&buf)
	mw.WriteField("email", "contact@other.example")
	mw.WriteField("label", "Other")
	fw, err := mw.CreateFormFile("key_file", "contact.asc")
	if err != nil {
		t.Fatal(err)
	}
	fw.Write(contactPub)
	mw.Close()

	req := httptest.NewRequest(http.MethodPost, MailboxPrefix+"/pgp/contacts/add", &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("add contact: status=%d body=%s", rec.Code, rec.Body.String())
	}

	contacts, err := app.DB.ListPGPContacts(mailboxID)
	if err != nil || len(contacts) != 1 || contacts[0].Email != "contact@other.example" || contacts[0].Fingerprint == "" {
		t.Fatalf("expected 1 contact with a fingerprint, got %+v (err=%v)", contacts, err)
	}

	rmReq := httptest.NewRequest(http.MethodPost, MailboxPrefix+"/pgp/contacts/"+strconv.FormatInt(contacts[0].ID, 10)+"/remove", nil)
	rmReq.AddCookie(cookie)
	rmRec := httptest.NewRecorder()
	mux.ServeHTTP(rmRec, rmReq)
	if rmRec.Code != http.StatusFound {
		t.Fatalf("remove contact: status=%d", rmRec.Code)
	}
	remaining, err := app.DB.ListPGPContacts(mailboxID)
	if err != nil || len(remaining) != 0 {
		t.Fatalf("expected no contacts left, got %d (err=%v)", len(remaining), err)
	}
}

// TestWebmailPGPAddContactRejectsGarbage confirms an upload that isn't a valid PGP
// public key is rejected rather than silently stored.
func TestWebmailPGPAddContactRejectsGarbage(t *testing.T) {
	app := newTestApp(t)
	mux := app.Mux()
	domains, _ := app.DB.ListDomains()
	mailboxID := createTestMailboxWithPassword(t, app, "pgp4@example.com", domains[0].ID, "pgp-password-1!")
	cookie := webmailLoginSession(t, app, mailboxID)

	var buf bytes.Buffer
	mw := multipart.NewWriter(&buf)
	mw.WriteField("email", "nope@example.com")
	fw, err := mw.CreateFormFile("key_file", "notakey.asc")
	if err != nil {
		t.Fatal(err)
	}
	fw.Write([]byte("this is not a pgp key"))
	mw.Close()

	req := httptest.NewRequest(http.MethodPost, MailboxPrefix+"/pgp/contacts/add", &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("status=%d", rec.Code)
	}

	contacts, err := app.DB.ListPGPContacts(mailboxID)
	if err != nil || len(contacts) != 0 {
		t.Fatalf("expected the invalid key rejected, got %d contacts (err=%v)", len(contacts), err)
	}
}

// TestWebmailPGPImportAlreadyEncryptedKey confirms importing an existing
// passphrase-protected armored key (as if exported from GnuPG) works end to end
// through the real HTTP import endpoint.
func TestWebmailPGPImportAlreadyEncryptedKey(t *testing.T) {
	app := newTestApp(t)
	mux := app.Mux()
	domains, _ := app.DB.ListDomains()
	mailboxID := createTestMailboxWithPassword(t, app, "pgp5@example.com", domains[0].ID, "pgp-password-1!")
	cookie := webmailLoginSession(t, app, mailboxID)

	_, privArmor, err := pgp.GenerateKeyPair("imported@example.com", "existing passphrase")
	if err != nil {
		t.Fatal(err)
	}

	var buf bytes.Buffer
	mw := multipart.NewWriter(&buf)
	mw.WriteField("label", "Imported")
	mw.WriteField("passphrase", "existing passphrase")
	fw, err := mw.CreateFormFile("key_file", "imported.asc")
	if err != nil {
		t.Fatal(err)
	}
	fw.Write(privArmor)
	mw.Close()

	req := httptest.NewRequest(http.MethodPost, MailboxPrefix+"/pgp/identity/import", &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("import: status=%d body=%s", rec.Code, rec.Body.String())
	}

	identities, err := app.DB.ListPGPIdentities(mailboxID)
	if err != nil || len(identities) != 1 {
		t.Fatalf("expected 1 identity, got %d (err=%v)", len(identities), err)
	}
	if identities[0].Email != "imported@example.com" {
		t.Errorf("expected the email extracted from the key itself, got %q", identities[0].Email)
	}
}
