package handlers

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

	"github.com/google/uuid"

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

// Admin rule-editor endpoints — Phase 3 Slice 11a (t-paliad-191).
// Every handler in this file is wired through auth.RequireAdminFunc
// in handlers.go, so the handlers themselves assume the caller is a
// global_admin and only validate request shape.
//
// Every write endpoint takes an audit_reason field on the request
// body. The service layer sets paliad.audit_reason in the same tx
// before the UPDATE so mig 079's audit trigger captures the rationale
// forever. Missing reason → 400 (ErrAuditReasonRequired).
//
// Lifecycle invariants live in the service layer: ErrInvalidLifecycleState
// is mapped to 409 Conflict so the editor UI can show a clear "must
// clone first" hint.

// GET /admin/api/rules — paginated list with filters.
func handleAdminListRules(w http.ResponseWriter, r *http.Request) {
	if dbSvc == nil || dbSvc.ruleEditor == nil {
		writeJSON(w, http.StatusServiceUnavailable, map[string]string{"error": "rule editor unavailable"})
		return
	}
	q := r.URL.Query()
	f := services.ListRulesFilter{
		LifecycleState: q.Get("lifecycle_state"),
		Query:          q.Get("q"),
	}
	if v := q.Get("proceeding_type_id"); v != "" {
		n, err := strconv.Atoi(v)
		if err != nil {
			writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid proceeding_type_id"})
			return
		}
		f.ProceedingTypeID = &n
	}
	if v := q.Get("trigger_event_id"); v != "" {
		n, err := strconv.ParseInt(v, 10, 64)
		if err != nil {
			writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid trigger_event_id"})
			return
		}
		f.TriggerEventID = &n
	}
	if v := q.Get("offset"); v != "" {
		n, err := strconv.Atoi(v)
		if err != nil || n < 0 {
			writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid offset"})
			return
		}
		f.Offset = n
	}
	if v := q.Get("limit"); v != "" {
		n, err := strconv.Atoi(v)
		if err != nil || n < 0 {
			writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid limit"})
			return
		}
		f.Limit = n
	}
	rows, err := dbSvc.ruleEditor.ListRules(r.Context(), f)
	if err != nil {
		writeRuleEditorError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, rows)
}

// GET /admin/api/rules/{id}
func handleAdminGetRule(w http.ResponseWriter, r *http.Request) {
	if dbSvc == nil || dbSvc.ruleEditor == nil {
		writeJSON(w, http.StatusServiceUnavailable, map[string]string{"error": "rule editor unavailable"})
		return
	}
	id, ok := parseRuleID(w, r)
	if !ok {
		return
	}
	row, err := dbSvc.ruleEditor.GetByID(r.Context(), id)
	if err != nil {
		writeRuleEditorError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, row)
}

// POST /admin/api/rules — create draft.
func handleAdminCreateRule(w http.ResponseWriter, r *http.Request) {
	if dbSvc == nil || dbSvc.ruleEditor == nil {
		writeJSON(w, http.StatusServiceUnavailable, map[string]string{"error": "rule editor unavailable"})
		return
	}
	var body struct {
		services.CreateRuleInput
		Reason string `json:"reason"`
	}
	if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid JSON"})
		return
	}
	row, err := dbSvc.ruleEditor.Create(r.Context(), body.CreateRuleInput, body.Reason)
	if err != nil {
		writeRuleEditorError(w, err)
		return
	}
	writeJSON(w, http.StatusCreated, row)
}

// PATCH /admin/api/rules/{id} — partial update of a draft.
func handleAdminPatchRule(w http.ResponseWriter, r *http.Request) {
	if dbSvc == nil || dbSvc.ruleEditor == nil {
		writeJSON(w, http.StatusServiceUnavailable, map[string]string{"error": "rule editor unavailable"})
		return
	}
	id, ok := parseRuleID(w, r)
	if !ok {
		return
	}
	var body struct {
		services.RulePatch
		Reason string `json:"reason"`
	}
	if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid JSON"})
		return
	}
	row, err := dbSvc.ruleEditor.UpdateDraft(r.Context(), id, body.RulePatch, body.Reason)
	if err != nil {
		writeRuleEditorError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, row)
}

// POST /admin/api/rules/{id}/clone-as-draft
func handleAdminCloneAsDraft(w http.ResponseWriter, r *http.Request) {
	if dbSvc == nil || dbSvc.ruleEditor == nil {
		writeJSON(w, http.StatusServiceUnavailable, map[string]string{"error": "rule editor unavailable"})
		return
	}
	id, ok := parseRuleID(w, r)
	if !ok {
		return
	}
	reason, ok := decodeReason(w, r)
	if !ok {
		return
	}
	row, err := dbSvc.ruleEditor.CloneAsDraft(r.Context(), id, reason)
	if err != nil {
		writeRuleEditorError(w, err)
		return
	}
	writeJSON(w, http.StatusCreated, row)
}

