package handlers

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

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

// Fristenrechner page handler: serves the static HTML. No DB dependency.
//
// Back-compat: the pre-split sidebar entry for "Verfahrensablauf" pointed at
// /tools/fristenrechner?path=a. After the t-paliad-179 split, that landing is
// owned by /tools/verfahrensablauf. A naked ?path=a (no Akte context — i.e.
// no ?project=) is the bookmarked-legacy-entry case → 302 to the new route.
// ?project=<uuid>&path=a is the Akte-mode internal wizard pathway and stays
// on /tools/fristenrechner so the wizard state survives a refresh.
func handleFristenrechnerPage(w http.ResponseWriter, r *http.Request) {
	q := r.URL.Query()
	if q.Get("path") == "a" && q.Get("project") == "" {
		http.Redirect(w, r, "/tools/verfahrensablauf", http.StatusFound)
		return
	}
	http.ServeFile(w, r, "dist/fristenrechner.html")
}

// Verfahrensablauf page handler (t-paliad-179 Slice 1): the dedicated
// abstract-browse surface for procedural shape. No DB dependency — the page
// shell is static HTML; the calculator API still drives the timeline render.
func handleVerfahrensablaufPage(w http.ResponseWriter, r *http.Request) {
	http.ServeFile(w, r, "dist/verfahrensablauf.html")
}

// POST /api/tools/fristenrechner — calculate the UI timeline for a proceeding.
//
// Phase C: routes through FristenrechnerService which pulls rules from
// paliad.deadline_rules. When DATABASE_URL is unset, returns 503; the page
// itself still renders because it's static HTML.
func handleFristenrechnerAPI(w http.ResponseWriter, r *http.Request) {
	if dbSvc == nil || dbSvc.fristenrechner == nil {
		writeJSON(w, http.StatusServiceUnavailable, map[string]string{
			"error": "Fristenrechner ist vorübergehend nicht verfügbar (keine Datenbank).",
		})
		return
	}
	var req struct {
		ProceedingType  string            `json:"proceedingType"`
		TriggerDate     string            `json:"triggerDate"`
		PriorityDate    string            `json:"priorityDate,omitempty"`
		Flags           []string          `json:"flags,omitempty"`
		AnchorOverrides map[string]string `json:"anchorOverrides,omitempty"`
		CourtID         string            `json:"courtId,omitempty"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "Ungültige Anfrage"})
		return
	}
	if req.ProceedingType == "" || req.TriggerDate == "" {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "proceedingType und triggerDate sind erforderlich"})
		return
	}

	resp, err := dbSvc.fristenrechner.Calculate(r.Context(), req.ProceedingType, req.TriggerDate, services.CalcOptions{
		PriorityDateStr: req.PriorityDate,
		Flags:           req.Flags,
		AnchorOverrides: req.AnchorOverrides,
		CourtID:         req.CourtID,
	})
	if err != nil {
		if errors.Is(err, services.ErrUnknownProceedingType) {
			writeJSON(w, http.StatusBadRequest, map[string]string{"error": "unbekannter Verfahrenstyp: " + req.ProceedingType})
			return
		}
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
		return
	}
	writeJSON(w, http.StatusOK, resp)
}

// POST /api/tools/fristenrechner/calculate-rule — single-rule calc for
// the v4 (t-paliad-136 Phase B) result-card click flow.
//
// Body: { ruleId? } OR { proceedingCode, ruleLocalCode }, plus
//       triggerDate (YYYY-MM-DD, required) and flags? (string array,
//       optional condition_flag inputs).
//
// Returns a RuleCalculation (see services.RuleCalculation) — the rule
// metadata + computed dueDate / originalDate / adjustmentReason. Used by
// the result-card calc panel; distinct from the full-timeline endpoint
// at POST /api/tools/fristenrechner.
func handleFristenrechnerCalculateRule(w http.ResponseWriter, r *http.Request) {
	if dbSvc == nil || dbSvc.fristenrechner == nil {
		writeJSON(w, http.StatusServiceUnavailable, map[string]string{
			"error": "Fristenrechner ist vorübergehend nicht verfügbar (keine Datenbank).",
		})
		return
	}
	var req struct {
		RuleID         string   `json:"ruleId"`
		ProceedingCode string   `json:"proceedingCode"`
		RuleLocalCode  string   `json:"ruleLocalCode"`
		TriggerDate    string   `json:"triggerDate"`
		Flags          []string `json:"flags,omitempty"`
		CourtID        string   `json:"courtId,omitempty"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "Ungültige Anfrage"})
		return
	}
	if req.TriggerDate == "" {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "triggerDate ist erforderlich"})
		return
	}
	if req.RuleID == "" && (req.ProceedingCode == "" || req.RuleLocalCode == "") {
		writeJSON(w, http.StatusBadRequest, map[string]string{
			"error": "Entweder ruleId oder (proceedingCode + ruleLocalCode) ist erforderlich",
		})
		return
	}

	resp, err := dbSvc.fristenrechner.CalculateRule(r.Context(), services.CalcRuleParams{
		RuleID:         req.RuleID,
		ProceedingCode: req.ProceedingCode,
		RuleLocalCode:  req.RuleLocalCode,
		TriggerDate:    req.TriggerDate,
		Flags:          req.Flags,
		CourtID:        req.CourtID,
	})
	if err != nil {
		switch {
		case errors.Is(err, services.ErrUnknownRule):
			writeJSON(w, http.StatusBadRequest, map[string]string{"error": "unbekannte Regel"})
		case errors.Is(err, services.ErrUnknownProceedingType):
			writeJSON(w, http.StatusBadRequest, map[string]string{"error": "unbekannter Verfahrenstyp: " + req.ProceedingCode})
		default:
			writeJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
		}
		return
	}
	writeJSON(w, http.StatusOK, resp)
}

