package webui

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

	"mailgoserver/internal/db"
)

// TestAdminMFAEnforcementForcesIsolatedSetupThenReleases confirms enforce_admin_mfa
// redirects EVERY route — including /account itself — to the isolated /mfa-setup
// page for an admin with no second factor yet, until they actually set one up, after
// which normal access (including /account) resumes.
func TestAdminMFAEnforcementForcesIsolatedSetupThenReleases(t *testing.T) {
	app := newTestApp(t)
	app.Cfg.Section("Auth").Key("enforce_admin_mfa").SetValue("true")
	mux := app.Mux()

	hash, err := db.HashPassword("no-mfa-yet-password-1!")
	if err != nil {
		t.Fatal(err)
	}
	userID, err := app.DB.CreateAdminUser("no-mfa-admin", hash, false)
	if err != nil {
		t.Fatal(err)
	}
	token, err := app.DB.CreateSession(userID, true, sessionTTL)
	if err != nil {
		t.Fatal(err)
	}
	cookie := &http.Cookie{Name: sessionCookieName, Value: token}

	// Blocked from an ordinary page, AND from /account, both redirected to /mfa-setup.
	for _, path := range []string{Prefix + "/domains", Prefix + "/account"} {
		req := httptest.NewRequest(http.MethodGet, path, nil)
		req.AddCookie(cookie)
		rec := httptest.NewRecorder()
		mux.ServeHTTP(rec, req)
		if rec.Code != http.StatusFound || rec.Header().Get("Location") != Prefix+"/mfa-setup" {
			t.Fatalf("%s: expected redirect to /mfa-setup, got %d Location=%q", path, rec.Code, rec.Header().Get("Location"))
		}
	}

	// The isolated setup page itself must be reachable and show no sidebar/nav.
	req := httptest.NewRequest(http.MethodGet, Prefix+"/mfa-setup", nil)
	req.AddCookie(cookie)
	rec := httptest.NewRecorder()
	mux.ServeHTTP(rec, req)
	if rec.Code != http.StatusOK {
		t.Fatalf("/mfa-setup: status=%d body=%s", rec.Code, rec.Body.String())
	}
	body := rec.Body.String()
	if !strings.Contains(body, "Two-factor authentication required") {
		t.Error("expected the MFA-required heading on /mfa-setup")
	}
	if strings.Contains(body, "sidebar") || strings.Contains(body, `href="/pymta-manager/domains"`) {
		t.Error("expected no sidebar/navigation on the isolated setup page")
	}

	// Once TOTP is enabled, both /domains and /account become reachable again.
	if err := app.DB.SetAdminTOTPSecret(userID, "JBSWY3DPEHPK3PXP", true); err != nil {
		t.Fatal(err)
	}
	for _, path := range []string{Prefix + "/domains", Prefix + "/account"} {
		req := httptest.NewRequest(http.MethodGet, path, nil)
		req.AddCookie(cookie)
		rec := httptest.NewRecorder()
		mux.ServeHTTP(rec, req)
		if rec.Code != http.StatusOK {
			t.Fatalf("%s: expected reachable after enabling MFA, got %d", path, rec.Code)
		}
	}
}

// TestAdminMFAEnforcementOffByDefault confirms nothing changes for existing installs
// unless the admin explicitly turns enforcement on.
func TestAdminMFAEnforcementOffByDefault(t *testing.T) {
	app := newTestApp(t)
	mux := app.Mux()
	cookie := loginSession(t, app)

	req := httptest.NewRequest(http.MethodGet, Prefix+"/domains", nil)
	req.AddCookie(cookie)
	rec := httptest.NewRecorder()
	mux.ServeHTTP(rec, req)
	if rec.Code != http.StatusOK {
		t.Fatalf("expected /domains reachable with enforcement off, got %d", rec.Code)
	}
}

