package handlers

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

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

// GET /api/me — returns the caller's paliad.users row (or 404 if onboarding
// hasn't happened yet). The frontend uses this to gate role-specific UI
// (partner/admin-only delete, partner-only firm_wide_visible checkbox, etc.).
//
// The 404 body includes the caller's JWT email so the onboarding form can
// pre-fill the display name from the email prefix without a second request.
func handleGetMe(w http.ResponseWriter, r *http.Request) {
	if !requireDB(w) {
		return
	}
	uid, ok := requireUser(w, r)
	if !ok {
		return
	}
	u, err := dbSvc.users.GetByID(r.Context(), uid)
	if err != nil {
		writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "internal error"})
		return
	}
	if u == nil {
		body := map[string]string{
			"error": "no paliad.users row — onboarding required",
		}
		if claims, ok := auth.ClaimsFromContext(r.Context()); ok {
			body["email"] = claims.Email
		}
		writeJSON(w, http.StatusNotFound, body)
		return
	}
	writeJSON(w, http.StatusOK, u)
}

// PATCH /api/me — mutates the caller's paliad.users row. The settings page
// sends only the fields the user touched; other fields stay as-is. Email is
// *not* updatable here — auth.users owns the email and any attempt to set it
// via this endpoint is ignored (the decode silently drops unknown fields).
func handleUpdateMe(w http.ResponseWriter, r *http.Request) {
	if !requireDB(w) {
		return
	}
	uid, ok := requireUser(w, r)
	if !ok {
		return
	}

	// Decode into a permissive map first so we can detect (and reject) a
	// client that *tried* to change their email — keeping behaviour explicit
	// is friendlier than a silent no-op.
	var peek map[string]json.RawMessage
	if err := json.NewDecoder(r.Body).Decode(&peek); err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid JSON"})
		return
	}
	if _, tryingEmail := peek["email"]; tryingEmail {
		writeJSON(w, http.StatusBadRequest, map[string]string{
			"error": "email cannot be changed — contact an administrator",
		})
		return
	}
	var input services.UpdateProfileInput
	// Re-serialise and decode into the typed struct so strict field mapping
	// applies without a second body read.
	if raw, err := json.Marshal(peek); err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid JSON"})
		return
	} else if err := json.Unmarshal(raw, &input); err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid JSON"})
		return
	}

	u, err := dbSvc.users.UpdateProfile(r.Context(), uid, input)
	if err != nil {
		switch {
		case errors.Is(err, services.ErrUserNotOnboarded):
			writeJSON(w, http.StatusNotFound, map[string]string{
				"error": "no paliad.users row — onboarding required",
			})
		default:
			writeJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
		}
		return
	}
	writeJSON(w, http.StatusOK, u)
}

// GET /api/users — minimal user list for the collaborator picker. Only callable
// by authenticated users. Response is the full models.User list (email +
// display_name + office + role).
func handleListUsers(w http.ResponseWriter, r *http.Request) {
	if !requireDB(w) {
		return
	}
	if _, ok := requireUser(w, r); !ok {
		return
	}
	users, err := dbSvc.users.List(r.Context())
	if err != nil {
		writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "internal error"})
		return
	}
	writeJSON(w, http.StatusOK, users)
}

// Removed — superseded by handleListProjectEvents in projects.go.
