package webui

import (
	"net/http"
	"sort"
	"time"

	"mailgoserver/internal/db"
)

const perPage = 50

// logs mirrors logs.py's logs(): type=all|emails|auth, page (mostly cosmetic in "all"
// mode, matching the Python version's own quirk where "all" mode's pagination isn't
// real pagination — see the route inventory). Scoped admins get the fetched page
// filtered down to their domains in-memory (these tables have no domain_id column to
// filter in SQL — see accessibleDomainNames) — pagination counts stay based on the
// unfiltered page, same imprecision the "all" mode already had before scoping existed.
func (a *App) logs(w http.ResponseWriter, r *http.Request) {
	allowedNames, isGlobal, err := a.accessibleDomainNames(r)
	if err != nil {
		setFlash(w, "error", "Error loading logs")
	}
	emailAllowed := func(e db.EmailLog) bool { return isGlobal || allowedNames[emailDomain(e.MailFrom)] }
	// admin_login/admin_mfa entries are identified by admin username, not a mailbox
	// email — there's no domain to attribute them to (a scoped admin's own username
	// could otherwise coincidentally collide with a domain name they're allowed to
	// see), so they're global-admin-only regardless of the identifier heuristic below.
	authAllowed := func(au db.AuthLog) bool {
		if au.AuthType == "admin_login" || au.AuthType == "admin_mfa" {
			return isGlobal
		}
		return isGlobal || allowedNames[authLogDomain(au.Identifier)]
	}

	filterType := r.URL.Query().Get("type")
	if filterType == "" {
		filterType = "all"
	}
	page := atoi(r.URL.Query().Get("page"))
	if page < 1 {
		page = 1
	}
	offset := (page - 1) * perPage
	// Read once here (not just inside the "auth" case) so every branch's M literal
	// can set it unconditionally — logs.html's pagination links reference it regardless
	// of filter_type, and a map[string]any with the key entirely absent renders
	// inconsistently across template functions versus one that's always present as "".
	authCategory := r.URL.Query().Get("auth_category")

	switch filterType {
	case "emails":
		fetched, err := a.DB.ListEmailLogsPage(offset, perPage)
		if err != nil {
			setFlash(w, "error", "Error loading logs")
		}
		var emails []db.EmailLog
		for _, e := range fetched {
			if emailAllowed(e) {
				emails = append(emails, e)
			}
		}
		recipientMap, attachMap := a.buildLogMaps(emails)
		a.render(w, r, "logs.html", M{
			"active": "logs", "logs": emails, "filter_type": filterType, "page": page,
			"auth_category": authCategory,
			"has_next":      len(fetched) == perPage, "has_prev": page > 1,
			"recipient_logs_map": recipientMap, "attachments_map": attachMap,
		})
	case "auth":
		fetched, err := a.DB.ListAuthLogsPage(offset, perPage)
		if err != nil {
			setFlash(w, "error", "Error loading logs")
		}
		var auths []db.AuthLog
		for _, au := range fetched {
			if authAllowed(au) && authCategoryMatches(au.AuthType, authCategory) {
				auths = append(auths, au)
			}
		}
		a.render(w, r, "logs.html", M{
			"active": "logs", "logs": auths, "filter_type": filterType, "page": page,
			"auth_category": authCategory,
			"has_next":      len(fetched) == perPage, "has_prev": page > 1,
		})
	default:
		half := perPage / 2
		fetchedEmails, _ := a.DB.ListEmailLogsPage(0, half)
		fetchedAuths, _ := a.DB.ListAuthLogsPage(0, half)
		recipientMap, _ := a.buildLogMaps(fetchedEmails)

		type combinedEntry struct {
			M  M
			At time.Time
		}
		var combined []combinedEntry
		for _, e := range fetchedEmails {
			if !emailAllowed(e) {
				continue
			}
			combined = append(combined, combinedEntry{M: M{"type": "email", "data": e, "recipients": recipientMap[e.ID]}, At: e.Timestamp})
		}
		for _, au := range fetchedAuths {
			if !authAllowed(au) {
				continue
			}
			combined = append(combined, combinedEntry{M: M{"type": "auth", "data": au}, At: au.CreatedAt})
		}
		sort.SliceStable(combined, func(i, j int) bool { return combined[i].At.After(combined[j].At) })
		if len(combined) > perPage {
			combined = combined[:perPage]
		}
		var logs []M
		for _, c := range combined {
			logs = append(logs, c.M)
		}
		a.render(w, r, "logs.html", M{
			"active": "logs", "logs": logs, "filter_type": filterType, "page": page,
			"auth_category": authCategory,
			"has_next":      len(logs) > perPage, "has_prev": page > 1,
		})
	}
}

// authCategoryMatches buckets esrv_auth_logs.auth_type values into "admin" (dashboard
// login/MFA), "webmail" (mailbox portal login/MFA), or "mailserver" (SMTP/IMAP — the
// same set abuseguard counts, see crud_ip_blacklist.go's smtpImapAuthTypesSQL). An
// empty category matches everything (no filter applied).
func authCategoryMatches(authType, category string) bool {
	switch category {
	case "", "all":
		return true
	case "admin":
		return authType == "admin_login" || authType == "admin_mfa"
	case "webmail":
		return authType == "webmail_login" || authType == "mailbox_mfa"
	case "mailserver":
		switch authType {
		case "sender", "mailbox", "sender_validation", "mailbox_validation", "ip", "imap_login":
			return true
		}
		return false
	default:
		return true
	}
}

func (a *App) buildLogMaps(emails []db.EmailLog) (map[int64][]db.EmailRecipientLog, map[int64][]db.EmailAttachment) {
	recipientMap := map[int64][]db.EmailRecipientLog{}
	attachMap := map[int64][]db.EmailAttachment{}
	for _, e := range emails {
		recs, _ := a.DB.ListRecipientLogsForEmail(e.ID)
		recipientMap[e.ID] = recs
		atts, _ := a.DB.ListAttachmentsForEmail(e.ID)
		attachMap[e.ID] = atts
	}
	return recipientMap, attachMap
}