// POST /admin/api/rules/{id}/publish
func handleAdminPublishRule(w http.ResponseWriter, r *http.Request) {
	if dbSvc == nil || dbSvc.ruleEditor == nil {
		writeJSON(w, http.StatusServiceUnavailable, map[string]string{"error": "rule editor unavailable"})
		return
	}
	id, ok := parseRuleID(w, r)
	if !ok {
		return
	}
	reason, ok := decodeReason(w, r)
	if !ok {
		return
	}
	row, err := dbSvc.ruleEditor.Publish(r.Context(), id, reason)
	if err != nil {
		writeRuleEditorError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, row)
}

// POST /admin/api/rules/{id}/archive
func handleAdminArchiveRule(w http.ResponseWriter, r *http.Request) {
	if dbSvc == nil || dbSvc.ruleEditor == nil {
		writeJSON(w, http.StatusServiceUnavailable, map[string]string{"error": "rule editor unavailable"})
		return
	}
	id, ok := parseRuleID(w, r)
	if !ok {
		return
	}
	reason, ok := decodeReason(w, r)
	if !ok {
		return
	}
	row, err := dbSvc.ruleEditor.Archive(r.Context(), id, reason)
	if err != nil {
		writeRuleEditorError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, row)
}

// POST /admin/api/rules/{id}/restore
func handleAdminRestoreRule(w http.ResponseWriter, r *http.Request) {
	if dbSvc == nil || dbSvc.ruleEditor == nil {
		writeJSON(w, http.StatusServiceUnavailable, map[string]string{"error": "rule editor unavailable"})
		return
	}
	id, ok := parseRuleID(w, r)
	if !ok {
		return
	}
	reason, ok := decodeReason(w, r)
	if !ok {
		return
	}
	row, err := dbSvc.ruleEditor.Restore(r.Context(), id, reason)
	if err != nil {
		writeRuleEditorError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, row)
}

// GET /admin/api/rules/{id}/audit?offset=N&limit=M
func handleAdminGetRuleAudit(w http.ResponseWriter, r *http.Request) {
	if dbSvc == nil || dbSvc.ruleEditor == nil {
		writeJSON(w, http.StatusServiceUnavailable, map[string]string{"error": "rule editor unavailable"})
		return
	}
	id, ok := parseRuleID(w, r)
	if !ok {
		return
	}
	offset, limit := 0, 0
	q := r.URL.Query()
	if v := q.Get("offset"); v != "" {
		n, err := strconv.Atoi(v)
		if err != nil || n < 0 {
			writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid offset"})
			return
		}
		offset = n
	}
	if v := q.Get("limit"); v != "" {
		n, err := strconv.Atoi(v)
		if err != nil || n < 0 {
			writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid limit"})
			return
		}
		limit = n
	}
	rows, err := dbSvc.ruleEditor.ListAudit(r.Context(), id, offset, limit)
	if err != nil {
		writeRuleEditorError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, rows)
}

// GET /admin/api/rules/{id}/preview?trigger_date=YYYY-MM-DD&flags=a,b&court_id=...
func handleAdminPreviewRule(w http.ResponseWriter, r *http.Request) {
	if dbSvc == nil || dbSvc.ruleEditor == nil || dbSvc.fristenrechner == nil {
		writeJSON(w, http.StatusServiceUnavailable, map[string]string{"error": "rule editor unavailable"})
		return
	}
	id, ok := parseRuleID(w, r)
	if !ok {
		return
	}
	q := r.URL.Query()
	triggerDate := q.Get("trigger_date")
	if triggerDate == "" {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "trigger_date required"})
		return
	}
	var flags []string
	if v := q.Get("flags"); v != "" {
		for _, f := range splitCSV(v) {
			if f != "" {
				flags = append(flags, f)
			}
		}
	}
	courtID := q.Get("court_id")
	resp, err := dbSvc.ruleEditor.Preview(r.Context(), dbSvc.fristenrechner, id, triggerDate, flags, courtID)
	if err != nil {
		writeRuleEditorError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, resp)
}

