Ctrl K
ComunidadGuías…

internal/handler/phase4_handler.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package handler

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

	"github.com/go-chi/chi/v5"
	"github.com/johnzastrow/actalog/internal/domain"
	"github.com/johnzastrow/actalog/internal/service"
	"github.com/johnzastrow/actalog/pkg/logger"
	"github.com/johnzastrow/actalog/pkg/middleware"
)

type Phase4Handler struct {
	phase4Service *service.Phase4Service
	logger        *logger.Logger
}

func NewPhase4Handler(phase4Service *service.Phase4Service, logger *logger.Logger) *Phase4Handler {
	return &Phase4Handler{
		phase4Service: phase4Service,
		logger:        logger,
	}
}

// ============================================
// DOCUMENT HANDLERS (Admin)
// ============================================

// CreateDocument handles POST /api/gyms/{gym_id}/documents
func (h *Phase4Handler) CreateDocument(w http.ResponseWriter, r *http.Request) {
	userID, ok := middleware.GetUserID(r.Context())
	if !ok {
		respondError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	gymIDStr := chi.URLParam(r, "gym_id")
	gymID, err := strconv.ParseInt(gymIDStr, 10, 64)
	if err != nil {
		respondError(w, http.StatusBadRequest, "Invalid gym ID")
		return
	}

	var req struct {
		Name             string  `json:"name"`
		Description      *string `json:"description"`
		DocumentType     string  `json:"document_type"`
		URL              *string `json:"url"`
		IsRequired       bool    `json:"is_required"`
		ExpiresAfterDays *int    `json:"expires_after_days"`
	}

	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		respondError(w, http.StatusBadRequest, "Invalid request body")
		return
	}

	if req.Name == "" {
		respondError(w, http.StatusBadRequest, "Document name is required")
		return
	}

	if req.DocumentType == "" {
		req.DocumentType = domain.DocumentTypeOther
	}

	doc := &domain.Document{
		OrganizationID:   gymID,
		Name:             req.Name,
		Description:      req.Description,
		DocumentType:     req.DocumentType,
		URL:              req.URL,
		IsRequired:       req.IsRequired,
		ExpiresAfterDays: req.ExpiresAfterDays,
		IsActive:         true,
	}

	if err := h.phase4Service.CreateDocument(userID, doc); err != nil {
		h.logger.Error("Failed to create document: %v", err)
		respondError(w, http.StatusInternalServerError, "Failed to create document")
		return
	}

	respondJSON(w, http.StatusCreated, doc)
}

// ListDocuments handles GET /api/gyms/{gym_id}/documents
func (h *Phase4Handler) ListDocuments(w http.ResponseWriter, r *http.Request) {
	gymIDStr := chi.URLParam(r, "gym_id")
	gymID, err := strconv.ParseInt(gymIDStr, 10, 64)
	if err != nil {
		respondError(w, http.StatusBadRequest, "Invalid gym ID")
		return
	}

	includeInactive := r.URL.Query().Get("include_inactive") == "true"

	docs, err := h.phase4Service.GetDocumentsByOrganization(gymID, includeInactive)
	if err != nil {
		h.logger.Error("Failed to list documents: %v", err)
		respondError(w, http.StatusInternalServerError, "Failed to list documents")
		return
	}

	respondJSON(w, http.StatusOK, map[string]interface{}{
		"documents": docs,
		"count":     len(docs),
	})
}

// GetDocument handles GET /api/gyms/{gym_id}/documents/{id}
func (h *Phase4Handler) GetDocument(w http.ResponseWriter, r *http.Request) {
	idStr := chi.URLParam(r, "id")
	id, err := strconv.ParseInt(idStr, 10, 64)
	if err != nil {
		respondError(w, http.StatusBadRequest, "Invalid document ID")
		return
	}

	doc, err := h.phase4Service.GetDocumentByID(id)
	if err != nil {
		if err == service.ErrDocumentNotFound {
			respondError(w, http.StatusNotFound, "Document not found")
			return
		}
		h.logger.Error("Failed to get document: %v", err)
		respondError(w, http.StatusInternalServerError, "Failed to get document")
		return
	}

	respondJSON(w, http.StatusOK, doc)
}

