"""Vehicles router: CRUD, filtering, sorting, and mobile.de integration endpoints.""" import uuid from fastapi import APIRouter, Depends, HTTPException, Query, status from sqlalchemy.ext.asyncio import AsyncSession from app.database import get_db from app.dependencies import get_current_user, get_pagination from app.models.user import User from app.schemas.vehicle import ( MobileDePushResponse, MobileDeStatusResponse, VehicleCreate, VehicleListResponse, VehicleResponse, VehicleUpdate, ) from app.services import vehicle_service from app.services import mobilede_service router = APIRouter(prefix="/vehicles", tags=["vehicles"]) @router.get("/", response_model=VehicleListResponse, status_code=status.HTTP_200_OK) async def list_vehicles( db: AsyncSession = Depends(get_db), pagination: dict = Depends(get_pagination), vehicle_type: str | None = Query(None, description="Filter by vehicle type"), availability: str | None = Query(None, description="Filter by availability"), min_price: float | None = Query(None, ge=0, description="Minimum price"), max_price: float | None = Query(None, ge=0, description="Maximum price"), search: str | None = Query( None, description="Search in make, model, fin, location" ), sort: str | None = Query(None, description="Sort field (prefix - for descending)"), current_user: User = Depends(get_current_user), ): """List vehicles with pagination, filtering, and sorting.""" vehicles, total = await vehicle_service.list_vehicles( db, page=pagination["page"], page_size=pagination["page_size"], vehicle_type=vehicle_type, availability=availability, min_price=min_price, max_price=max_price, search=search, sort=sort, ) return VehicleListResponse( items=[VehicleResponse.model_validate(v) for v in vehicles], total=total, page=pagination["page"], page_size=pagination["page_size"], ) @router.post("/", response_model=VehicleResponse, status_code=status.HTTP_201_CREATED) async def create_vehicle( body: VehicleCreate, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): """Create a new vehicle.""" data = body.model_dump(exclude_unset=False) try: vehicle = await vehicle_service.create_vehicle(db, data) except ValueError as exc: raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail={"error": {"code": "DUPLICATE_FIN", "message": str(exc)}}, ) return VehicleResponse.model_validate(vehicle) @router.get( "/{vehicle_id}", response_model=VehicleResponse, status_code=status.HTTP_200_OK ) async def get_vehicle( vehicle_id: uuid.UUID, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): """Get a single vehicle by ID.""" vehicle = await vehicle_service.get_vehicle_by_id(db, vehicle_id) if vehicle is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail={ "error": {"code": "VEHICLE_NOT_FOUND", "message": "Vehicle not found"} }, ) return VehicleResponse.model_validate(vehicle) @router.put( "/{vehicle_id}", response_model=VehicleResponse, status_code=status.HTTP_200_OK ) async def update_vehicle( vehicle_id: uuid.UUID, body: VehicleUpdate, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): """Update a vehicle's fields.""" updates = body.model_dump(exclude_unset=True) if not updates: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail={"error": {"code": "NO_FIELDS", "message": "No fields to update"}}, ) try: vehicle = await vehicle_service.update_vehicle(db, vehicle_id, updates) except ValueError as exc: raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail={"error": {"code": "DUPLICATE_FIN", "message": str(exc)}}, ) if vehicle is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail={ "error": {"code": "VEHICLE_NOT_FOUND", "message": "Vehicle not found"} }, ) return VehicleResponse.model_validate(vehicle) @router.delete( "/{vehicle_id}", response_model=VehicleResponse, status_code=status.HTTP_200_OK ) async def delete_vehicle( vehicle_id: uuid.UUID, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): """Soft-delete a vehicle (sets deleted_at).""" vehicle = await vehicle_service.soft_delete_vehicle(db, vehicle_id) if vehicle is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail={ "error": {"code": "VEHICLE_NOT_FOUND", "message": "Vehicle not found"} }, ) return VehicleResponse.model_validate(vehicle) @router.post( "/{vehicle_id}/mobile-de/push", response_model=MobileDePushResponse, status_code=status.HTTP_202_ACCEPTED, ) async def push_to_mobile_de( vehicle_id: uuid.UUID, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): """Push a vehicle listing to mobile.de (async, returns 202).""" vehicle = await vehicle_service.get_vehicle_by_id(db, vehicle_id) if vehicle is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail={ "error": {"code": "VEHICLE_NOT_FOUND", "message": "Vehicle not found"} }, ) listing = await mobilede_service.push_listing(db, vehicle) return MobileDePushResponse( message="Push queued", vehicle_id=vehicle.id, listing_id=listing.id, ) @router.get( "/{vehicle_id}/mobile-de/status", response_model=MobileDeStatusResponse, status_code=status.HTTP_200_OK, ) async def get_mobile_de_status( vehicle_id: uuid.UUID, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): """Get the mobile.de sync status for a vehicle.""" # Verify vehicle exists vehicle = await vehicle_service.get_vehicle_by_id(db, vehicle_id) if vehicle is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail={ "error": {"code": "VEHICLE_NOT_FOUND", "message": "Vehicle not found"} }, ) listing = await mobilede_service.get_listing_status(db, vehicle_id) if listing is None: return MobileDeStatusResponse( synced=False, ad_id=None, synced_at=None, sync_status="pending", error_log=None, ) return MobileDeStatusResponse( synced=(listing.sync_status == "synced"), ad_id=listing.ad_id, synced_at=listing.synced_at, sync_status=listing.sync_status, error_log=listing.error_log, )