// GET /admin/api/rules/export-migrations?since=<audit_id>
func handleAdminExportRuleMigrations(w http.ResponseWriter, r *http.Request) {
	if dbSvc == nil || dbSvc.ruleEditor == nil {
		writeJSON(w, http.StatusServiceUnavailable, map[string]string{"error": "rule editor unavailable"})
		return
	}
	since := r.URL.Query().Get("since")
	out, err := dbSvc.ruleEditor.ExportMigrationsSince(r.Context(), since)
	if err != nil {
		writeRuleEditorError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, out)
}

// =============================================================================
// Page handlers — serve the static SPA shells. Auth + admin gate live
// at the route registration in handlers.go.
// =============================================================================

func handleAdminRulesListPage(w http.ResponseWriter, r *http.Request) {
	http.ServeFile(w, r, "dist/admin-rules-list.html")
}

func handleAdminRulesEditPage(w http.ResponseWriter, r *http.Request) {
	http.ServeFile(w, r, "dist/admin-rules-edit.html")
}

func handleAdminRulesExportPage(w http.ResponseWriter, r *http.Request) {
	http.ServeFile(w, r, "dist/admin-rules-export.html")
}

// =============================================================================
// helpers
// =============================================================================

func parseRuleID(w http.ResponseWriter, r *http.Request) (uuid.UUID, bool) {
	id, err := uuid.Parse(r.PathValue("id"))
	if err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid id"})
		return uuid.Nil, false
	}
	return id, true
}

func decodeReason(w http.ResponseWriter, r *http.Request) (string, bool) {
	var body struct {
		Reason string `json:"reason"`
	}
	if r.ContentLength > 0 {
		if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
			writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid JSON"})
			return "", false
		}
	}
	return body.Reason, true
}

// writeRuleEditorError maps the service-level typed errors to HTTP statuses.
// Distinct from writeServiceError (projects path) because the rule
// editor's lifecycle errors map to 409 Conflict, which the project
// service doesn't use.
func writeRuleEditorError(w http.ResponseWriter, err error) {
	switch {
	case errors.Is(err, services.ErrRuleNotFound):
		writeJSON(w, http.StatusNotFound, map[string]string{"error": "rule not found"})
	case errors.Is(err, services.ErrAuditReasonRequired):
		writeJSON(w, http.StatusBadRequest, map[string]string{
			"error":   "audit_reason required",
			"message": "Every rule-editor write must include a non-empty `reason` body field.",
		})
	case errors.Is(err, services.ErrInvalidLifecycleState):
		writeJSON(w, http.StatusConflict, map[string]string{"error": err.Error()})
	case errors.Is(err, services.ErrCyclicSpawn):
		writeJSON(w, http.StatusConflict, map[string]string{"error": err.Error()})
	case errors.Is(err, services.ErrOrphanAlreadyResolved):
		writeJSON(w, http.StatusConflict, map[string]string{"error": err.Error()})
	case errors.Is(err, services.ErrOrphanCandidateMismatch):
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
	case errors.Is(err, services.ErrInvalidInput):
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
	default:
		writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}
}

// =============================================================================
// Orphan-resolution handlers — Slice 11b admin add-on.
// Lists the unresolved rows from paliad.deadline_rule_backfill_orphans
// (mig 089) and lets an admin hand-bind each to one of the matcher's
// candidate rule_ids. The resolve write lands in a single tx via the
// rule editor service so the deadline row + the staging row stay in
// sync; admin-only at the route layer.
// =============================================================================

// GET /admin/api/orphans
func handleAdminListOrphans(w http.ResponseWriter, r *http.Request) {
	if dbSvc == nil || dbSvc.ruleEditor == nil {
		writeJSON(w, http.StatusServiceUnavailable, map[string]string{"error": "rule editor unavailable"})
		return
	}
	rows, err := dbSvc.ruleEditor.ListOrphans(r.Context())
	if err != nil {
		writeRuleEditorError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, rows)
}

// POST /admin/api/orphans/{id}/resolve  body: {"rule_id": "...", "reason": "..."}
func handleAdminResolveOrphan(w http.ResponseWriter, r *http.Request) {
	if dbSvc == nil || dbSvc.ruleEditor == nil {
		writeJSON(w, http.StatusServiceUnavailable, map[string]string{"error": "rule editor unavailable"})
		return
	}
	id, err := uuid.Parse(r.PathValue("id"))
	if err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid id"})
		return
	}
	var body struct {
		RuleID string `json:"rule_id"`
		Reason string `json:"reason"`
	}
	if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid JSON"})
		return
	}
	ruleID, err := uuid.Parse(body.RuleID)
	if err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid rule_id"})
		return
	}
	if err := dbSvc.ruleEditor.ResolveOrphan(r.Context(), id, ruleID, body.Reason); err != nil {
		writeRuleEditorError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, map[string]string{"status": "resolved"})
}
