package handlers

import (
	"net/http"
	"strings"
	"time"

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

// GET /api/agenda?from=YYYY-MM-DD&to=YYYY-MM-DD&types=deadlines,appointments
//
// Returns a merged, date-sorted slice of AgendaItems across every Project the
// caller can see. Defaults: from=today, to=today+30d, both types included.
func handleAgendaAPI(w http.ResponseWriter, r *http.Request) {
	if !requireDB(w) {
		return
	}
	uid, ok := requireUser(w, r)
	if !ok {
		return
	}

	filter, err := parseAgendaFilter(r)
	if err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
		return
	}

	items, err := dbSvc.agenda.List(r.Context(), uid, filter)
	if err != nil {
		writeServiceError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, items)
}

// GET /agenda — server-rendered page shell. Server-side hydration pattern
// (same as /dashboard): the handler splices the initial payload into the HTML
// so the client can paint the timeline on first frame.
func handleAgendaPage(w http.ResponseWriter, r *http.Request) {
	uid, hasUser := auth.UserIDFromContext(r.Context())
	filter, err := parseAgendaFilter(r)
	if err != nil {
		// Fall back to defaults on bad query strings — landing with garbled
		// params shouldn't 400 the page, only the API endpoint.
		filter = defaultAgendaFilter()
	}

	var payload []byte
	if hasUser && dbSvc != nil {
		if items, err := dbSvc.agenda.List(r.Context(), uid, filter); err == nil {
			payload = mustJSON(map[string]any{
				"items": items,
				"from":  filter.From.Format("2006-01-02"),
				"to":    filter.To.Format("2006-01-02"),
				"types": agendaTypesFromFilter(filter),
			})
		}
	}
	serveAgendaShell(w, r, payload)
}

// parseAgendaFilter reads ?from, ?to, ?types from the query string and
// returns a normalised AgendaFilter with UTC day-aligned boundaries. Unknown
// type tokens are ignored rather than rejected.
func parseAgendaFilter(r *http.Request) (services.AgendaFilter, error) {
	q := r.URL.Query()

	filter := defaultAgendaFilter()

	if raw := strings.TrimSpace(q.Get("from")); raw != "" {
		t, err := time.Parse("2006-01-02", raw)
		if err != nil {
			return services.AgendaFilter{}, fmtInvalidDate("from")
		}
		filter.From = time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, time.UTC)
	}
	if raw := strings.TrimSpace(q.Get("to")); raw != "" {
		t, err := time.Parse("2006-01-02", raw)
		if err != nil {
			return services.AgendaFilter{}, fmtInvalidDate("to")
		}
		// `to` is inclusive in the query UX — translate to an exclusive
		// upper bound at the next UTC midnight so an all-day deadline on
		// that date is included.
		filter.To = time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, time.UTC).AddDate(0, 0, 1)
	}
	if filter.To.Before(filter.From) {
		filter.To = filter.From.AddDate(0, 0, 1)
	}

	if raw := strings.TrimSpace(q.Get("types")); raw != "" {
		filter.IncludeDeadlines = false
		filter.IncludeAppointments = false
		for tok := range strings.SplitSeq(raw, ",") {
			switch strings.TrimSpace(tok) {
			case "deadlines":
				filter.IncludeDeadlines = true
			case "appointments":
				filter.IncludeAppointments = true
			}
		}
		if !filter.IncludeDeadlines && !filter.IncludeAppointments {
			// Empty/unknown token list → fall back to both so the page still
			// shows something useful.
			filter.IncludeDeadlines = true
			filter.IncludeAppointments = true
		}
	}

	if ids, untyped, err := parseEventTypeFilter(q.Get("event_type")); err != nil {
		return services.AgendaFilter{}, err
	} else {
		filter.EventTypeIDs = ids
		filter.IncludeUntyped = untyped
	}
	return filter, nil
}

// defaultAgendaFilter is today → today+30d, both types.
func defaultAgendaFilter() services.AgendaFilter {
	now := time.Now().UTC()
	from := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC)
	return services.AgendaFilter{
		From:                from,
		To:                  from.AddDate(0, 0, 30),
		IncludeDeadlines:    true,
		IncludeAppointments: true,
	}
}

func agendaTypesFromFilter(f services.AgendaFilter) []string {
	out := make([]string, 0, 2)
	if f.IncludeDeadlines {
		out = append(out, "deadlines")
	}
	if f.IncludeAppointments {
		out = append(out, "appointments")
	}
	return out
}

type agendaErr struct{ msg string }

func (e agendaErr) Error() string { return e.msg }

func fmtInvalidDate(field string) error {
	return agendaErr{msg: "invalid " + field + " — expected YYYY-MM-DD"}
}
