from datetime import date from typing import Any from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel, Field from sqlalchemy.orm import Session from app.db.base import get_db from app.fitness.service import FitnessService from app.fitness.structuring import structure_meal, structure_workout from app.integrations.openfoodfacts import OpenFoodFactsClient from app.integrations.wger import WgerClient router = APIRouter() class ProfileUpdate(BaseModel): sex: str | None = None age: int | None = None height_cm: float | None = None weight_kg: float | None = None activity_level: str | None = None goal: str | None = None target_weight_kg: float | None = None weekly_workouts: int | None = None class MealCreate(BaseModel): text: str = Field(min_length=1) meal_type: str | None = None class WaterCreate(BaseModel): amount_ml: int = Field(gt=0) class WeightCreate(BaseModel): weight_kg: float = Field(gt=0) body_fat_pct: float | None = None chest_cm: float | None = None waist_cm: float | None = None notes: str = "" class WorkoutCreate(BaseModel): text: str = Field(min_length=1) class ReminderUpdate(BaseModel): enabled: bool | None = None hour: int | None = Field(default=None, ge=0, le=23) minute: int | None = Field(default=None, ge=0, le=59) interval_hours: int | None = Field(default=None, ge=1, le=12) @router.get("/fitness") def get_snapshot(db: Session = Depends(get_db)) -> dict[str, Any]: return FitnessService(db).snapshot() @router.get("/fitness/summary") def get_summary( day: str | None = None, db: Session = Depends(get_db), ) -> dict[str, Any]: d = date.fromisoformat(day) if day else None return FitnessService(db).get_daily_summary(d) @router.get("/fitness/history") def get_history( days: int = 7, end: str | None = None, db: Session = Depends(get_db), ) -> dict[str, Any]: end_day = date.fromisoformat(end) if end else None return FitnessService(db).get_history(days=days, end_day=end_day) @router.get("/fitness/profile") def get_profile(db: Session = Depends(get_db)) -> dict[str, Any]: profile = FitnessService(db).get_profile() return profile or {"configured": False} @router.put("/fitness/profile") def update_profile( payload: ProfileUpdate, db: Session = Depends(get_db), ) -> dict[str, Any]: return FitnessService(db).set_profile(payload.model_dump(exclude_none=True)) @router.post("/fitness/profile/calc") def calc_targets( payload: ProfileUpdate, db: Session = Depends(get_db), ) -> dict[str, Any]: params = payload.model_dump(exclude_none=True) if not params: raise HTTPException(status_code=400, detail="No parameters") return FitnessService(db).calc_targets(params) @router.post("/fitness/meals") async def create_meal( payload: MealCreate, db: Session = Depends(get_db), ) -> dict[str, Any]: service = FitnessService(db) try: structured = await structure_meal(payload.text) except Exception as exc: raise HTTPException(status_code=502, detail=str(exc)) from exc return service.log_meal( description=structured.get("description") or payload.text, meal_type=payload.meal_type or structured.get("meal_type") or "snack", calories=float(structured.get("calories") or 0), protein_g=float(structured.get("protein_g") or 0), fat_g=float(structured.get("fat_g") or 0), carbs_g=float(structured.get("carbs_g") or 0), source="llm", estimated=bool(structured.get("estimated", True)), ) @router.post("/fitness/water") def create_water( payload: WaterCreate, db: Session = Depends(get_db), ) -> dict[str, Any]: return FitnessService(db).log_water(payload.amount_ml) @router.post("/fitness/weight") def create_weight( payload: WeightCreate, db: Session = Depends(get_db), ) -> dict[str, Any]: return FitnessService(db).log_weight( payload.weight_kg, body_fat_pct=payload.body_fat_pct, chest_cm=payload.chest_cm, waist_cm=payload.waist_cm, notes=payload.notes, ) @router.post("/fitness/workouts") async def create_workout( payload: WorkoutCreate, db: Session = Depends(get_db), ) -> dict[str, Any]: service = FitnessService(db) try: structured = await structure_workout(payload.text) except Exception as exc: raise HTTPException(status_code=502, detail=str(exc)) from exc return service.log_workout( title=structured.get("title") or "Тренировка", notes=structured.get("notes") or payload.text, duration_min=structured.get("duration_min"), exercises=structured.get("exercises"), ) @router.get("/fitness/body-metrics") def list_metrics( limit: int = 30, db: Session = Depends(get_db), ) -> list[dict[str, Any]]: return FitnessService(db).list_body_metrics(limit=limit) @router.delete("/fitness/meals/{log_id}") def delete_meal(log_id: int, db: Session = Depends(get_db)) -> dict[str, bool]: if not FitnessService(db).delete_food_log(log_id): raise HTTPException(status_code=404, detail="Not found") return {"ok": True} @router.delete("/fitness/water/{log_id}") def delete_water(log_id: int, db: Session = Depends(get_db)) -> dict[str, bool]: if not FitnessService(db).delete_water_log(log_id): raise HTTPException(status_code=404, detail="Not found") return {"ok": True} @router.delete("/fitness/workouts/{log_id}") def delete_workout(log_id: int, db: Session = Depends(get_db)) -> dict[str, bool]: if not FitnessService(db).delete_workout_log(log_id): raise HTTPException(status_code=404, detail="Not found") return {"ok": True} @router.get("/fitness/reminders") def list_reminders(db: Session = Depends(get_db)) -> list[dict[str, Any]]: return FitnessService(db).list_reminders() @router.put("/fitness/reminders/{kind}") def update_reminder( kind: str, payload: ReminderUpdate, db: Session = Depends(get_db), ) -> dict[str, Any]: return FitnessService(db).set_reminder( kind, enabled=payload.enabled, hour=payload.hour, minute=payload.minute, interval_hours=payload.interval_hours, ) @router.get("/fitness/lookup/food") def lookup_food(q: str, limit: int = 5) -> list[dict[str, Any]]: return OpenFoodFactsClient().search(q, limit=limit) @router.get("/fitness/lookup/exercise") def lookup_exercise(q: str, limit: int = 8) -> list[dict[str, Any]]: return WgerClient().search_exercises(q, limit=limit)