package webui

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

	"mailgoserver/internal/db"
)

// TestResetAdminMFA confirms a manager can clear another admin's TOTP and passkeys
// (e.g. after a lost device), and that only an admin who could otherwise manage that
// target (per the existing delegation rule) is allowed to.
func TestResetAdminMFA(t *testing.T) {
	app := newTestApp(t)
	mux := app.Mux()
	cookie := loginSession(t, app)

	targetID, err := app.DB.CreateAdminUser("has-mfa-admin", mustHash(t), false)
	if err != nil {
		t.Fatal(err)
	}
	if err := app.DB.SetAdminTOTPSecret(targetID, "JBSWY3DPEHPK3PXP", true); err != nil {
		t.Fatal(err)
	}
	if err := app.DB.CreateWebAuthnCredential(targetID, "yubikey", "cred-id-1", "cred-data-1"); err != nil {
		t.Fatal(err)
	}

	req := httptest.NewRequest(http.MethodPost, Prefix+"/admins/"+strconv.FormatInt(targetID, 10)+"/reset_mfa", nil)
	req.AddCookie(cookie)
	rec := httptest.NewRecorder()
	mux.ServeHTTP(rec, req)
	if rec.Code != http.StatusFound {
		t.Fatalf("status=%d body=%s", rec.Code, rec.Body.String())
	}

	target, err := app.DB.GetAdminUserByID(targetID)
	if err != nil || target == nil {
		t.Fatal(err)
	}
	if target.TOTPEnabled || target.TOTPSecret != "" {
		t.Error("TOTP should be cleared")
	}
	creds, err := app.DB.ListWebAuthnCredentials(targetID)
	if err != nil || len(creds) != 0 {
		t.Errorf("expected no passkeys left, got %d (err=%v)", len(creds), err)
	}
}

// TestResetAdminMFADeniedOutsideDelegationScope confirms a scoped admin can't reset
// MFA for an admin outside their delegation scope (mirrors the existing remove/edit
// access checks — resetting someone's MFA is just as sensitive an action).
func TestResetAdminMFADeniedOutsideDelegationScope(t *testing.T) {
	app := newTestApp(t)
	mux := app.Mux()
	domainA, domainB, _, _ := setupTwoTenants(t, app)
	_ = domainA

	cookieA := scopedLogin(t, app, "tenant-a-admin", []int64{domainA.ID})
	targetID, err := app.DB.CreateScopedAdminUser("tenant-b-admin", mustHash(t), 0, []int64{domainB.ID})
	if err != nil {
		t.Fatal(err)
	}
	if err := app.DB.SetAdminTOTPSecret(targetID, "JBSWY3DPEHPK3PXP", true); err != nil {
		t.Fatal(err)
	}

	req := httptest.NewRequest(http.MethodPost, Prefix+"/admins/"+strconv.FormatInt(targetID, 10)+"/reset_mfa", nil)
	req.AddCookie(cookieA)
	rec := httptest.NewRecorder()
	mux.ServeHTTP(rec, req)
	if rec.Code != http.StatusNotFound {
		t.Fatalf("expected 404 for an out-of-scope target, got %d", rec.Code)
	}

	target, err := app.DB.GetAdminUserByID(targetID)
	if err != nil || target == nil {
		t.Fatal(err)
	}
	if !target.TOTPEnabled {
		t.Error("TOTP should NOT have been reset for an out-of-scope admin")
	}
}

// TestAdminCannotRemoveOrResetOwnAccount confirms the existing self-management
// blocks (canManageAdmin already rejects target.ID == user.ID) also apply to the new
// reset_mfa route, and that the admins.html list hides both actions for your own row.
func TestAdminCannotRemoveOrResetOwnAccount(t *testing.T) {
	app := newTestApp(t)
	mux := app.Mux()
	cookie := loginSession(t, app)

	sess, err := app.DB.GetSession(cookie.Value)
	if err != nil || sess == nil {
		t.Fatal(err)
	}
	selfID := strconv.FormatInt(sess.UserID, 10)

	for _, action := range []string{"remove", "reset_mfa"} {
		req := httptest.NewRequest(http.MethodPost, Prefix+"/admins/"+selfID+"/"+action, nil)
		req.AddCookie(cookie)
		rec := httptest.NewRecorder()
		mux.ServeHTTP(rec, req)
		if rec.Code != http.StatusNotFound {
			t.Errorf("%s on own account: status=%d, want 404", action, rec.Code)
		}
	}

	stillThere, err := app.DB.GetAdminUserByID(sess.UserID)
	if err != nil || stillThere == nil {
		t.Fatal("own account should not have been removed")
	}

	// The admins list must not render a Remove/Reset MFA button for your own row.
	req := httptest.NewRequest(http.MethodGet, Prefix+"/admins", nil)
	req.AddCookie(cookie)
	rec := httptest.NewRecorder()
	mux.ServeHTTP(rec, req)
	if rec.Code != http.StatusOK {
		t.Fatalf("/admins: status=%d", rec.Code)
	}
	body := rec.Body.String()
	if strings.Contains(body, "/admins/"+selfID+"/remove") {
		t.Error("admins.html should not render a Remove action for the signed-in admin's own row")
	}
	if strings.Contains(body, "/admins/"+selfID+"/reset_mfa") {
		t.Error("admins.html should not render a Reset MFA action for the signed-in admin's own row")
	}
	if !strings.Contains(body, "This is you") {
		t.Error("expected the signed-in admin's own row to be marked, not just have its buttons hidden")
	}
}

// TestResetMailboxMFA confirms an admin can clear a mailbox owner's TOTP and passkeys.
func TestResetMailboxMFA(t *testing.T) {
	app := newTestApp(t)
	mux := app.Mux()
	cookie := loginSession(t, app)

	mailboxes, err := app.DB.ListMailboxes()
	if err != nil || len(mailboxes) == 0 {
		t.Fatal("no seeded mailbox")
	}
	mboxID := mailboxes[0].ID
	if err := app.DB.SetMailboxTOTPSecret(mboxID, "JBSWY3DPEHPK3PXP", true); err != nil {
		t.Fatal(err)
	}
	if err := app.DB.CreateMailboxWebAuthnCredential(mboxID, "phone", "mcred-1", "mcred-data-1"); err != nil {
		t.Fatal(err)
	}

	req := httptest.NewRequest(http.MethodPost, Prefix+"/mailboxes/"+strconv.FormatInt(mboxID, 10)+"/reset_mfa", nil)
	req.AddCookie(cookie)
	rec := httptest.NewRecorder()
	mux.ServeHTTP(rec, req)
	if rec.Code != http.StatusFound {
		t.Fatalf("status=%d body=%s", rec.Code, rec.Body.String())
	}

	updated, err := app.DB.GetMailboxByID(mboxID)
	if err != nil || updated == nil {
		t.Fatal(err)
	}
	if updated.TOTPEnabled || updated.TOTPSecret != "" {
		t.Error("TOTP should be cleared")
	}
	creds, err := app.DB.ListMailboxWebAuthnCredentials(mboxID)
	if err != nil || len(creds) != 0 {
		t.Errorf("expected no passkeys left, got %d (err=%v)", len(creds), err)
	}
}

func mustHash(t *testing.T) string {
	t.Helper()
	hash, err := db.HashPassword("some-strong-password-1!")
	if err != nil {
		t.Fatal(err)
	}
	return hash
}