// UpdateDocument handles PUT /api/gyms/{gym_id}/documents/{id}
func (h *Phase4Handler) UpdateDocument(w http.ResponseWriter, r *http.Request) {
	userID, ok := middleware.GetUserID(r.Context())
	if !ok {
		respondError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	idStr := chi.URLParam(r, "id")
	id, err := strconv.ParseInt(idStr, 10, 64)
	if err != nil {
		respondError(w, http.StatusBadRequest, "Invalid document ID")
		return
	}

	doc, err := h.phase4Service.GetDocumentByID(id)
	if err != nil {
		if err == service.ErrDocumentNotFound {
			respondError(w, http.StatusNotFound, "Document not found")
			return
		}
		h.logger.Error("Failed to get document: %v", err)
		respondError(w, http.StatusInternalServerError, "Failed to get document")
		return
	}

	var req struct {
		Name             string  `json:"name"`
		Description      *string `json:"description"`
		DocumentType     string  `json:"document_type"`
		URL              *string `json:"url"`
		IsRequired       bool    `json:"is_required"`
		ExpiresAfterDays *int    `json:"expires_after_days"`
		IsActive         bool    `json:"is_active"`
	}

	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		respondError(w, http.StatusBadRequest, "Invalid request body")
		return
	}

	doc.Name = req.Name
	doc.Description = req.Description
	doc.DocumentType = req.DocumentType
	doc.URL = req.URL
	doc.IsRequired = req.IsRequired
	doc.ExpiresAfterDays = req.ExpiresAfterDays
	doc.IsActive = req.IsActive

	if err := h.phase4Service.UpdateDocument(userID, doc); err != nil {
		h.logger.Error("Failed to update document: %v", err)
		respondError(w, http.StatusInternalServerError, "Failed to update document")
		return
	}

	respondJSON(w, http.StatusOK, doc)
}

