package web

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

	"pete/internal/storage"
)

// isAdmin reports whether the request carries a signed-in session whose OIDC
// subject is on the admin allowlist. False when auth is off, the allowlist is
// empty, or the visitor is anonymous.
func (s *Server) isAdmin(r *http.Request) bool {
	if s.auth == nil || len(s.adminSubs) == 0 {
		return false
	}
	u := s.auth.userFromRequest(r)
	if u == nil {
		return false
	}
	return s.adminSubs[u.Sub]
}

// sourceStatus is one row of the source-health dashboard: the configured feed
// plus its persisted poll health and derived content stats.
type sourceStatus struct {
	Name     string
	Channel  string
	Healthy  bool // last poll succeeded (no consecutive failures)
	NeverRun bool // no poll recorded yet

	LastPollAt    time.Time
	LastSuccessAt time.Time
	LastError     string
	Failures      int
	LastItemCount int

	Total        int
	Classified   int
	Paywalled    int
	PaywallRate  int // percent of retained stories that are gated
	LastSeenAt   time.Time
	LastPostedAt time.Time
}

type statusPage struct {
	pageData
	Sources     []sourceStatus
	DegradedCnt int  // sources currently failing
	Admin       bool // viewer is an admin: show the full diagnostic columns
}

// handleStatus renders the source-health page. It's public: everyone sees a
// trimmed reader view (per-feed up/stale/idle and when each last updated), while
// admins additionally get the operator diagnostics (poll cadence, item counts,
// paywall rates, posting times, and raw fetch errors).
func (s *Server) handleStatus(w http.ResponseWriter, r *http.Request) {
	admin := s.isAdmin(r)
	s.track(r, "status")

	health, err := storage.ListSourceHealth()
	if err != nil {
		slog.Error("web: source health query failed", "err", err)
		http.Error(w, "internal error", http.StatusInternalServerError)
		return
	}
	stats, err := storage.SourceContentStats()
	if err != nil {
		slog.Error("web: source content stats failed", "err", err)
		http.Error(w, "internal error", http.StatusInternalServerError)
		return
	}

	rows := make([]sourceStatus, 0, len(s.sources))
	degraded := 0
	for _, src := range s.sources {
		h, hasHealth := health[src.Name]
		st := stats[src.Name]

		row := sourceStatus{
			Name:          src.Name,
			Channel:       src.Channel,
			NeverRun:      !hasHealth || h.LastPollAt == 0,
			LastError:     h.LastError,
			Failures:      h.ConsecutiveFailures,
			LastItemCount: h.LastItemCount,
			Total:         st.Total,
			Classified:    st.Classified,
			Paywalled:     st.Paywalled,
		}
		row.Healthy = hasHealth && h.ConsecutiveFailures == 0
		// Raw fetch errors leak feed-specific workarounds and upstream URLs, so
		// they stay out of the public payload entirely (not just hidden in CSS).
		if !admin {
			row.LastError = ""
		}
		if h.LastPollAt > 0 {
			row.LastPollAt = time.Unix(h.LastPollAt, 0)
		}
		if h.LastSuccessAt > 0 {
			row.LastSuccessAt = time.Unix(h.LastSuccessAt, 0)
		}
		if st.LastSeenAt > 0 {
			row.LastSeenAt = time.Unix(st.LastSeenAt, 0)
		}
		if st.LastPostedAt > 0 {
			row.LastPostedAt = time.Unix(st.LastPostedAt, 0)
		}
		if st.Total > 0 {
			row.PaywallRate = st.Paywalled * 100 / st.Total
		}
		if !row.NeverRun && !row.Healthy {
			degraded++
		}
		rows = append(rows, row)
	}

	// Failing sources first (most consecutive failures), then healthy ones by
	// name, so the owner's eye lands on what needs attention.
	sort.SliceStable(rows, func(i, j int) bool {
		if rows[i].Failures != rows[j].Failures {
			return rows[i].Failures > rows[j].Failures
		}
		return rows[i].Name < rows[j].Name
	})

	base := s.base(r)
	base.Active = "status"
	s.render(w, "status", statusPage{pageData: base, Sources: rows, DegradedCnt: degraded, Admin: admin})
}
