from datetime import datetime, timedelta from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, and_, or_ from typing import Optional, List from app.database import get_db from app.models.reminder import Reminder from app.schemas.reminder import ReminderCreate, ReminderUpdate, ReminderResponse, ReminderSnooze from app.routers.auth import get_current_user from app.models.user import User router = APIRouter() @router.get("/", response_model=List[ReminderResponse]) async def get_reminders( active: Optional[bool] = Query(None), dismissed: Optional[bool] = Query(None), db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user) ): """Get all reminders with optional filters.""" query = select(Reminder) if active is not None: query = query.where(Reminder.is_active == active) if dismissed is not None: query = query.where(Reminder.is_dismissed == dismissed) query = query.order_by(Reminder.remind_at.asc()) result = await db.execute(query) reminders = result.scalars().all() return reminders @router.get("/due", response_model=List[ReminderResponse]) async def get_due_reminders( client_now: Optional[datetime] = Query(None), db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user) ): """Get reminders that are currently due for alerting.""" now = client_now or datetime.now() query = select(Reminder).where( and_( Reminder.remind_at <= now, Reminder.is_dismissed == False, Reminder.is_active == True, or_( Reminder.recurrence_rule.is_(None), Reminder.recurrence_rule == '', ), or_( Reminder.snoozed_until.is_(None), Reminder.snoozed_until <= now, ), ) ).order_by(Reminder.remind_at.asc()) result = await db.execute(query) return result.scalars().all() @router.patch("/{reminder_id}/snooze", response_model=ReminderResponse) async def snooze_reminder( reminder_id: int, body: ReminderSnooze, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user) ): """Snooze a reminder for N minutes from now.""" result = await db.execute(select(Reminder).where(Reminder.id == reminder_id)) reminder = result.scalar_one_or_none() if not reminder: raise HTTPException(status_code=404, detail="Reminder not found") if reminder.is_dismissed or not reminder.is_active: raise HTTPException(status_code=409, detail="Cannot snooze a dismissed or inactive reminder") base_time = body.client_now or datetime.now() reminder.snoozed_until = base_time + timedelta(minutes=body.minutes) await db.commit() await db.refresh(reminder) return reminder @router.post("/", response_model=ReminderResponse, status_code=201) async def create_reminder( reminder: ReminderCreate, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user) ): """Create a new reminder.""" new_reminder = Reminder(**reminder.model_dump()) db.add(new_reminder) await db.commit() await db.refresh(new_reminder) return new_reminder @router.get("/{reminder_id}", response_model=ReminderResponse) async def get_reminder( reminder_id: int, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user) ): """Get a specific reminder by ID.""" result = await db.execute(select(Reminder).where(Reminder.id == reminder_id)) reminder = result.scalar_one_or_none() if not reminder: raise HTTPException(status_code=404, detail="Reminder not found") return reminder @router.put("/{reminder_id}", response_model=ReminderResponse) async def update_reminder( reminder_id: int, reminder_update: ReminderUpdate, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user) ): """Update a reminder.""" result = await db.execute(select(Reminder).where(Reminder.id == reminder_id)) reminder = result.scalar_one_or_none() if not reminder: raise HTTPException(status_code=404, detail="Reminder not found") update_data = reminder_update.model_dump(exclude_unset=True) # Reactivate reminder if remind_at is being changed if 'remind_at' in update_data and update_data['remind_at'] is not None: reminder.snoozed_until = None reminder.is_dismissed = False # Clear snoozed_until when dismissing via update (match dedicated endpoint) if update_data.get('is_dismissed') is True: reminder.snoozed_until = None for key, value in update_data.items(): setattr(reminder, key, value) await db.commit() await db.refresh(reminder) return reminder @router.delete("/{reminder_id}", status_code=204) async def delete_reminder( reminder_id: int, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user) ): """Delete a reminder.""" result = await db.execute(select(Reminder).where(Reminder.id == reminder_id)) reminder = result.scalar_one_or_none() if not reminder: raise HTTPException(status_code=404, detail="Reminder not found") await db.delete(reminder) await db.commit() return None @router.patch("/{reminder_id}/dismiss", response_model=ReminderResponse) async def dismiss_reminder( reminder_id: int, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user) ): """Dismiss a reminder.""" result = await db.execute(select(Reminder).where(Reminder.id == reminder_id)) reminder = result.scalar_one_or_none() if not reminder: raise HTTPException(status_code=404, detail="Reminder not found") reminder.is_dismissed = True reminder.snoozed_until = None await db.commit() await db.refresh(reminder) return reminder