package webui

import (
	"fmt"
	"net/http"
	"strings"
	"time"

	"mailgoserver/internal/db"
)

// appPasswordsList shows a mailbox's existing app-password labels (never the secrets
// themselves — those are shown once, at creation) plus a form to add a new one.
func (a *App) appPasswordsList(w http.ResponseWriter, r *http.Request) {
	mailbox, ok := a.mailboxWithAccess(w, r)
	if !ok {
		return
	}
	passwords, err := a.DB.ListAppPasswordsForMailbox(mailbox.ID)
	if err != nil {
		setFlash(w, "error", "Error loading app passwords")
	}
	a.render(w, r, "mailbox_apppasswords.html", M{
		"active": "mailboxes", "mailbox": mailbox, "passwords": passwords,
		"reveal": popAppPasswordReveal(w, r),
	})
}

// appPasswordExpiry turns the create form's preset select (plus an optional custom
// date) into an expiry timestamp. Returns (nil, nil) for "never expires", the default.
func appPasswordExpiry(preset, customDate string, loc *time.Location) (*time.Time, error) {
	now := time.Now()
	var t time.Time
	switch preset {
	case "", "never":
		return nil, nil
	case "1d":
		t = now.Add(24 * time.Hour)
	case "7d":
		t = now.Add(7 * 24 * time.Hour)
	case "30d":
		t = now.Add(30 * 24 * time.Hour)
	case "180d":
		t = now.Add(180 * 24 * time.Hour)
	case "365d":
		t = now.Add(365 * 24 * time.Hour)
	case "custom":
		if customDate == "" {
			return nil, fmt.Errorf("an expiration date is required")
		}
		d, err := time.ParseInLocation("2006-01-02", customDate, loc)
		if err != nil {
			return nil, fmt.Errorf("invalid expiration date")
		}
		// End of the chosen day, not midnight at its start, so the picked date is
		// still valid for its whole duration.
		t = d.Add(24*time.Hour - time.Second)
	default:
		return nil, fmt.Errorf("invalid expiration option")
	}
	return &t, nil
}

// addAppPassword generates a random secret (the only credential IMAP/SMTP clients ever
// use for this mailbox — never the portal password), reveals it once via a one-time
// cookie the list page renders as a modal, and stores only its bcrypt hash.
func (a *App) addAppPassword(w http.ResponseWriter, r *http.Request) {
	mailbox, ok := a.mailboxWithAccess(w, r)
	if !ok {
		return
	}
	label := strings.TrimSpace(r.FormValue("label"))
	if label == "" {
		label = "App password"
	}

	tzName := a.Cfg.Section("Server").Key("time_zone").MustString("UTC")
	loc, err := time.LoadLocation(tzName)
	if err != nil {
		loc = time.UTC
	}
	expiresAt, err := appPasswordExpiry(r.FormValue("expires_preset"), r.FormValue("expires_custom"), loc)
	if err != nil {
		setFlash(w, "error", err.Error())
		http.Redirect(w, r, Prefix+"/mailboxes/"+idStr(r)+"/apppasswords", http.StatusFound)
		return
	}

	minLen := a.Cfg.Section("Mailstore").Key("app_password_min_length").MustInt(25)
	secret := db.GenerateAppPassword(minLen)
	hash, err := db.HashPassword(secret)
	if err != nil {
		setFlash(w, "error", "Error creating app password")
		http.Redirect(w, r, Prefix+"/mailboxes/"+idStr(r)+"/apppasswords", http.StatusFound)
		return
	}
	if _, err := a.DB.CreateAppPassword(mailbox.ID, label, hash, expiresAt); err != nil {
		setFlash(w, "error", "Error creating app password")
		http.Redirect(w, r, Prefix+"/mailboxes/"+idStr(r)+"/apppasswords", http.StatusFound)
		return
	}
	setAppPasswordReveal(w, label, secret)
	http.Redirect(w, r, Prefix+"/mailboxes/"+idStr(r)+"/apppasswords", http.StatusFound)
}

func (a *App) revokeAppPassword(w http.ResponseWriter, r *http.Request) {
	mailbox, ok := a.mailboxWithAccess(w, r)
	if !ok {
		return
	}
	pwID := int64(atoi(r.PathValue("pw_id")))
	if err := a.DB.RemoveAppPassword(pwID, mailbox.ID); err != nil {
		setFlash(w, "error", "Error revoking app password")
	} else {
		setFlash(w, "success", "App password revoked")
	}
	http.Redirect(w, r, Prefix+"/mailboxes/"+idStr(r)+"/apppasswords", http.StatusFound)
}

func idStr(r *http.Request) string {
	return r.PathValue("id")
}
