from __future__ import annotations import json from datetime import datetime, time, timedelta from typing import Any, Optional from pydantic import ValidationError from sqlalchemy.ext.asyncio import AsyncSession from sqlmodel import select from src.utils.sleep import calculate_sleep_duration from ..database.models import ActivityEntry, DayLog, Goal, User from ..schemas.activity import activity_create from ..schemas.daylog import daylog_create from ..schemas.goal import goal_create from .exceptions import CommandError from .command_validation import parse_command_text, build_typed_payload, validate_required_fields async def create_goal_service(payload: goal_create, current_user: User, db: AsyncSession) -> dict[str, Any]: statement = select(Goal).where((Goal.user_id == current_user.id) & (Goal.title == payload.title)) result = await db.execute(statement) existing_goal = result.scalar_one_or_none() if existing_goal: raise CommandError(400, "business_rule_violation", "A goal with the same name already exists.") new_goal = Goal( user_id=current_user.id, title=payload.title, description=payload.description, total_productivity_hours=payload.total_productivity_hours, active_status=payload.active_status, date=payload.date ) db.add(new_goal) await db.commit() await db.refresh(new_goal) return {"message": "Goal created successfully", "goal": new_goal.model_dump()} async def create_daylog_service(payload: daylog_create, current_user: User, db: AsyncSession) -> dict[str, Any]: statement = select(DayLog).where((DayLog.user_id == current_user.id) & (DayLog.date == payload.date)) result = await db.execute(statement) existing_log = result.scalar_one_or_none() if existing_log: raise CommandError(400, "business_rule_violation", "A day log for this date already exists.") if payload.wake_time: # 1. Fetch Yesterday's Log yesterday_date = payload.date - timedelta(days=1) stmt = select(DayLog).where( DayLog.date == yesterday_date, DayLog.user_id == current_user.id ) result = await db.execute(stmt) yesterday_log = result.scalar_one_or_none() if yesterday_log.sleep_time: payload.sleep_duration_hours = calculate_sleep_duration( yesterday_date=yesterday_log.date, yesterday_sleep=yesterday_log.sleep_time, today_date=payload.date, today_wake=payload.wake_time ) # 3. Proceed with standard creation... db_daylog = DayLog(**payload.model_dump(), user_id=current_user.id) db.add(db_daylog) await db.commit() await db.refresh(db_daylog) return {"message": "Day log created successfully", "daylog_id": db_daylog.id, "daylog_data": db_daylog} def build_help_text(intent: Optional[str] = None) -> dict[str, Any]: guides = { None: ( "Use /goal, /daylog, /activity, or /help. Values with spaces must be quoted. " "Examples : " "/goal --name --description --status or -n -desc -s" "/daylog --date --bed --wake --sleep --sleephr --totalcal --totalprod --note or -d -b -w -s -sh -tc -tp -n" "/activity --goal --date --name --type --status --time --min --place --quan --what --result --note or -g -d -n -t -s -ti -m -p -q -w -r -no -de" ), "goal": ( "/goal --name --description --status" "OR -n -desc -s" ), "daylog": ( "/daylog --date YYYY-MM-DD --bed HH:MM:SS --wake HH:MM:SS --note --sleep --sleephr --totalcal --totalprod" "OR -d, -b, -w, -s, -sh, -tc, -tp, -n" ), "activity": ( "/activity --goal --date YYYY-MM-DD --name --type --status --time HH:MM:SS --min --place --quan --what --result --note details" "OR -g, -d, -n, -t, -s, -ti, -m, -p, -q, -w, -r, -no, -de" ), } if intent not in guides: raise CommandError(400, "unknown_intent", f"Unknown help topic '{intent}'.") return {"message": "Help guide", "guide": guides[intent]} async def dispatch_command(command_text: str, current_user: User, db: AsyncSession) -> dict[str, Any]: intent, help_topic, raw_values = parse_command_text(command_text) if intent == "help": guide = build_help_text(help_topic) return {"success": True, "intent": "help", "data": guide} typed_values = build_typed_payload(intent, raw_values) validate_required_fields(intent, typed_values) try: if intent == "goal": payload = goal_create.model_validate(typed_values) result = await create_goal_service(payload, current_user, db) return {"success": True, "intent": "goal", "data": result} if intent == "daylog": payload = daylog_create.model_validate(typed_values) result = await create_daylog_service(payload, current_user, db) return {"success": True, "intent": "daylog", "data": result} if intent == "activity": payload = activity_create.model_validate(typed_values) result = await create_activity_service(payload, current_user, db) return {"success": True, "intent": "activity", "data": result} raise CommandError(400, "unknown_intent", f"Unknown intent '{intent}'.") except ValidationError as exc: raise CommandError(400, "invalid_field_type", "Command data does not match the expected schema.", exc.errors()) from exc def command_error_payload(exc: CommandError) -> dict[str, Any]: return { "success": False, "error_code": exc.error_code, "message": exc.message, "details": exc.details, } async def list_user_activities(current_user: User, db: AsyncSession) -> dict[str, Any]: statement = select(ActivityEntry).where(ActivityEntry.user_id == current_user.id) result = await db.execute(statement) activities = result.scalars().all() return { "success": True, "intent": "activity", "data": {"activities": [activity.model_dump() for activity in activities]}, }