from typing import List, Optional from fastapi import APIRouter, Depends, HTTPException, status, Request from sqlalchemy.orm import Session from datetime import date from app.api.deps import get_db, get_current_read_user, get_current_operator_user from app.crud.crud_ppr import ppr as crud_ppr from app.crud.crud_journal import journal as crud_journal from app.schemas.ppr import PPR, PPRCreate, PPRUpdate, PPRStatus, PPRStatusUpdate, Journal from app.models.ppr import User from app.core.utils import get_client_ip from app.core.email import email_service from app.core.config import settings router = APIRouter() @router.get("/", response_model=List[PPR]) async def get_pprs( request: Request, skip: int = 0, limit: int = 100, status: Optional[PPRStatus] = None, date_from: Optional[date] = None, date_to: Optional[date] = None, db: Session = Depends(get_db), current_user: User = Depends(get_current_read_user) ): """Get PPR records with optional filtering""" pprs = crud_ppr.get_multi( db, skip=skip, limit=limit, status=status, date_from=date_from, date_to=date_to ) return pprs @router.post("/", response_model=PPR) async def create_ppr( request: Request, ppr_in: PPRCreate, db: Session = Depends(get_db), current_user: User = Depends(get_current_operator_user) ): """Create a new PPR record""" client_ip = get_client_ip(request) ppr = crud_ppr.create(db, obj_in=ppr_in, created_by=current_user.username, user_ip=client_ip) # Send real-time update via WebSocket if hasattr(request.app.state, 'connection_manager'): await request.app.state.connection_manager.broadcast({ "type": "ppr_created", "data": { "id": ppr.id, "ac_reg": ppr.ac_reg, "status": ppr.status.value } }) return ppr @router.post("/public", response_model=PPR) async def create_public_ppr( request: Request, ppr_in: PPRCreate, db: Session = Depends(get_db) ): """Create a new PPR record (public endpoint, no authentication required)""" client_ip = get_client_ip(request) # For public submissions, use a default created_by or None ppr = crud_ppr.create(db, obj_in=ppr_in, created_by="public", user_ip=client_ip) # Send real-time update via WebSocket if hasattr(request.app.state, 'connection_manager'): await request.app.state.connection_manager.broadcast({ "type": "ppr_created", "data": { "id": ppr.id, "ac_reg": ppr.ac_reg, "status": ppr.status.value } }) # Send email if email provided if ppr_in.email: await email_service.send_email( to_email=ppr_in.email, subject="PPR Submitted Successfully", template_name="ppr_submitted.html", template_vars={ "name": ppr_in.captain, "aircraft": ppr_in.ac_reg, "arrival_time": ppr_in.eta.strftime("%Y-%m-%d %H:%M"), "departure_time": ppr_in.etd.strftime("%Y-%m-%d %H:%M") if ppr_in.etd else "N/A", "purpose": ppr_in.notes or "N/A", "public_token": ppr.public_token, "base_url": settings.base_url } ) return ppr @router.get("/{ppr_id}", response_model=PPR) async def get_ppr( ppr_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_read_user) ): """Get a specific PPR record""" ppr = crud_ppr.get(db, ppr_id=ppr_id) if not ppr: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="PPR record not found" ) return ppr @router.put("/{ppr_id}", response_model=PPR) async def update_ppr( request: Request, ppr_id: int, ppr_in: PPRUpdate, db: Session = Depends(get_db), current_user: User = Depends(get_current_operator_user) ): """Update a PPR record""" db_ppr = crud_ppr.get(db, ppr_id=ppr_id) if not db_ppr: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="PPR record not found" ) client_ip = get_client_ip(request) ppr = crud_ppr.update(db, db_obj=db_ppr, obj_in=ppr_in, user=current_user.username, user_ip=client_ip) # Send real-time update if hasattr(request.app.state, 'connection_manager'): await request.app.state.connection_manager.broadcast({ "type": "ppr_updated", "data": { "id": ppr.id, "ac_reg": ppr.ac_reg, "status": ppr.status.value } }) return ppr @router.patch("/{ppr_id}", response_model=PPR) async def patch_ppr( request: Request, ppr_id: int, ppr_in: PPRUpdate, db: Session = Depends(get_db), current_user: User = Depends(get_current_operator_user) ): """Partially update a PPR record (only provided fields will be updated)""" db_ppr = crud_ppr.get(db, ppr_id=ppr_id) if not db_ppr: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="PPR record not found" ) client_ip = get_client_ip(request) ppr = crud_ppr.update(db, db_obj=db_ppr, obj_in=ppr_in, user=current_user.username, user_ip=client_ip) # Send real-time update if hasattr(request.app.state, 'connection_manager'): await request.app.state.connection_manager.broadcast({ "type": "ppr_updated", "data": { "id": ppr.id, "ac_reg": ppr.ac_reg, "status": ppr.status.value } }) return ppr @router.patch("/{ppr_id}/status", response_model=PPR) async def update_ppr_status( request: Request, ppr_id: int, status_update: PPRStatusUpdate, db: Session = Depends(get_db), current_user: User = Depends(get_current_operator_user) ): """Update PPR status (LANDED, DEPARTED, etc.)""" client_ip = get_client_ip(request) ppr = crud_ppr.update_status( db, ppr_id=ppr_id, status=status_update.status, timestamp=status_update.timestamp, user=current_user.username, user_ip=client_ip ) if not ppr: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="PPR record not found" ) # Send real-time update if hasattr(request.app.state, 'connection_manager'): await request.app.state.connection_manager.broadcast({ "type": "status_update", "data": { "id": ppr.id, "ac_reg": ppr.ac_reg, "status": ppr.status.value, "timestamp": ppr.landed_dt.isoformat() if ppr.landed_dt else (ppr.departed_dt.isoformat() if ppr.departed_dt else None) } }) # Send email if cancelled and email provided if status_update.status == PPRStatus.CANCELED and ppr.email: await email_service.send_email( to_email=ppr.email, subject="PPR Cancelled", template_name="ppr_cancelled.html", template_vars={ "name": ppr.captain, "aircraft": ppr.ac_reg, "arrival_time": ppr.eta.strftime("%Y-%m-%d %H:%M"), "departure_time": ppr.etd.strftime("%Y-%m-%d %H:%M") if ppr.etd else "N/A" } ) return ppr @router.delete("/{ppr_id}", response_model=PPR) async def delete_ppr( request: Request, ppr_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_operator_user) ): """Delete (soft delete) a PPR record""" client_ip = get_client_ip(request) ppr = crud_ppr.delete(db, ppr_id=ppr_id, user=current_user.username, user_ip=client_ip) if not ppr: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="PPR record not found" ) # Send real-time update if hasattr(request.app.state, 'connection_manager'): await request.app.state.connection_manager.broadcast({ "type": "ppr_deleted", "data": { "id": ppr.id, "ac_reg": ppr.ac_reg } }) return ppr @router.get("/public/edit/{token}", response_model=PPR) async def get_ppr_for_edit( token: str, db: Session = Depends(get_db) ): """Get PPR details for public editing using token""" ppr = crud_ppr.get_by_public_token(db, token) if not ppr: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Invalid or expired token" ) # Only allow editing if not already processed if ppr.status in [PPRStatus.CANCELED, PPRStatus.DELETED, PPRStatus.LANDED, PPRStatus.DEPARTED]: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="PPR cannot be edited at this stage" ) return ppr @router.patch("/public/edit/{token}", response_model=PPR) async def update_ppr_public( token: str, ppr_in: PPRUpdate, request: Request, db: Session = Depends(get_db) ): """Update PPR publicly using token""" ppr = crud_ppr.get_by_public_token(db, token) if not ppr: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Invalid or expired token" ) # Only allow editing if not already processed if ppr.status in [PPRStatus.CANCELED, PPRStatus.DELETED, PPRStatus.LANDED, PPRStatus.DEPARTED]: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="PPR cannot be edited at this stage" ) client_ip = get_client_ip(request) updated_ppr = crud_ppr.update(db, db_obj=ppr, obj_in=ppr_in, user="public", user_ip=client_ip) return updated_ppr @router.delete("/public/cancel/{token}", response_model=PPR) async def cancel_ppr_public( token: str, request: Request, db: Session = Depends(get_db) ): """Cancel PPR publicly using token""" ppr = crud_ppr.get_by_public_token(db, token) if not ppr: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Invalid or expired token" ) # Only allow canceling if not already processed if ppr.status in [PPRStatus.CANCELED, PPRStatus.DELETED, PPRStatus.LANDED, PPRStatus.DEPARTED]: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="PPR cannot be cancelled at this stage" ) client_ip = get_client_ip(request) # Cancel by setting status to CANCELED cancelled_ppr = crud_ppr.update_status( db, ppr_id=ppr.id, status=PPRStatus.CANCELED, user="public", user_ip=client_ip ) # Send cancellation email if email provided if cancelled_ppr.email: await email_service.send_email( to_email=cancelled_ppr.email, subject="PPR Cancelled", template_name="ppr_cancelled.html", template_vars={ "name": cancelled_ppr.captain, "aircraft": cancelled_ppr.ac_reg, "arrival_time": cancelled_ppr.eta.strftime("%Y-%m-%d %H:%M"), "departure_time": cancelled_ppr.etd.strftime("%Y-%m-%d %H:%M") if cancelled_ppr.etd else "N/A" } ) return cancelled_ppr @router.get("/{ppr_id}/journal", response_model=List[Journal]) async def get_ppr_journal( ppr_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_read_user) ): """Get journal entries for a specific PPR""" # Verify PPR exists ppr = crud_ppr.get(db, ppr_id=ppr_id) if not ppr: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="PPR record not found" ) return crud_journal.get_by_ppr_id(db, ppr_id=ppr_id)