// TestMailboxMFAEnforcementLetsLoginThroughButBlocksPasswordChange confirms
// enforce_mailbox_mfa no longer blocks login itself for a mailbox with no MFA
// configured — it lands them on the dashboard (which has the TOTP/passkey setup
// cards) and only blocks changing the account password until MFA is set up. App
// passwords are deliberately untouched throughout; see mailboxNeedsMFASetup's doc
// comment.
func TestMailboxMFAEnforcementLetsLoginThroughButIsolatesEverythingElse(t *testing.T) {
	app := newTestApp(t)
	app.Cfg.Section("Auth").Key("enforce_mailbox_mfa").SetValue("true")
	mux := app.Mux()

	domainID, err := app.DB.CreateDomain("mfatest.example")
	if err != nil {
		t.Fatal(err)
	}
	mhash, err := db.HashPassword("mailbox-owner-password-1!")
	if err != nil {
		t.Fatal(err)
	}
	dek := make([]byte, 32)
	mboxID, err := app.DB.CreateMailbox("owner@mfatest.example", mhash, domainID, 1<<30, dek, dek)
	if err != nil {
		t.Fatal(err)
	}

	form := url.Values{"email": {"owner@mfatest.example"}, "password": {"mailbox-owner-password-1!"}}
	req := httptest.NewRequest(http.MethodPost, MailboxPrefix+"/login", strings.NewReader(form.Encode()))
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	rec := httptest.NewRecorder()
	mux.ServeHTTP(rec, req)
	if rec.Code != http.StatusFound || rec.Header().Get("Location") != MailboxPrefix+"/" {
		t.Fatalf("expected login itself to succeed, got %d Location=%q", rec.Code, rec.Header().Get("Location"))
	}
	var cookie *http.Cookie
	for _, c := range rec.Result().Cookies() {
		if c.Name == mailboxSessionCookieName {
			cookie = c
		}
	}
	if cookie == nil {
		t.Fatal("expected a session cookie despite no MFA configured")
	}

	// The mailbox, account page, password change, and app-password creation are ALL
	// redirected to the isolated setup page — nothing else is reachable in the browser.
	blockedGets := []string{MailboxPrefix + "/", MailboxPrefix + "/account"}
	for _, path := range blockedGets {
		req = httptest.NewRequest(http.MethodGet, path, nil)
		req.AddCookie(cookie)
		rec = httptest.NewRecorder()
		mux.ServeHTTP(rec, req)
		if rec.Code != http.StatusFound || rec.Header().Get("Location") != MailboxPrefix+"/mfa-setup" {
			t.Fatalf("GET %s: expected redirect to /mfa-setup, got %d Location=%q", path, rec.Code, rec.Header().Get("Location"))
		}
	}

	pwForm := url.Values{"current_password": {"mailbox-owner-password-1!"}, "new_password": {"NewPassw0rd!!"}, "new_password_confirm": {"NewPassw0rd!!"}}
	req = httptest.NewRequest(http.MethodPost, MailboxPrefix+"/account/password", strings.NewReader(pwForm.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.Header().Get("Location") != MailboxPrefix+"/mfa-setup" {
		t.Fatalf("password change: expected redirect to /mfa-setup, got %d", rec.Code)
	}
	stillOld, err := app.DB.GetMailboxByID(mboxID)
	if err != nil || stillOld == nil {
		t.Fatal(err)
	}
	if !db.CheckPassword("mailbox-owner-password-1!", stillOld.PasswordHash) {
		t.Fatal("password should not have changed")
	}

	appForm := url.Values{"label": {"laptop"}}
	req = httptest.NewRequest(http.MethodPost, MailboxPrefix+"/account/apppasswords/add", strings.NewReader(appForm.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.Header().Get("Location") != MailboxPrefix+"/mfa-setup" {
		t.Fatalf("app password creation: expected redirect to /mfa-setup, got %d", rec.Code)
	}
	if passwords, _ := app.DB.ListAppPasswordsForMailbox(mboxID); len(passwords) != 0 {
		t.Fatalf("app password creation should have been blocked in the browser, got %d created", len(passwords))
	}

	// The isolated setup page itself is reachable and shows no other portal content.
	req = httptest.NewRequest(http.MethodGet, MailboxPrefix+"/mfa-setup", nil)
	req.AddCookie(cookie)
	rec = httptest.NewRecorder()
	mux.ServeHTTP(rec, req)
	if rec.Code != http.StatusOK {
		t.Fatalf("/mfa-setup: status=%d body=%s", rec.Code, rec.Body.String())
	}
	body := rec.Body.String()
	if !strings.Contains(body, "Two-factor authentication required") {
		t.Error("expected the MFA-required heading on /mfa-setup")
	}
	if strings.Contains(body, "App Passwords") || strings.Contains(body, "Change Password") {
		t.Error("expected no other portal sections on the isolated setup page")
	}

	// Once TOTP is configured, everything works normally again — mailbox, account
	// page, password change, and app passwords.
	if err := app.DB.SetMailboxTOTPSecret(mboxID, "JBSWY3DPEHPK3PXP", true); err != nil {
		t.Fatal(err)
	}
	req = httptest.NewRequest(http.MethodGet, MailboxPrefix+"/", nil)
	req.AddCookie(cookie)
	rec = httptest.NewRecorder()
	mux.ServeHTTP(rec, req)
	if rec.Code != http.StatusFound || rec.Header().Get("Location") != MailboxPrefix+"/mail/INBOX" {
		t.Fatalf("mailbox root: expected reachable (redirect to inbox) after enabling MFA, got %d Location=%q", rec.Code, rec.Header().Get("Location"))
	}
	req = httptest.NewRequest(http.MethodGet, MailboxPrefix+"/account", nil)
	req.AddCookie(cookie)
	rec = httptest.NewRecorder()
	mux.ServeHTTP(rec, req)
	if rec.Code != http.StatusOK {
		t.Fatalf("account page: expected reachable after enabling MFA, got %d", rec.Code)
	}

	req = httptest.NewRequest(http.MethodPost, MailboxPrefix+"/account/password", strings.NewReader(pwForm.Encode()))
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.AddCookie(cookie)
	rec = httptest.NewRecorder()
	mux.ServeHTTP(rec, req)
	updated, err := app.DB.GetMailboxByID(mboxID)
	if err != nil || updated == nil {
		t.Fatal(err)
	}
	if !db.CheckPassword("NewPassw0rd!!", updated.PasswordHash) {
		t.Fatal("password should have changed once MFA is configured")
	}

	req = httptest.NewRequest(http.MethodPost, MailboxPrefix+"/account/apppasswords/add", strings.NewReader(appForm.Encode()))
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.AddCookie(cookie)
	rec = httptest.NewRecorder()
	mux.ServeHTTP(rec, req)
	if passwords, _ := app.DB.ListAppPasswordsForMailbox(mboxID); len(passwords) != 1 {
		t.Fatalf("expected app password creation to succeed once MFA is configured, got %d", len(passwords))
	}
}
