package handlers

import (
	"net/http"

	"github.com/google/uuid"

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

// GET /api/events?type=deadline|appointment|all&status=…&project_id=…&event_type=…&type_filter=…&from=…&to=…&personal_only=true&direct_only=true
//
// type           — discriminator for the union; default "all" (Beides on the
//                   front-end). When "deadline" or "appointment", the matching
//                   type-specific filters take effect; the others are ignored.
// status         — DeadlineStatusFilter (deadline-only on the deadline rail;
//                   bucket values also narrow the appointment rail).
// project_id     — single project scope. Ignored when personal_only=true.
// event_type     — comma-separated event_type uuids; the literal "none"
//                   toggles include-untyped (deadlines only).
// type_filter    — appointment_type filter (hearing/meeting/...). Named
//                   `type_filter` in the query string to avoid clashing with
//                   the `type` discriminator above.
// from / to      — date window applied to the canonical event_date.
// personal_only  — narrow BOTH rails to rows the caller created
//                   (t-paliad-128). Mutually exclusive with project_id —
//                   if both are sent, project_id is ignored.
// direct_only    — narrow project_id from "this project + every descendant"
//                   (t-paliad-139 subtree default) to "this project only"
//                   (t-paliad-152). No effect when project_id is unset or
//                   personal_only=true.
func handleListEvents(w http.ResponseWriter, r *http.Request) {
	if !requireDB(w) {
		return
	}
	uid, ok := requireUser(w, r)
	if !ok {
		return
	}
	q := r.URL.Query()

	filter := services.EventListFilter{
		Type:         parseEventTypeDiscriminator(q.Get("type")),
		Status:       services.DeadlineStatusFilter(q.Get("status")),
		PersonalOnly: parseBoolFlag(q.Get("personal_only")),
		DirectOnly:   parseDirectOnly(q.Get("direct_only")),
	}

	if raw := q.Get("project_id"); raw != "" {
		projectID, err := uuid.Parse(raw)
		if err != nil {
			writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid project_id"})
			return
		}
		filter.ProjectID = &projectID
	}

	ids, untyped, err := parseEventTypeFilter(q.Get("event_type"))
	if err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
		return
	}
	filter.EventTypeIDs = ids
	filter.IncludeUntyped = untyped

	if raw := q.Get("type_filter"); raw != "" {
		filter.AppointmentType = &raw
	}
	if raw := q.Get("from"); raw != "" {
		t, err := parseDateOrTime(raw)
		if err != nil {
			writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid from"})
			return
		}
		filter.From = &t
	}
	if raw := q.Get("to"); raw != "" {
		t, err := parseDateOrTime(raw)
		if err != nil {
			writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid to"})
			return
		}
		filter.To = &t
	}

	rows, err := dbSvc.event.ListVisibleForUser(r.Context(), uid, filter)
	if err != nil {
		writeServiceError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, rows)
}

// GET /api/events/summary?type=deadline|appointment|all&project_id=…&personal_only=true&direct_only=true
//
// direct_only mirrors /api/events — narrows project_id to the direct project
// (no descendants) when truthy. No effect when project_id is unset or
// personal_only=true.
func handleEventsSummary(w http.ResponseWriter, r *http.Request) {
	if !requireDB(w) {
		return
	}
	uid, ok := requireUser(w, r)
	if !ok {
		return
	}
	q := r.URL.Query()

	filter := services.EventSummaryFilter{
		Type:         parseEventTypeDiscriminator(q.Get("type")),
		PersonalOnly: parseBoolFlag(q.Get("personal_only")),
		DirectOnly:   parseDirectOnly(q.Get("direct_only")),
	}
	if raw := q.Get("project_id"); raw != "" {
		projectID, err := uuid.Parse(raw)
		if err != nil {
			writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid project_id"})
			return
		}
		filter.ProjectID = &projectID
	}

	c, err := dbSvc.event.SummaryCounts(r.Context(), uid, filter)
	if err != nil {
		writeServiceError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, c)
}

// parseEventTypeDiscriminator maps the user-facing `type=` query value
// onto the EventTypeFilter constants. Empty / "all" / unknown all degrade
// to "both" so a typo or older client doesn't 4xx.
func parseEventTypeDiscriminator(raw string) services.EventTypeFilter {
	switch raw {
	case "deadline":
		return services.EventTypeDeadline
	case "appointment":
		return services.EventTypeAppointment
	default:
		return services.EventTypeAll
	}
}

// parseBoolFlag accepts the common truthy spellings used in URL flags.
// Anything else (including the empty string) is false, matching Go's
// usual permissive treatment of optional bool query params.
func parseBoolFlag(raw string) bool {
	switch raw {
	case "true", "1", "yes", "on":
		return true
	}
	return false
}
