package webui

import (
	"bytes"
	"encoding/base64"
	"html/template"
	"image/png"
	"net/http"
	"strings"

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

// accountPage shows the admin their own profile: password change, TOTP MFA
// enable/disable, and registered passkeys (passkey registration itself is wired up
// in webauthn.go). Only ever reached with MFA already satisfying enforce_admin_mfa
// (or enforcement off) — requireAuth redirects everywhere else, including here, to
// the isolated /mfa-setup page otherwise (see mfaSetupRequiredPage).
func (a *App) accountPage(w http.ResponseWriter, r *http.Request) {
	user := userFromContext(r)
	creds, _ := a.DB.ListWebAuthnCredentials(user.ID)
	a.render(w, r, "account.html", M{"active": "account", "user": user, "passkeys": creds})
}

// changePassword mirrors a normal (not forced) password change from account settings.
func (a *App) changePassword(w http.ResponseWriter, r *http.Request) {
	user := userFromContext(r)
	current := r.FormValue("current_password")
	newPassword := r.FormValue("new_password")
	confirm := r.FormValue("new_password_confirm")

	if !db.CheckPassword(current, user.PasswordHash) {
		setFlash(w, "error", "Current password is incorrect")
		http.Redirect(w, r, Prefix+"/account", http.StatusFound)
		return
	}
	if !isStrongPassword(newPassword) {
		setFlash(w, "error", "New password must be at least 10 characters and include a letter, a number, and a symbol")
		http.Redirect(w, r, Prefix+"/account", http.StatusFound)
		return
	}
	if newPassword != confirm {
		setFlash(w, "error", "New passwords don't match")
		http.Redirect(w, r, Prefix+"/account", http.StatusFound)
		return
	}
	hash, err := db.HashPassword(newPassword)
	if err != nil {
		setFlash(w, "error", "Something went wrong")
		http.Redirect(w, r, Prefix+"/account", http.StatusFound)
		return
	}
	if err := a.DB.UpdateAdminPassword(user.ID, hash); err != nil {
		setFlash(w, "error", "Something went wrong")
		http.Redirect(w, r, Prefix+"/account", http.StatusFound)
		return
	}
	setFlash(w, "success", "Password updated")
	http.Redirect(w, r, Prefix+"/account", http.StatusFound)
}

// mfaSetupRequiredPage is the isolated, sidebar-free landing page requireAuth sends
// an admin to when enforce_admin_mfa applies and they have no second factor yet — the
// only page (besides the totp/passkey setup actions themselves) reachable until they
// set one up, so there's no visible navigation to anything else in the browser.
func (a *App) mfaSetupRequiredPage(w http.ResponseWriter, r *http.Request) {
	user := userFromContext(r)
	a.render(w, r, "mfa_setup_required.html", M{"username": user.Username, "flashes": popFlashes(w, r)})
}

// totpSetupBegin generates a fresh (not-yet-enabled) TOTP secret and shows it as a
// scannable QR code (rendered inline as a data: URI — simplest way to hand the
// browser an image without a second round-trip route) plus the manual entry key.
func (a *App) totpSetupBegin(w http.ResponseWriter, r *http.Request) {
	user := userFromContext(r)
	key, err := totp.Generate(totp.GenerateOpts{
		Issuer:      "mailgoserver",
		AccountName: user.Username,
	})
	if err != nil {
		setFlash(w, "error", "Could not generate a TOTP secret")
		http.Redirect(w, r, Prefix+"/account", http.StatusFound)
		return
	}
	if err := a.DB.SetAdminTOTPSecret(user.ID, key.Secret(), false); err != nil {
		setFlash(w, "error", "Could not save the TOTP secret")
		http.Redirect(w, r, Prefix+"/account", http.StatusFound)
		return
	}

	img, err := key.Image(256, 256)
	qrDataURI := ""
	if err == nil {
		var buf bytes.Buffer
		if png.Encode(&buf, img) == nil {
			qrDataURI = "data:image/png;base64," + base64.StdEncoding.EncodeToString(buf.Bytes())
		}
	}
	// html/template's URL-context escaper only allows http/https/mailto schemes for a
	// plain string in a src="..." attribute — anything else, including data: URIs,
	// gets silently replaced with "#ZgotmplZ" (confirmed live). template.URL marks
	// this value as pre-approved so the actual QR image renders instead of nothing.
	a.render(w, r, "totp_setup.html", M{"secret": key.Secret(), "qr_data_uri": template.URL(qrDataURI)})
}

// totpSetupConfirm verifies a code against the pending secret and, if correct, flips
// TOTP on for the account.
func (a *App) totpSetupConfirm(w http.ResponseWriter, r *http.Request) {
	user := userFromContext(r)
	code := strings.TrimSpace(r.FormValue("code"))
	if user.TOTPSecret == "" || !totp.Validate(code, user.TOTPSecret) {
		setFlash(w, "error", "That code didn't match — try scanning the QR code again")
		http.Redirect(w, r, Prefix+"/account", http.StatusFound)
		return
	}
	if err := a.DB.SetAdminTOTPSecret(user.ID, user.TOTPSecret, true); err != nil {
		setFlash(w, "error", "Something went wrong enabling MFA")
		http.Redirect(w, r, Prefix+"/account", http.StatusFound)
		return
	}
	_ = a.DB.LogAuthAttempt("admin_mfa", user.Username, a.requestIP(r), true, "TOTP authenticator enabled")
	setFlash(w, "success", "Authenticator app MFA enabled")
	http.Redirect(w, r, Prefix+"/account", http.StatusFound)
}

func (a *App) totpDisable(w http.ResponseWriter, r *http.Request) {
	user := userFromContext(r)
	if err := a.DB.DisableAdminTOTP(user.ID); err != nil {
		setFlash(w, "error", "Something went wrong")
	} else {
		_ = a.DB.LogAuthAttempt("admin_mfa", user.Username, a.requestIP(r), true, "TOTP authenticator disabled")
		setFlash(w, "success", "Authenticator app MFA disabled")
	}
	http.Redirect(w, r, Prefix+"/account", http.StatusFound)
}
