from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session from sqlalchemy import and_ from app.database import get_db from app.models.court import Court from app.models.booking import Booking, BookingStatus from app.models.player import Player from app.models.match import Match, MatchStatus from pydantic import BaseModel from datetime import datetime, timedelta from typing import Optional, List router = APIRouter(prefix="/courts", tags=["courts"]) class BookingCreate(BaseModel): player_id: int court_id: int start_time: datetime duration_hours: float = 1.0 class CourtResponse(BaseModel): id: int name: str court_number: int hourly_rate: float is_active: bool surface_type: str features: str current_status: str current_match: Optional[dict] = None class Config: from_attributes = True class BookingResponse(BaseModel): id: int player_id: int player_name: str court_id: int court_name: str start_time: datetime end_time: datetime duration_hours: float total_cost: float status: str class Config: from_attributes = True def get_court_status(court: Court, db: Session) -> tuple: """Get current status of a court""" # Check if match is in progress match = db.query(Match).filter( Match.court_id == court.id, Match.status == MatchStatus.IN_PROGRESS ).first() if match: team1 = [mp.player.name for mp in match.match_players if mp.team == 1] team2 = [mp.player.name for mp in match.match_players if mp.team == 2] return "occupied", { "match_id": match.id, "team1": team1, "team2": team2, "team1_score": match.team1_score, "team2_score": match.team2_score, "stage": match.stage.value, "started_at": match.started_at.isoformat() if match.started_at else None, } # Check if there's a booking now now = datetime.utcnow() booking = db.query(Booking).filter( Booking.court_id == court.id, Booking.start_time <= now, Booking.end_time >= now, Booking.status.in_([BookingStatus.CONFIRMED, BookingStatus.IN_PROGRESS]) ).first() if booking: return "booked", None return "available", None @router.get("/", response_model=List[CourtResponse]) def get_courts(db: Session = Depends(get_db)): courts = db.query(Court).filter(Court.is_active == True).order_by(Court.court_number).all() result = [] for court in courts: status, match_info = get_court_status(court, db) result.append(CourtResponse( id=court.id, name=court.name, court_number=court.court_number, hourly_rate=court.hourly_rate, is_active=court.is_active, surface_type=court.surface_type, features=court.features, current_status=status, current_match=match_info, )) return result @router.get("/{court_id}") def get_court(court_id: int, db: Session = Depends(get_db)): court = db.query(Court).filter(Court.id == court_id).first() if not court: raise HTTPException(status_code=404, detail="Court not found") status, match_info = get_court_status(court, db) return { "id": court.id, "name": court.name, "court_number": court.court_number, "hourly_rate": court.hourly_rate, "surface_type": court.surface_type, "features": court.features, "current_status": status, "current_match": match_info, } @router.get("/{court_id}/schedule") def get_court_schedule(court_id: int, db: Session = Depends(get_db)): """Get bookings for a court for the next 7 days""" now = datetime.utcnow() end = now + timedelta(days=7) bookings = db.query(Booking).filter( Booking.court_id == court_id, Booking.start_time >= now, Booking.end_time <= end, Booking.status.in_([BookingStatus.CONFIRMED, BookingStatus.IN_PROGRESS]) ).order_by(Booking.start_time).all() return [ { "id": b.id, "player_name": b.player.name, "start_time": b.start_time.isoformat(), "end_time": b.end_time.isoformat(), "duration_hours": b.duration_hours, "is_match_booking": b.is_match_booking, } for b in bookings ] @router.post("/book") def book_court(booking_data: BookingCreate, db: Session = Depends(get_db)): court = db.query(Court).filter(Court.id == booking_data.court_id).first() if not court: raise HTTPException(status_code=404, detail="Court not found") player = db.query(Player).filter(Player.id == booking_data.player_id).first() if not player: raise HTTPException(status_code=404, detail="Player not found") end_time = booking_data.start_time + timedelta(hours=booking_data.duration_hours) # Check for conflicts conflict = db.query(Booking).filter( Booking.court_id == booking_data.court_id, Booking.status.in_([BookingStatus.CONFIRMED, BookingStatus.IN_PROGRESS]), and_(Booking.start_time < end_time, Booking.end_time > booking_data.start_time) ).first() if conflict: raise HTTPException(status_code=400, detail="Court is already booked for this time slot") total_cost = court.hourly_rate * booking_data.duration_hours booking = Booking( player_id=booking_data.player_id, court_id=booking_data.court_id, start_time=booking_data.start_time, end_time=end_time, duration_hours=booking_data.duration_hours, total_cost=total_cost, status=BookingStatus.CONFIRMED, ) db.add(booking) db.commit() db.refresh(booking) return { "id": booking.id, "court_name": court.name, "player_name": player.name, "start_time": booking.start_time.isoformat(), "end_time": booking.end_time.isoformat(), "total_cost": booking.total_cost, "status": booking.status.value, } @router.get("/bookings/upcoming") def get_upcoming_bookings(db: Session = Depends(get_db)): now = datetime.utcnow() bookings = db.query(Booking).filter( Booking.start_time >= now, Booking.status == BookingStatus.CONFIRMED ).order_by(Booking.start_time).limit(20).all() return [ { "id": b.id, "player_name": b.player.name, "court_name": b.court.name, "start_time": b.start_time.isoformat(), "end_time": b.end_time.isoformat(), "total_cost": b.total_cost, } for b in bookings ]