package api

import (
	"net/http"
	"time"

	"github.com/ponzischeme89/memby/server/internal/store"
)

// The console's window on search history.
//
// Two shapes of the same table, because they answer different questions. The summary says
// what the household looks for, which is what a library is bought and organised against;
// the log says what happened just now, which is what an operator needs the moment somebody
// reports that search is not finding something — it shows the query exactly as it was
// typed, by whom, and when.
const (
	// searchTermLimit caps the summary. Long enough to show a tail, short enough that the
	// table is read rather than scrolled.
	searchTermLimit = 25
	// searchEventLimit caps the log. It is a window on recent activity, not an export.
	searchEventLimit = 100
	// searchWindowDays is the widest window the page offers, and it is the retention
	// period rather than a round number: RecordSearch prunes to it, so a page offering
	// more would draw a flat line for the difference.
	searchWindowDays = int(store.SearchRetention / (24 * time.Hour))
)

type adminSearchesResponse struct {
	Days       int                 `json:"days"`
	Retention  int                 `json:"retentionDays"`
	Totals     store.SearchTotals  `json:"totals"`
	Terms      []store.SearchTerm  `json:"terms"`
	Recent     []store.SearchEvent `json:"recent"`
	TermLimit  int                 `json:"termLimit"`
	EventLimit int                 `json:"eventLimit"`
}

func (s *Server) handleAdminSearches(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	days := queryInt(r, "days", 7, searchWindowDays)
	since := time.Now().UTC().AddDate(0, 0, -days)

	totals, err := s.store.SearchTotals(ctx, since)
	if err != nil {
		s.loggerFor(ctx).Error("search totals failed", "error", err)
		writeError(w, http.StatusInternalServerError, "could not read search history")
		return
	}
	terms, err := s.store.SearchTerms(ctx, since, searchTermLimit)
	if err != nil {
		s.loggerFor(ctx).Error("search terms failed", "error", err)
		writeError(w, http.StatusInternalServerError, "could not read search history")
		return
	}
	recent, err := s.store.SearchEvents(ctx, since, searchEventLimit)
	if err != nil {
		s.loggerFor(ctx).Error("search events failed", "error", err)
		writeError(w, http.StatusInternalServerError, "could not read search history")
		return
	}

	// A name a search cannot be attributed to costs the column and nothing else: the
	// queries are the page, and an operator reading it after a household member's last
	// session expired must still see what was searched for.
	if users, err := s.store.KnownUsers(ctx); err == nil {
		recent = nameSearchEvents(recent, users)
	} else {
		s.loggerFor(ctx).Warn("search history names unresolved", "error", err)
	}

	writeJSON(w, http.StatusOK, adminSearchesResponse{
		Days:       days,
		Retention:  searchWindowDays,
		Totals:     totals,
		Terms:      terms,
		Recent:     recent,
		TermLimit:  searchTermLimit,
		EventLimit: searchEventLimit,
	})
}

// nameSearchEvents fills in who made each search.
//
// Resolved here rather than joined in SQL because the log is capped and the household is
// small: one read of sessions serves a whole page, where a join would repeat the lookup
// per row. An id with no session left is returned unnamed rather than dropped — the
// console prints the id, which still distinguishes one searcher from another.
func nameSearchEvents(events []store.SearchEvent, users []store.KnownUser) []store.SearchEvent {
	names := make(map[string]string, len(users))
	for _, user := range users {
		if user.Username != "" {
			names[user.ID] = user.Username
		}
	}
	for i := range events {
		events[i].Username = names[events[i].UserID]
	}
	return events
}
