package http

import (
	"encoding/json"
	"log/slog"
	stdhttp "net/http"
	"strings"
	"time"

	"github.com/go-chi/chi/v5"
	"github.com/oklog/ulid/v2"

	"gitea.dcglab.co.uk/steve/restic-manager/internal/store"
)

type alertsPage struct {
	Filter     store.AlertFilter
	Alerts     []store.Alert
	Counts     alertCounts
	HostNames  map[string]string // host_id → name for table rendering
	Usernames  map[string]string // user_id → username for the "ack'd by …" line
	RefreshURL string            // self-URL for the live-refresh poll
}

type alertCounts struct {
	Open         int
	Acknowledged int
	Resolved24h  int
}

// handleUIAlerts renders the alerts page with the chosen filters.
func (s *Server) handleUIAlerts(w stdhttp.ResponseWriter, r *stdhttp.Request) {
	u := s.requireUIUser(w, r)
	if u == nil {
		return
	}
	q := r.URL.Query()
	f := store.AlertFilter{
		Status:   q.Get("status"),
		Severity: q.Get("severity"),
		HostID:   q.Get("host_id"),
		Search:   strings.TrimSpace(q.Get("q")),
		Limit:    200,
	}
	if f.Status == "" {
		f.Status = "open"
	}

	alerts, err := s.deps.Store.ListAlerts(r.Context(), f)
	if err != nil {
		slog.Error("ui alerts: list", "err", err)
		stdhttp.Error(w, "internal", stdhttp.StatusInternalServerError)
		return
	}

	page := alertsPage{
		Filter:     f,
		Alerts:     alerts,
		HostNames:  map[string]string{},
		Usernames:  map[string]string{},
		RefreshURL: r.URL.RequestURI(),
	}
	if hosts, err := s.deps.Store.ListHosts(r.Context()); err == nil {
		for _, h := range hosts {
			page.HostNames[h.ID] = h.Name
		}
	}
	// Resolve user IDs that appear on acknowledged rows to usernames so
	// the "ack'd by …" line shows a human name rather than the
	// underlying ULID. Cheap at fleet sizes we care about (one extra
	// query per alerts page render). Disabled users are still resolved
	// — operators want to know *who* ack'd, even if the account is
	// since gone.
	if users, err := s.deps.Store.ListUsers(r.Context(), store.UserSort{}); err == nil {
		for _, usr := range users {
			page.Usernames[usr.ID] = usr.Username
		}
	}
	page.Counts = computeAlertCounts(s, r)

	view := s.baseView(r, u)
	view.Title = "Alerts · restic-manager"
	view.Active = "alerts"
	view.Page = page
	if err := s.deps.UI.Render(w, "alerts", view); err != nil {
		slog.Error("ui alerts: render", "err", err)
	}
}

func computeAlertCounts(s *Server, r *stdhttp.Request) alertCounts {
	open, _ := s.deps.Store.ListAlerts(r.Context(),
		store.AlertFilter{Status: "open"})
	acked, _ := s.deps.Store.ListAlerts(r.Context(),
		store.AlertFilter{Status: "acknowledged"})
	cutoff := time.Now().UTC().Add(-24 * time.Hour)
	all, _ := s.deps.Store.ListAlerts(r.Context(),
		store.AlertFilter{Status: "resolved"})
	res := 0
	for _, a := range all {
		if a.ResolvedAt != nil && a.ResolvedAt.After(cutoff) {
			res++
		}
	}
	return alertCounts{Open: len(open), Acknowledged: len(acked), Resolved24h: res}
}

// handleAPIAlerts is the JSON list — same filter shape.
func (s *Server) handleAPIAlerts(w stdhttp.ResponseWriter, r *stdhttp.Request) {
	if _, ok := s.requireUser(r); !ok {
		writeJSONError(w, stdhttp.StatusUnauthorized, "unauthorised", "")
		return
	}
	q := r.URL.Query()
	f := store.AlertFilter{
		Status:   q.Get("status"),
		Severity: q.Get("severity"),
		HostID:   q.Get("host_id"),
		Search:   strings.TrimSpace(q.Get("q")),
		Limit:    200,
	}
	alerts, err := s.deps.Store.ListAlerts(r.Context(), f)
	if err != nil {
		writeJSONError(w, stdhttp.StatusInternalServerError, "internal", "")
		return
	}
	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(alerts)
}

// handleUIAlertAcknowledge is POST /alerts/{id}/acknowledge.
func (s *Server) handleUIAlertAcknowledge(w stdhttp.ResponseWriter, r *stdhttp.Request) {
	u := s.requireUIUser(w, r)
	if u == nil {
		return
	}
	id := chi.URLParam(r, "id")
	if id == "" {
		stdhttp.Error(w, "missing id", stdhttp.StatusBadRequest)
		return
	}
	var err error
	if s.deps.AlertEngine != nil {
		err = s.deps.AlertEngine.Acknowledge(r.Context(), id, u.ID, time.Now().UTC())
	} else {
		err = s.deps.Store.Acknowledge(r.Context(), id, u.ID, time.Now().UTC())
	}
	if err != nil {
		slog.Warn("ui alerts: ack", "err", err)
	}
	_ = s.deps.Store.AppendAudit(r.Context(), store.AuditEntry{
		ID: ulid.Make().String(), UserID: &u.ID, Actor: "user",
		Action:     "alert.acknowledge",
		TargetKind: ptr("alert"), TargetID: &id,
		TS: time.Now().UTC(),
	})
	if r.Header.Get("HX-Request") == "true" {
		w.Header().Set("HX-Redirect", "/alerts?"+r.URL.RawQuery)
		w.WriteHeader(stdhttp.StatusNoContent)
		return
	}
	stdhttp.Redirect(w, r, "/alerts", stdhttp.StatusSeeOther)
}

// handleUIAlertResolve is POST /alerts/{id}/resolve.
func (s *Server) handleUIAlertResolve(w stdhttp.ResponseWriter, r *stdhttp.Request) {
	u := s.requireUIUser(w, r)
	if u == nil {
		return
	}
	id := chi.URLParam(r, "id")
	if id == "" {
		stdhttp.Error(w, "missing id", stdhttp.StatusBadRequest)
		return
	}
	var err error
	if s.deps.AlertEngine != nil {
		err = s.deps.AlertEngine.Resolve(r.Context(), id, time.Now().UTC())
	} else {
		err = s.deps.Store.Resolve(r.Context(), id, time.Now().UTC())
	}
	if err != nil {
		slog.Warn("ui alerts: resolve", "err", err)
	}
	_ = s.deps.Store.AppendAudit(r.Context(), store.AuditEntry{
		ID: ulid.Make().String(), UserID: &u.ID, Actor: "user",
		Action:     "alert.resolve",
		TargetKind: ptr("alert"), TargetID: &id,
		TS: time.Now().UTC(),
	})
	if r.Header.Get("HX-Request") == "true" {
		w.Header().Set("HX-Redirect", "/alerts?"+r.URL.RawQuery)
		w.WriteHeader(stdhttp.StatusNoContent)
		return
	}
	stdhttp.Redirect(w, r, "/alerts", stdhttp.StatusSeeOther)
}