// DeleteDocument handles DELETE /api/gyms/{gym_id}/documents/{id}
func (h *Phase4Handler) DeleteDocument(w http.ResponseWriter, r *http.Request) {
	userID, ok := middleware.GetUserID(r.Context())
	if !ok {
		respondError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	idStr := chi.URLParam(r, "id")
	id, err := strconv.ParseInt(idStr, 10, 64)
	if err != nil {
		respondError(w, http.StatusBadRequest, "Invalid document ID")
		return
	}

	if err := h.phase4Service.DeleteDocument(userID, id); err != nil {
		if err == service.ErrDocumentNotFound {
			respondError(w, http.StatusNotFound, "Document not found")
			return
		}
		h.logger.Error("Failed to delete document: %v", err)
		respondError(w, http.StatusInternalServerError, "Failed to delete document")
		return
	}

	respondJSON(w, http.StatusOK, map[string]string{"message": "Document deleted"})
}

// ============================================
// USER DOCUMENT HANDLERS
// ============================================

// GetUserDocuments handles GET /api/users/me/documents
func (h *Phase4Handler) GetUserDocuments(w http.ResponseWriter, r *http.Request) {
	userID, ok := middleware.GetUserID(r.Context())
	if !ok {
		respondError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	docs, err := h.phase4Service.GetUserDocuments(userID)
	if err != nil {
		h.logger.Error("Failed to get user documents: %v", err)
		respondError(w, http.StatusInternalServerError, "Failed to get user documents")
		return
	}

	respondJSON(w, http.StatusOK, map[string]interface{}{
		"user_documents": docs,
		"count":          len(docs),
	})
}

// GetUserDocumentsByOrganization handles GET /api/gyms/{gym_id}/users/me/documents
func (h *Phase4Handler) GetUserDocumentsByOrganization(w http.ResponseWriter, r *http.Request) {
	userID, ok := middleware.GetUserID(r.Context())
	if !ok {
		respondError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	gymIDStr := chi.URLParam(r, "gym_id")
	gymID, err := strconv.ParseInt(gymIDStr, 10, 64)
	if err != nil {
		respondError(w, http.StatusBadRequest, "Invalid gym ID")
		return
	}

	docs, err := h.phase4Service.GetUserDocumentsByOrganization(userID, gymID)
	if err != nil {
		h.logger.Error("Failed to get user documents: %v", err)
		respondError(w, http.StatusInternalServerError, "Failed to get user documents")
		return
	}

	respondJSON(w, http.StatusOK, map[string]interface{}{
		"user_documents": docs,
		"count":          len(docs),
	})
}

// GetPendingDocuments handles GET /api/gyms/{gym_id}/users/me/documents/pending
func (h *Phase4Handler) GetPendingDocuments(w http.ResponseWriter, r *http.Request) {
	userID, ok := middleware.GetUserID(r.Context())
	if !ok {
		respondError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	gymIDStr := chi.URLParam(r, "gym_id")
	gymID, err := strconv.ParseInt(gymIDStr, 10, 64)
	if err != nil {
		respondError(w, http.StatusBadRequest, "Invalid gym ID")
		return
	}

	docs, err := h.phase4Service.GetPendingUserDocuments(userID, gymID)
	if err != nil {
		h.logger.Error("Failed to get pending documents: %v", err)
		respondError(w, http.StatusInternalServerError, "Failed to get pending documents")
		return
	}

	respondJSON(w, http.StatusOK, map[string]interface{}{
		"pending_documents": docs,
		"count":             len(docs),
	})
}

// MarkDocumentCompleted handles POST /api/gyms/{gym_id}/user-documents/{id}/complete
func (h *Phase4Handler) MarkDocumentCompleted(w http.ResponseWriter, r *http.Request) {
	adminUserID, ok := middleware.GetUserID(r.Context())
	if !ok {
		respondError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	idStr := chi.URLParam(r, "id")
	id, err := strconv.ParseInt(idStr, 10, 64)
	if err != nil {
		respondError(w, http.StatusBadRequest, "Invalid user document ID")
		return
	}

	if err := h.phase4Service.MarkDocumentCompleted(adminUserID, id); err != nil {
		if err == service.ErrUserDocumentNotFound {
			respondError(w, http.StatusNotFound, "User document not found")
			return
		}
		h.logger.Error("Failed to mark document completed: %v", err)
		respondError(w, http.StatusInternalServerError, "Failed to mark document completed")
		return
	}

	respondJSON(w, http.StatusOK, map[string]string{"message": "Document marked as completed"})
}

// InitializeUserDocuments handles POST /api/gyms/{gym_id}/users/{user_id}/documents/init
func (h *Phase4Handler) InitializeUserDocuments(w http.ResponseWriter, r *http.Request) {
	_, ok := middleware.GetUserID(r.Context())
	if !ok {
		respondError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	gymIDStr := chi.URLParam(r, "gym_id")
	gymID, err := strconv.ParseInt(gymIDStr, 10, 64)
	if err != nil {
		respondError(w, http.StatusBadRequest, "Invalid gym ID")
		return
	}

	targetUserIDStr := chi.URLParam(r, "user_id")
	targetUserID, err := strconv.ParseInt(targetUserIDStr, 10, 64)
	if err != nil {
		respondError(w, http.StatusBadRequest, "Invalid user ID")
		return
	}

	if err := h.phase4Service.InitializeUserDocuments(targetUserID, gymID); err != nil {
		h.logger.Error("Failed to initialize user documents: %v", err)
		respondError(w, http.StatusInternalServerError, "Failed to initialize user documents")
		return
	}

	respondJSON(w, http.StatusOK, map[string]string{"message": "User documents initialized"})
}

// ============================================
// CLASS PACKAGE HANDLERS (Admin)
// ============================================

// CreateClassPackage handles POST /api/gyms/{gym_id}/packages
func (h *Phase4Handler) CreateClassPackage(w http.ResponseWriter, r *http.Request) {
	userID, ok := middleware.GetUserID(r.Context())
	if !ok {
		respondError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	gymIDStr := chi.URLParam(r, "gym_id")
	gymID, err := strconv.ParseInt(gymIDStr, 10, 64)
	if err != nil {
		respondError(w, http.StatusBadRequest, "Invalid gym ID")
		return
	}

	var req struct {
		Name         string  `json:"name"`
		Description  *string `json:"description"`
		Credits      int     `json:"credits"`
		PriceCents   int     `json:"price_cents"`
		ValidityDays *int    `json:"validity_days"`
	}

	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		respondError(w, http.StatusBadRequest, "Invalid request body")
		return
	}

	if req.Name == "" {
		respondError(w, http.StatusBadRequest, "Package name is required")
		return
	}

	if req.Credits <= 0 {
		respondError(w, http.StatusBadRequest, "Credits must be positive")
		return
	}

	pkg := &domain.ClassPackage{
		OrganizationID: gymID,
		Name:           req.Name,
		Description:    req.Description,
		Credits:        req.Credits,
		PriceCents:     req.PriceCents,
		ValidityDays:   req.ValidityDays,
		IsActive:       true,
	}

	if err := h.phase4Service.CreatePackage(userID, pkg); err != nil {
		h.logger.Error("Failed to create class package: %v", err)
		respondError(w, http.StatusInternalServerError, "Failed to create class package")
		return
	}

	respondJSON(w, http.StatusCreated, pkg)
}

// ListClassPackages handles GET /api/gyms/{gym_id}/packages
func (h *Phase4Handler) ListClassPackages(w http.ResponseWriter, r *http.Request) {
	gymIDStr := chi.URLParam(r, "gym_id")
	gymID, err := strconv.ParseInt(gymIDStr, 10, 64)
	if err != nil {
		respondError(w, http.StatusBadRequest, "Invalid gym ID")
		return
	}

	includeInactive := r.URL.Query().Get("include_inactive") == "true"

	packages, err := h.phase4Service.GetPackagesByOrganization(gymID, includeInactive)
	if err != nil {
		h.logger.Error("Failed to list class packages: %v", err)
		respondError(w, http.StatusInternalServerError, "Failed to list class packages")
		return
	}

	respondJSON(w, http.StatusOK, map[string]interface{}{
		"packages": packages,
		"count":    len(packages),
	})
}

// GetClassPackage handles GET /api/gyms/{gym_id}/packages/{id}
func (h *Phase4Handler) GetClassPackage(w http.ResponseWriter, r *http.Request) {
	idStr := chi.URLParam(r, "id")
	id, err := strconv.ParseInt(idStr, 10, 64)
	if err != nil {
		respondError(w, http.StatusBadRequest, "Invalid package ID")
		return
	}

	pkg, err := h.phase4Service.GetPackageByID(id)
	if err != nil {
		if err == service.ErrClassPackageNotFound {
			respondError(w, http.StatusNotFound, "Class package not found")
			return
		}
		h.logger.Error("Failed to get class package: %v", err)
		respondError(w, http.StatusInternalServerError, "Failed to get class package")
		return
	}

	respondJSON(w, http.StatusOK, pkg)
}

// UpdateClassPackage handles PUT /api/gyms/{gym_id}/packages/{id}
func (h *Phase4Handler) UpdateClassPackage(w http.ResponseWriter, r *http.Request) {
	userID, ok := middleware.GetUserID(r.Context())
	if !ok {
		respondError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	idStr := chi.URLParam(r, "id")
	id, err := strconv.ParseInt(idStr, 10, 64)
	if err != nil {
		respondError(w, http.StatusBadRequest, "Invalid package ID")
		return
	}

	pkg, err := h.phase4Service.GetPackageByID(id)
	if err != nil {
		if err == service.ErrClassPackageNotFound {
			respondError(w, http.StatusNotFound, "Class package not found")
			return
		}
		h.logger.Error("Failed to get class package: %v", err)
		respondError(w, http.StatusInternalServerError, "Failed to get class package")
		return
	}

	var req struct {
		Name         string  `json:"name"`
		Description  *string `json:"description"`
		Credits      int     `json:"credits"`
		PriceCents   int     `json:"price_cents"`
		ValidityDays *int    `json:"validity_days"`
		IsActive     bool    `json:"is_active"`
	}

	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		respondError(w, http.StatusBadRequest, "Invalid request body")
		return
	}

	pkg.Name = req.Name
	pkg.Description = req.Description
	pkg.Credits = req.Credits
	pkg.PriceCents = req.PriceCents
	pkg.ValidityDays = req.ValidityDays
	pkg.IsActive = req.IsActive

	if err := h.phase4Service.UpdatePackage(userID, pkg); err != nil {
		h.logger.Error("Failed to update class package: %v", err)
		respondError(w, http.StatusInternalServerError, "Failed to update class package")
		return
	}

	respondJSON(w, http.StatusOK, pkg)
}

// DeleteClassPackage handles DELETE /api/gyms/{gym_id}/packages/{id}
func (h *Phase4Handler) DeleteClassPackage(w http.ResponseWriter, r *http.Request) {
	userID, ok := middleware.GetUserID(r.Context())
	if !ok {
		respondError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	idStr := chi.URLParam(r, "id")
	id, err := strconv.ParseInt(idStr, 10, 64)
	if err != nil {
		respondError(w, http.StatusBadRequest, "Invalid package ID")
		return
	}

	if err := h.phase4Service.DeletePackage(userID, id); err != nil {
		if err == service.ErrClassPackageNotFound {
			respondError(w, http.StatusNotFound, "Class package not found")
			return
		}
		h.logger.Error("Failed to delete class package: %v", err)
		respondError(w, http.StatusInternalServerError, "Failed to delete class package")
		return
	}

	respondJSON(w, http.StatusOK, map[string]string{"message": "Class package deleted"})
}

// ============================================
// CREDITS HANDLERS
// ============================================

// PurchaseCredits handles POST /api/gyms/{gym_id}/users/{user_id}/credits
func (h *Phase4Handler) PurchaseCredits(w http.ResponseWriter, r *http.Request) {
	adminUserID, ok := middleware.GetUserID(r.Context())
	if !ok {
		respondError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	gymIDStr := chi.URLParam(r, "gym_id")
	gymID, err := strconv.ParseInt(gymIDStr, 10, 64)
	if err != nil {
		respondError(w, http.StatusBadRequest, "Invalid gym ID")
		return
	}

	targetUserIDStr := chi.URLParam(r, "user_id")
	targetUserID, err := strconv.ParseInt(targetUserIDStr, 10, 64)
	if err != nil {
		respondError(w, http.StatusBadRequest, "Invalid user ID")
		return
	}

	var req struct {
		PackageID *int64 `json:"package_id"`
		Credits   int    `json:"credits"`
		Notes     string `json:"notes"`
	}

	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		respondError(w, http.StatusBadRequest, "Invalid request body")
		return
	}

	// Get package credits for response if using a package
	creditsAdded := req.Credits
	if req.PackageID != nil && req.Credits == 0 {
		pkg, err := h.phase4Service.GetPackageByID(*req.PackageID)
		if err == nil && pkg != nil {
			creditsAdded = pkg.Credits
		}
	}

	if err := h.phase4Service.PurchaseCredits(adminUserID, targetUserID, gymID, req.PackageID, req.Credits, req.Notes); err != nil {
		if err == service.ErrClassPackageNotFound {
			respondError(w, http.StatusNotFound, "Class package not found")
			return
		}
		h.logger.Error("Failed to purchase credits: %v", err)
		respondError(w, http.StatusInternalServerError, "Failed to purchase credits")
		return
	}

	respondJSON(w, http.StatusCreated, map[string]interface{}{
		"message": "Credits added successfully",
		"credits": creditsAdded,
	})
}

// GetUserCredits handles GET /api/gyms/{gym_id}/users/me/credits
func (h *Phase4Handler) GetUserCredits(w http.ResponseWriter, r *http.Request) {
	userID, ok := middleware.GetUserID(r.Context())
	if !ok {
		respondError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	gymIDStr := chi.URLParam(r, "gym_id")
	gymID, err := strconv.ParseInt(gymIDStr, 10, 64)
	if err != nil {
		respondError(w, http.StatusBadRequest, "Invalid gym ID")
		return
	}

	credits, err := h.phase4Service.GetUserCreditsByOrganization(userID, gymID)
	if err != nil {
		h.logger.Error("Failed to get user credits: %v", err)
		respondError(w, http.StatusInternalServerError, "Failed to get user credits")
		return
	}

	available, err := h.phase4Service.GetAvailableCredits(userID, gymID)
	if err != nil {
		h.logger.Error("Failed to get available credits: %v", err)
		respondError(w, http.StatusInternalServerError, "Failed to get available credits")
		return
	}

	respondJSON(w, http.StatusOK, map[string]interface{}{
		"credit_records":    credits,
		"count":             len(credits),
		"available_credits": available,
	})
}

// GetAvailableCredits handles GET /api/gyms/{gym_id}/users/me/credits/available
func (h *Phase4Handler) GetAvailableCredits(w http.ResponseWriter, r *http.Request) {
	userID, ok := middleware.GetUserID(r.Context())
	if !ok {
		respondError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	gymIDStr := chi.URLParam(r, "gym_id")
	gymID, err := strconv.ParseInt(gymIDStr, 10, 64)
	if err != nil {
		respondError(w, http.StatusBadRequest, "Invalid gym ID")
		return
	}

	available, err := h.phase4Service.GetAvailableCredits(userID, gymID)
	if err != nil {
		h.logger.Error("Failed to get available credits: %v", err)
		respondError(w, http.StatusInternalServerError, "Failed to get available credits")
		return
	}

	respondJSON(w, http.StatusOK, map[string]interface{}{
		"available_credits": available,
	})
}

// ============================================
// WAITLIST HANDLERS
// ============================================

// JoinWaitlist handles POST /api/sessions/{session_id}/waitlist
func (h *Phase4Handler) JoinWaitlist(w http.ResponseWriter, r *http.Request) {
	userID, ok := middleware.GetUserID(r.Context())
	if !ok {
		respondError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	sessionIDStr := chi.URLParam(r, "session_id")
	sessionID, err := strconv.ParseInt(sessionIDStr, 10, 64)
	if err != nil {
		respondError(w, http.StatusBadRequest, "Invalid session ID")
		return
	}

	entry, err := h.phase4Service.JoinWaitlist(userID, sessionID)
	if err != nil {
		if err == service.ErrAlreadyOnWaitlist {
			respondError(w, http.StatusConflict, "Already on waitlist")
			return
		}
		h.logger.Error("Failed to join waitlist: %v", err)
		respondError(w, http.StatusInternalServerError, "Failed to join waitlist")
		return
	}

	respondJSON(w, http.StatusCreated, entry)
}

// LeaveWaitlist handles DELETE /api/sessions/{session_id}/waitlist
func (h *Phase4Handler) LeaveWaitlist(w http.ResponseWriter, r *http.Request) {
	userID, ok := middleware.GetUserID(r.Context())
	if !ok {
		respondError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	sessionIDStr := chi.URLParam(r, "session_id")
	sessionID, err := strconv.ParseInt(sessionIDStr, 10, 64)
	if err != nil {
		respondError(w, http.StatusBadRequest, "Invalid session ID")
		return
	}

	if err := h.phase4Service.CancelWaitlistBySession(userID, sessionID); err != nil {
		if err == service.ErrWaitlistEntryNotFound {
			respondError(w, http.StatusNotFound, "Not on waitlist")
			return
		}
		h.logger.Error("Failed to leave waitlist: %v", err)
		respondError(w, http.StatusInternalServerError, "Failed to leave waitlist")
		return
	}

	respondJSON(w, http.StatusOK, map[string]string{"message": "Removed from waitlist"})
}

// GetSessionWaitlist handles GET /api/sessions/{session_id}/waitlist
func (h *Phase4Handler) GetSessionWaitlist(w http.ResponseWriter, r *http.Request) {
	sessionIDStr := chi.URLParam(r, "session_id")
	sessionID, err := strconv.ParseInt(sessionIDStr, 10, 64)
	if err != nil {
		respondError(w, http.StatusBadRequest, "Invalid session ID")
		return
	}

	entries, err := h.phase4Service.GetSessionWaitlist(sessionID)
	if err != nil {
		h.logger.Error("Failed to get session waitlist: %v", err)
		respondError(w, http.StatusInternalServerError, "Failed to get session waitlist")
		return
	}

	respondJSON(w, http.StatusOK, map[string]interface{}{
		"waitlist": entries,
		"count":    len(entries),
	})
}

// GetUserWaitlistPosition handles GET /api/sessions/{session_id}/waitlist/position
func (h *Phase4Handler) GetUserWaitlistPosition(w http.ResponseWriter, r *http.Request) {
	userID, ok := middleware.GetUserID(r.Context())
	if !ok {
		respondError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	sessionIDStr := chi.URLParam(r, "session_id")
	sessionID, err := strconv.ParseInt(sessionIDStr, 10, 64)
	if err != nil {
		respondError(w, http.StatusBadRequest, "Invalid session ID")
		return
	}

	position, err := h.phase4Service.GetWaitlistPosition(sessionID, userID)
	if err != nil {
		if err == service.ErrWaitlistEntryNotFound {
			respondError(w, http.StatusNotFound, "Not on waitlist")
			return
		}
		h.logger.Error("Failed to get waitlist position: %v", err)
		respondError(w, http.StatusInternalServerError, "Failed to get waitlist position")
		return
	}

	respondJSON(w, http.StatusOK, map[string]interface{}{
		"position": position,
	})
}

// GetUserWaitlistEntries handles GET /api/users/me/waitlist
func (h *Phase4Handler) GetUserWaitlistEntries(w http.ResponseWriter, r *http.Request) {
	userID, ok := middleware.GetUserID(r.Context())
	if !ok {
		respondError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	entries, err := h.phase4Service.GetUserWaitlistEntries(userID)
	if err != nil {
		h.logger.Error("Failed to get user waitlist entries: %v", err)
		respondError(w, http.StatusInternalServerError, "Failed to get user waitlist entries")
		return
	}

	respondJSON(w, http.StatusOK, map[string]interface{}{
		"waitlist_entries": entries,
		"count":            len(entries),
	})
}

// ============================================
// NOTIFICATION HANDLERS
// ============================================

// GetUserNotifications handles GET /api/users/me/notifications
func (h *Phase4Handler) GetUserNotifications(w http.ResponseWriter, r *http.Request) {
	userID, ok := middleware.GetUserID(r.Context())
	if !ok {
		respondError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	limitStr := r.URL.Query().Get("limit")
	limit := 50
	if limitStr != "" {
		if l, err := strconv.Atoi(limitStr); err == nil && l > 0 && l <= 100 {
			limit = l
		}
	}

	notifications, err := h.phase4Service.GetUserNotifications(userID, limit)
	if err != nil {
		h.logger.Error("Failed to get user notifications: %v", err)
		respondError(w, http.StatusInternalServerError, "Failed to get user notifications")
		return
	}

	respondJSON(w, http.StatusOK, map[string]interface{}{
		"notifications": notifications,
		"count":         len(notifications),
	})
}