// GET /api/tools/proceeding-types — metadata list for the wizard buttons.
// Returns 503 with an empty array when DATABASE_URL is unset so the page
// still renders (buttons are server-rendered from tsx and don't depend on
// this endpoint for existence, only for dynamic list updates).
func handleProceedingTypes(w http.ResponseWriter, r *http.Request) {
	if dbSvc == nil || dbSvc.fristenrechner == nil {
		writeJSON(w, http.StatusServiceUnavailable, map[string]string{
			"error": "Verfahrenstypen vorübergehend nicht verfügbar (keine Datenbank).",
		})
		return
	}
	types, err := dbSvc.fristenrechner.ListFristenrechnerTypes(r.Context())
	if err != nil {
		writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "konnte Verfahrenstypen nicht laden"})
		return
	}
	writeJSON(w, http.StatusOK, types)
}

// GET /api/tools/trigger-events — list active UPC trigger events for the
// "Was kommt nach…" mode picker. Sorted alphabetically by name.
func handleTriggerEventsList(w http.ResponseWriter, r *http.Request) {
	if dbSvc == nil || dbSvc.eventDeadline == nil {
		writeJSON(w, http.StatusServiceUnavailable, map[string]string{
			"error": "Trigger-Ereignisse vorübergehend nicht verfügbar (keine Datenbank).",
		})
		return
	}
	events, err := dbSvc.eventDeadline.ListTriggerEvents(r.Context())
	if err != nil {
		writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "konnte Trigger-Ereignisse nicht laden"})
		return
	}
	writeJSON(w, http.StatusOK, events)
}

// POST /api/tools/event-deadlines — compute all deadlines flowing from a
// trigger event + date. Body: {"triggerEventId": <int>, "triggerDate": "YYYY-MM-DD"}.
func handleEventDeadlinesCalculate(w http.ResponseWriter, r *http.Request) {
	if dbSvc == nil || dbSvc.eventDeadline == nil {
		writeJSON(w, http.StatusServiceUnavailable, map[string]string{
			"error": "Fristenrechner ist vorübergehend nicht verfügbar (keine Datenbank).",
		})
		return
	}
	var req struct {
		TriggerEventID int64  `json:"triggerEventId"`
		TriggerDate    string `json:"triggerDate"`
		CourtID        string `json:"courtId"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "Ungültige Anfrage"})
		return
	}
	if req.TriggerEventID <= 0 || req.TriggerDate == "" {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "triggerEventId und triggerDate sind erforderlich"})
		return
	}
	resp, err := dbSvc.eventDeadline.Calculate(r.Context(), req.TriggerEventID, req.TriggerDate, req.CourtID)
	if err != nil {
		if errors.Is(err, services.ErrUnknownTriggerEvent) {
			writeJSON(w, http.StatusBadRequest, map[string]string{"error": "unbekanntes Trigger-Ereignis"})
			return
		}
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
		return
	}
	writeJSON(w, http.StatusOK, resp)
}

// GET /api/tools/courts — list active courts for the Fristenrechner court
// picker. Optional ?courtType=UPC-LD filter narrows to a single tier so the
// UI can render only the courts compatible with the selected proceeding.
// Returns the deadline-computation slice (id, code, names, country, regime,
// court_type, sort_order) — NOT the full Gerichtsverzeichnis catalog. The
// rich addresses / phone / languages payload still lives at /api/courts.
func handleCourtsList(w http.ResponseWriter, r *http.Request) {
	if dbSvc == nil || dbSvc.courts == nil {
		writeJSON(w, http.StatusServiceUnavailable, map[string]string{
			"error": "Gerichte vorübergehend nicht verfügbar (keine Datenbank).",
		})
		return
	}
	courtType := r.URL.Query().Get("courtType")
	var (
		courts []services.Court
		err    error
	)
	if courtType != "" {
		courts, err = dbSvc.courts.ByCourtType(courtType)
	} else {
		courts, err = dbSvc.courts.All()
	}
	if err != nil {
		writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "konnte Gerichte nicht laden"})
		return
	}
	writeJSON(w, http.StatusOK, courts)
}
