package webui

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

// TestWebmailMarkAsJunkAddsToBlocklistNotRules reproduces the intended behavior
// change: "Mark as Junk" used to create a filter rule (mark_as_spam action); it now
// adds the sender to the mailbox's own Blocklist ("junk" allowblock entries) instead,
// leaving the Rules list untouched.
func TestWebmailMarkAsJunkAddsToBlocklistNotRules(t *testing.T) {
	app := newTestApp(t)
	mux := app.Mux()
	domains, _ := app.DB.ListDomains()
	mailboxID := createTestMailboxWithPassword(t, app, "junker@example.com", domains[0].ID, "junker-password-1!")
	cookie := webmailLoginSession(t, app, mailboxID)

	uid := storeTestMessage(t, app, mailboxID, "INBOX", "spammer@example.com", "buy now", "body")

	req := httptest.NewRequest(http.MethodPost, MailboxPrefix+"/mail/INBOX/"+strconv.FormatInt(uid, 10)+"/mark-junk", nil)
	req.AddCookie(cookie)
	rec := httptest.NewRecorder()
	mux.ServeHTTP(rec, req)
	if rec.Code != http.StatusFound {
		t.Fatalf("mark as junk: status=%d body=%s", rec.Code, rec.Body.String())
	}

	junked, err := app.DB.IsJunked(mailboxID, "spammer@example.com")
	if err != nil || !junked {
		t.Fatalf("expected spammer@example.com added to the blocklist, junked=%v err=%v", junked, err)
	}
	rules, err := app.DB.ListRulesForMailbox(mailboxID)
	if err != nil {
		t.Fatal(err)
	}
	if len(rules) != 0 {
		t.Fatalf("expected no filter rule created (blocklist replaces the old rule-based flow), got %+v", rules)
	}

	msgs, err := app.DB.ListMessagesInFolder(mailboxID, "Junk")
	if err != nil || len(msgs) != 1 {
		t.Fatalf("expected the message itself moved to Junk, got %d (err=%v)", len(msgs), err)
	}
}

// TestWebmailBlocklistAddAndRemove exercises the self-service Blocklist/Whitelist
// page's add+remove flow for both list types.
func TestWebmailBlocklistAddAndRemove(t *testing.T) {
	app := newTestApp(t)
	mux := app.Mux()
	domains, _ := app.DB.ListDomains()
	mailboxID := createTestMailboxWithPassword(t, app, "lister@example.com", domains[0].ID, "lister-password-1!")
	cookie := webmailLoginSession(t, app, mailboxID)

	add := func(pattern, listType string) *httptest.ResponseRecorder {
		form := url.Values{"pattern": {pattern}, "list_type": {listType}}
		req := httptest.NewRequest(http.MethodPost, MailboxPrefix+"/blocklist/add", strings.NewReader(form.Encode()))
		req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
		req.AddCookie(cookie)
		rec := httptest.NewRecorder()
		mux.ServeHTTP(rec, req)
		return rec
	}

	if rec := add("bad@example.com", "junk"); rec.Code != http.StatusFound {
		t.Fatalf("add junk: status=%d", rec.Code)
	}
	if rec := add("good@example.com", "allow"); rec.Code != http.StatusFound {
		t.Fatalf("add allow: status=%d", rec.Code)
	}

	entries, err := app.DB.ListAllowBlock(mailboxID)
	if err != nil || len(entries) != 2 {
		t.Fatalf("expected 2 entries, got %d (err=%v)", len(entries), err)
	}

	// "block" (admin's hard-reject list) must not be settable from this self-service
	// endpoint.
	if rec := add("someone@example.com", "block"); rec.Code != http.StatusFound {
		t.Fatalf("status=%d", rec.Code)
	}
	entries, err = app.DB.ListAllowBlock(mailboxID)
	if err != nil || len(entries) != 2 {
		t.Fatalf("expected 'block' rejected (still 2 entries), got %d (err=%v)", len(entries), err)
	}

	var junkID int64
	for _, e := range entries {
		if e.ListType == "junk" {
			junkID = e.ID
		}
	}
	rmReq := httptest.NewRequest(http.MethodPost, MailboxPrefix+"/blocklist/"+strconv.FormatInt(junkID, 10)+"/remove", nil)
	rmReq.AddCookie(cookie)
	rmRec := httptest.NewRecorder()
	mux.ServeHTTP(rmRec, rmReq)
	if rmRec.Code != http.StatusFound {
		t.Fatalf("remove: status=%d", rmRec.Code)
	}
	remaining, err := app.DB.ListAllowBlock(mailboxID)
	if err != nil || len(remaining) != 1 || remaining[0].ListType != "allow" {
		t.Fatalf("expected only the allow entry left, got %+v (err=%v)", remaining, err)
	}
}

// TestWebmailBlocklistScopedToOwnMailbox confirms one mailbox owner can't remove
// another mailbox's blocklist entry by guessing its ID.
func TestWebmailBlocklistScopedToOwnMailbox(t *testing.T) {
	app := newTestApp(t)
	mux := app.Mux()
	domains, _ := app.DB.ListDomains()
	victimID := createTestMailboxWithPassword(t, app, "victim3@example.com", domains[0].ID, "victim-password-1!")
	attackerID := createTestMailboxWithPassword(t, app, "attacker3@example.com", domains[0].ID, "attacker-password-1!")

	entryID, err := app.DB.AddAllowBlockEntry(victimID, "junk", "spam@example.com")
	if err != nil {
		t.Fatal(err)
	}

	attackerCookie := webmailLoginSession(t, app, attackerID)
	req := httptest.NewRequest(http.MethodPost, MailboxPrefix+"/blocklist/"+strconv.FormatInt(entryID, 10)+"/remove", nil)
	req.AddCookie(attackerCookie)
	rec := httptest.NewRecorder()
	mux.ServeHTTP(rec, req)
	if rec.Code != http.StatusFound {
		t.Fatalf("status=%d", rec.Code)
	}

	stillThere, err := app.DB.ListAllowBlock(victimID)
	if err != nil || len(stillThere) != 1 {
		t.Fatalf("expected the victim's entry untouched, got %d (err=%v)", len(stillThere), err)
	}
}
