package webui

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

	"github.com/pquerna/otp/totp"
	"mailgoserver/internal/db"
)

// authLogsFor issues an authenticated GET /logs?type=auth and returns the raw body,
// used below to check which auth-log rows a given admin session can see.
func authLogsFor(t *testing.T, mux http.Handler, cookie *http.Cookie) string {
	t.Helper()
	req := httptest.NewRequest(http.MethodGet, Prefix+"/logs?type=auth", nil)
	req.AddCookie(cookie)
	rec := httptest.NewRecorder()
	mux.ServeHTTP(rec, req)
	if rec.Code != http.StatusOK {
		t.Fatalf("/logs?type=auth: status=%d", rec.Code)
	}
	return rec.Body.String()
}

// TestAdminLoginLogsAuthAttempts confirms both a failed and a successful admin
// dashboard login are recorded to the audit log.
func TestAdminLoginLogsAuthAttempts(t *testing.T) {
	app := newTestApp(t)
	mux := app.Mux()

	hash, err := db.HashPassword("correct-horse-battery-1!")
	if err != nil {
		t.Fatal(err)
	}
	if _, err := app.DB.CreateAdminUser("audituser", hash, false); err != nil {
		t.Fatal(err)
	}

	// Wrong password.
	form := url.Values{"username": {"audituser"}, "password": {"wrong-password"}}
	req := httptest.NewRequest(http.MethodPost, Prefix+"/login", strings.NewReader(form.Encode()))
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	mux.ServeHTTP(httptest.NewRecorder(), req)

	// Correct password.
	form = url.Values{"username": {"audituser"}, "password": {"correct-horse-battery-1!"}}
	req = httptest.NewRequest(http.MethodPost, Prefix+"/login", strings.NewReader(form.Encode()))
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	mux.ServeHTTP(httptest.NewRecorder(), req)

	logs, err := app.DB.ListRecentAuthLogs(50)
	if err != nil {
		t.Fatal(err)
	}
	var sawFail, sawSuccess bool
	for _, l := range logs {
		if l.AuthType != "admin_login" || l.Identifier != "audituser" {
			continue
		}
		if !l.Success {
			sawFail = true
		} else {
			sawSuccess = true
		}
	}
	if !sawFail {
		t.Error("expected a failed admin_login entry for the wrong-password attempt")
	}
	if !sawSuccess {
		t.Error("expected a successful admin_login entry for the correct-password attempt")
	}
}

// TestAdminMFAEventsLogged confirms enabling/disabling TOTP, and an admin resetting
// another admin's MFA, all produce admin_mfa audit entries.
func TestAdminMFAEventsLogged(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)
	}
	if err := app.DB.SetAdminTOTPSecret(sess.UserID, "JBSWY3DPEHPK3PXP", false); err != nil {
		t.Fatal(err)
	}

	req := httptest.NewRequest(http.MethodPost, Prefix+"/account/totp/confirm", strings.NewReader(url.Values{"code": {totpCodeFor(t, "JBSWY3DPEHPK3PXP")}}.Encode()))
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.AddCookie(cookie)
	mux.ServeHTTP(httptest.NewRecorder(), req)

	req = httptest.NewRequest(http.MethodPost, Prefix+"/account/totp/disable", nil)
	req.AddCookie(cookie)
	mux.ServeHTTP(httptest.NewRecorder(), req)

	targetID, err := app.DB.CreateAdminUser("reset-target", mustHash(t), false)
	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(cookie)
	mux.ServeHTTP(httptest.NewRecorder(), req)

	logs, err := app.DB.ListRecentAuthLogs(50)
	if err != nil {
		t.Fatal(err)
	}
	var sawEnabled, sawDisabled, sawReset bool
	for _, l := range logs {
		if l.AuthType != "admin_mfa" {
			continue
		}
		switch {
		case strings.Contains(l.Message, "enabled") && l.Identifier == "test-admin":
			sawEnabled = true
		case strings.Contains(l.Message, "disabled") && l.Identifier == "test-admin":
			sawDisabled = true
		case strings.Contains(l.Message, "reset by admin") && l.Identifier == "reset-target":
			sawReset = true
		}
	}
	if !sawEnabled {
		t.Error("expected an admin_mfa entry for TOTP enabled")
	}
	if !sawDisabled {
		t.Error("expected an admin_mfa entry for TOTP disabled")
	}
	if !sawReset {
		t.Error("expected an admin_mfa entry for the admin-initiated reset")
	}
}

