# app/api/v1/endpoints/costs.py import logging from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import Session, selectinload from app.database import get_db from app.api.dependencies import get_current_user from app.models import User as UserModel, Group as GroupModel # For get_current_user dependency and Group model from app.schemas.cost import ListCostSummary, GroupBalanceSummary from app.crud import cost as crud_cost from app.crud import list as crud_list # For permission checking from app.core.exceptions import ListNotFoundError, ListPermissionError, UserNotFoundError, GroupNotFoundError logger = logging.getLogger(__name__) router = APIRouter() @router.get( "/lists/{list_id}/cost-summary", response_model=ListCostSummary, summary="Get Cost Summary for a List", tags=["Costs"], responses={ status.HTTP_403_FORBIDDEN: {"description": "User does not have permission to access this list"}, status.HTTP_404_NOT_FOUND: {"description": "List or associated user not found"} } ) async def get_list_cost_summary( list_id: int, db: AsyncSession = Depends(get_db), current_user: UserModel = Depends(get_current_user), ): """ Retrieves a calculated cost summary for a specific list, detailing total costs, equal shares per user, and individual user balances based on their contributions. The user must have access to the list to view its cost summary. Costs are split among group members if the list belongs to a group, or just for the creator if it's a personal list. All users who added items with prices are included in the calculation. """ logger.info(f"User {current_user.email} requesting cost summary for list {list_id}") # 1. Verify user has access to the target list try: await crud_list.check_list_permission(db=db, list_id=list_id, user_id=current_user.id) except ListPermissionError as e: logger.warning(f"Permission denied for user {current_user.email} on list {list_id}: {str(e)}") raise # Re-raise the original exception to be handled by FastAPI except ListNotFoundError as e: logger.warning(f"List {list_id} not found when checking permissions for cost summary: {str(e)}") raise # Re-raise # 2. Calculate the cost summary try: cost_summary = await crud_cost.calculate_list_cost_summary(db=db, list_id=list_id) logger.info(f"Successfully generated cost summary for list {list_id} for user {current_user.email}") return cost_summary except ListNotFoundError as e: logger.warning(f"List {list_id} not found during cost summary calculation: {str(e)}") # This might be redundant if check_list_permission already confirmed list existence, # but calculate_list_cost_summary also fetches the list. raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) except UserNotFoundError as e: logger.error(f"User not found during cost summary calculation for list {list_id}: {str(e)}") # This indicates a data integrity issue (e.g., list creator or item adder missing) raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) except Exception as e: logger.error(f"Unexpected error generating cost summary for list {list_id} for user {current_user.email}: {str(e)}", exc_info=True) raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="An unexpected error occurred while generating the cost summary.") @router.get( "/groups/{group_id}/balance-summary", response_model=GroupBalanceSummary, summary="Get Detailed Balance Summary for a Group", tags=["Costs", "Groups"], responses={ status.HTTP_403_FORBIDDEN: {"description": "User does not have permission to access this group"}, status.HTTP_404_NOT_FOUND: {"description": "Group not found"} } ) async def get_group_balance_summary( group_id: int, db: AsyncSession = Depends(get_db), current_user: UserModel = Depends(get_current_user), ): """ Retrieves a detailed financial balance summary for all users within a specific group. It considers all expenses, their splits, and all settlements recorded for the group. The user must be a member of the group to view its balance summary. """ logger.info(f"User {current_user.email} requesting balance summary for group {group_id}") # 1. Verify user is a member of the target group (using crud_group.check_group_membership or similar) # Assuming a function like this exists in app.crud.group or we add it. # For now, let's placeholder this check logic. # await crud_group.check_group_membership(db=db, group_id=group_id, user_id=current_user.id) # A simpler check for now: fetch the group and see if user is part of member_associations group_check = await db.execute( select(GroupModel) .options(selectinload(GroupModel.member_associations)) .where(GroupModel.id == group_id) ) db_group_for_check = group_check.scalars().first() if not db_group_for_check: raise GroupNotFoundError(group_id) user_is_member = any(assoc.user_id == current_user.id for assoc in db_group_for_check.member_associations) if not user_is_member: # If ListPermissionError is generic enough for "access resource", use it, or a new GroupPermissionError raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=f"User not a member of group {group_id}") # 2. Calculate the group balance summary try: balance_summary = await crud_cost.calculate_group_balance_summary(db=db, group_id=group_id) logger.info(f"Successfully generated balance summary for group {group_id} for user {current_user.email}") return balance_summary except GroupNotFoundError as e: logger.warning(f"Group {group_id} not found during balance summary calculation: {str(e)}") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) except UserNotFoundError as e: # Should not happen if group members are correctly fetched logger.error(f"User not found during balance summary for group {group_id}: {str(e)}") raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="An internal error occurred finding a user for the summary.") except Exception as e: logger.error(f"Unexpected error generating balance summary for group {group_id}: {str(e)}", exc_info=True) raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="An unexpected error occurred while generating the group balance summary.")