package handlers

import (
	"encoding/json"
	"net/http"

	"github.com/google/uuid"

	"mgit.msbls.de/m/paliad/internal/services"
)

// admin_event_types.go — backing endpoints for /admin/event-types
// (t-paliad-089). Routes are registered behind RequireAdminFunc in
// handlers.go, so handlers assume the caller is global_admin and only the
// operation itself needs validation. The service layer additionally
// re-asserts the role (defence in depth) before any mutation.
//
// Q6 of t-paliad-088 left firm-wide event_type creation open to any user;
// this is the moderation surface that handles the inevitable drift
// (duplicates, typos, private types worth promoting).

// GET /api/admin/event-types?include_archived=1 — every firm-wide row
// enriched with usage_count and author_display_name. Sorted live-first,
// then by category, then label_de.
func handleAdminListEventTypes(w http.ResponseWriter, r *http.Request) {
	if !requireDB(w) {
		return
	}
	uid, ok := requireUser(w, r)
	if !ok {
		return
	}
	includeArchived := r.URL.Query().Get("include_archived")
	rows, err := dbSvc.eventType.ListAllForAdmin(r.Context(), uid, services.EventTypeAdminFilter{
		IncludeArchived: includeArchived == "1" || includeArchived == "true",
	})
	if err != nil {
		writeServiceError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, rows)
}

// GET /api/admin/event-types/private — every private (is_firm_wide=false),
// non-archived row across all users. Powers the "promote pending" list in
// the moderation panel.
func handleAdminListPrivateEventTypes(w http.ResponseWriter, r *http.Request) {
	if !requireDB(w) {
		return
	}
	uid, ok := requireUser(w, r)
	if !ok {
		return
	}
	rows, err := dbSvc.eventType.ListPrivatePendingPromotion(r.Context(), uid)
	if err != nil {
		writeServiceError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, rows)
}

// adminBulkArchiveInput is the body for POST /api/admin/event-types/archive.
type adminBulkArchiveInput struct {
	IDs []uuid.UUID `json:"ids"`
}

// POST /api/admin/event-types/archive — archive a set of firm-wide rows in
// one round-trip. Returns the count of rows actually archived (already-
// archived rows are silently no-oped).
func handleAdminBulkArchiveEventTypes(w http.ResponseWriter, r *http.Request) {
	if !requireDB(w) {
		return
	}
	uid, ok := requireUser(w, r)
	if !ok {
		return
	}
	var input adminBulkArchiveInput
	if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid JSON"})
		return
	}
	if len(input.IDs) == 0 {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "ids: at least one required"})
		return
	}
	n, err := dbSvc.eventType.ArchiveBulk(r.Context(), uid, input.IDs)
	if err != nil {
		writeServiceError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, map[string]int{"archived": n})
}

// POST /api/admin/event-types/{id}/promote — flip is_firm_wide=true on a
// private row.
func handleAdminPromoteEventType(w http.ResponseWriter, r *http.Request) {
	if !requireDB(w) {
		return
	}
	uid, ok := requireUser(w, r)
	if !ok {
		return
	}
	id, err := uuid.Parse(r.PathValue("id"))
	if err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid id"})
		return
	}
	row, err := dbSvc.eventType.Promote(r.Context(), uid, id)
	if err != nil {
		writeServiceError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, row)
}

// POST /api/admin/event-types/{id}/restore — flip archived_at back to NULL.
func handleAdminRestoreEventType(w http.ResponseWriter, r *http.Request) {
	if !requireDB(w) {
		return
	}
	uid, ok := requireUser(w, r)
	if !ok {
		return
	}
	id, err := uuid.Parse(r.PathValue("id"))
	if err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid id"})
		return
	}
	row, err := dbSvc.eventType.Restore(r.Context(), uid, id)
	if err != nil {
		writeServiceError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, row)
}

// adminMergeInput is the body for POST /api/admin/event-types/merge.
// The winner survives; loser_ids get their junction rows redirected to the
// winner and are then archived.
type adminMergeInput struct {
	WinnerID uuid.UUID   `json:"winner_id"`
	LoserIDs []uuid.UUID `json:"loser_ids"`
}

// POST /api/admin/event-types/merge — fold loser_ids into winner_id.
//
// Atomic in one tx: redirect deadline_event_types rows from losers → winner
// (junction PK dedups duplicates), drop any leftover loser junction rows,
// archive the losers. On failure, all three steps roll back together.
func handleAdminMergeEventTypes(w http.ResponseWriter, r *http.Request) {
	if !requireDB(w) {
		return
	}
	uid, ok := requireUser(w, r)
	if !ok {
		return
	}
	var input adminMergeInput
	if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid JSON"})
		return
	}
	if len(input.LoserIDs) == 0 {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "loser_ids: at least one required"})
		return
	}
	if err := dbSvc.eventType.MergeIDs(r.Context(), uid, input.WinnerID, input.LoserIDs); err != nil {
		writeServiceError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
}

// handleAdminEventTypesPage serves the SPA shell for /admin/event-types.
// Same gate pattern as the other /admin/* pages — RequireAdminFunc wraps
// the route at registration time.
func handleAdminEventTypesPage(w http.ResponseWriter, r *http.Request) {
	http.ServeFile(w, r, "dist/admin-event-types.html")
}
