package ai import ( "encoding/json" "log" "net/http" "rideaware/internal/config" "rideaware/internal/middleware" ) type Handler struct { service *Service } func NewHandler() *Handler { return &Handler{ service: NewService(), } } // GenerateRecommendations POST /api/protected/ai/generate func (h *Handler) GenerateRecommendations(w http.ResponseWriter, r *http.Request) { claims := r.Context().Value(middleware.UserContextKey).(*config.CustomClaims) if claims == nil { respondError(w, http.StatusUnauthorized, "unauthorized") return } var req GenerateRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { respondError(w, http.StatusBadRequest, "invalid request body") return } // Validate request if err := validateGenerateRequest(req); err != nil { respondError(w, http.StatusBadRequest, err.Error()) return } log.Printf("[AI] User %d requesting %d-day plan (focus: %v, intensity: %s)", claims.UserID, req.PlanDuration, req.FocusAreas, req.IntensityLevel) // Generate workouts response, err := h.service.GenerateWorkouts(claims.UserID, req) if err != nil { log.Printf("[AI] Generation error for user %d: %v", claims.UserID, err) respondError(w, http.StatusInternalServerError, "failed to generate workouts: "+err.Error()) return } log.Printf("[AI] Successfully generated %d workouts for user %d (total TSS: %.1f)", len(response.Workouts), claims.UserID, response.TotalTSS) respondJSON(w, http.StatusOK, response) } // ScheduleRecommendations POST /api/protected/ai/schedule func (h *Handler) ScheduleRecommendations(w http.ResponseWriter, r *http.Request) { claims := r.Context().Value(middleware.UserContextKey).(*config.CustomClaims) if claims == nil { respondError(w, http.StatusUnauthorized, "unauthorized") return } var req struct { RecommendationID uint `json:"recommendation_id"` WorkoutIndices []int `json:"workout_indices"` // Which workouts to schedule } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { respondError(w, http.StatusBadRequest, "invalid request") return } if req.RecommendationID == 0 { respondError(w, http.StatusBadRequest, "recommendation_id is required") return } log.Printf("[AI] User %d scheduling %d workouts from recommendation %d", claims.UserID, len(req.WorkoutIndices), req.RecommendationID) // Schedule selected workouts workouts, err := h.service.ScheduleWorkouts(claims.UserID, req.RecommendationID, req.WorkoutIndices) if err != nil { log.Printf("[AI] Schedule error for user %d: %v", claims.UserID, err) respondError(w, http.StatusInternalServerError, err.Error()) return } log.Printf("[AI] Successfully scheduled %d workouts for user %d", len(workouts), claims.UserID) respondJSON(w, http.StatusCreated, map[string]interface{}{ "scheduled_count": len(workouts), "workouts": workouts, }) } // GetRecommendationHistory GET /api/protected/ai/history func (h *Handler) GetRecommendationHistory(w http.ResponseWriter, r *http.Request) { claims := r.Context().Value(middleware.UserContextKey).(*config.CustomClaims) if claims == nil { respondError(w, http.StatusUnauthorized, "unauthorized") return } history, err := h.service.GetUserRecommendations(claims.UserID, 10) if err != nil { log.Printf("[AI] Failed to fetch history for user %d: %v", claims.UserID, err) respondError(w, http.StatusInternalServerError, "failed to fetch history") return } // Return empty array instead of null if history == nil { history = []AIRecommendation{} } respondJSON(w, http.StatusOK, history) } // Helper functions func respondJSON(w http.ResponseWriter, status int, data interface{}) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) json.NewEncoder(w).Encode(data) } func respondError(w http.ResponseWriter, status int, message string) { respondJSON(w, status, map[string]string{"error": message}) }