// TestWebmailLoginLogsAuthAttempts mirrors TestAdminLoginLogsAuthAttempts for the
// self-service webmail portal.
func TestWebmailLoginLogsAuthAttempts(t *testing.T) {
	app := newTestApp(t)
	mux := app.Mux()

	mailboxes, err := app.DB.ListMailboxes()
	if err != nil || len(mailboxes) == 0 {
		t.Fatal("no seeded mailbox")
	}
	email := mailboxes[0].Email

	form := url.Values{"email": {email}, "password": {"wrong-password"}}
	req := httptest.NewRequest(http.MethodPost, MailboxPrefix+"/login", strings.NewReader(form.Encode()))
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	mux.ServeHTTP(httptest.NewRecorder(), req)

	form = url.Values{"email": {email}, "password": {"testpass123"}}
	req = httptest.NewRequest(http.MethodPost, MailboxPrefix+"/login", strings.NewReader(form.Encode()))
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	mux.ServeHTTP(httptest.NewRecorder(), req)

	logs, err := app.DB.ListRecentAuthLogs(50)
	if err != nil {
		t.Fatal(err)
	}
	var sawFail, sawSuccess bool
	for _, l := range logs {
		if l.AuthType != "webmail_login" || l.Identifier != email {
			continue
		}
		if !l.Success {
			sawFail = true
		} else {
			sawSuccess = true
		}
	}
	if !sawFail {
		t.Error("expected a failed webmail_login entry for the wrong-password attempt")
	}
	if !sawSuccess {
		t.Error("expected a successful webmail_login entry for the correct-password attempt")
	}
}

// TestAdminAuditLogsHiddenFromScopedAdmins confirms admin_login/admin_mfa entries
// (identified by admin username, with no domain to attribute them to) are visible
// only to global admins, while webmail_login/mailbox_mfa entries (identified by
// mailbox email) remain visible to a scoped admin for their own domain.
func TestAdminAuditLogsHiddenFromScopedAdmins(t *testing.T) {
	app := newTestApp(t)
	mux := app.Mux()
	domains, err := app.DB.ListDomains()
	if err != nil || len(domains) == 0 {
		t.Fatal("no seeded domain")
	}
	domainName := domains[0].DomainName

	if err := app.DB.LogAuthAttempt("admin_login", "some-admin-username", "127.0.0.1", true, "Login successful"); err != nil {
		t.Fatal(err)
	}
	if err := app.DB.LogAuthAttempt("webmail_login", "owner@"+domainName, "127.0.0.1", true, "Login successful"); err != nil {
		t.Fatal(err)
	}

	scopedCookie := scopedLogin(t, app, "scoped-log-viewer", []int64{domains[0].ID})
	scopedBody := authLogsFor(t, mux, scopedCookie)
	if strings.Contains(scopedBody, "some-admin-username") {
		t.Error("a scoped admin should not see admin_login entries at all")
	}
	if !strings.Contains(scopedBody, "owner@"+domainName) {
		t.Error("a scoped admin should see webmail_login entries for their own domain")
	}

	globalCookie := loginSession(t, app)
	globalBody := authLogsFor(t, mux, globalCookie)
	if !strings.Contains(globalBody, "some-admin-username") {
		t.Error("a global admin should see admin_login entries")
	}
}

// totpCodeFor generates a valid current TOTP code for a secret — used to drive
// account.go's totpSetupConfirm through a real form submission.
func totpCodeFor(t *testing.T, secret string) string {
	t.Helper()
	code, err := totp.GenerateCode(secret, time.Now())
	if err != nil {
		t.Fatal(err)
	}
	return code
}
