from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session from app.database import get_db from app.models.match import Match, MatchPlayer, MatchStage, MatchStatus, MatchType from app.models.player import Player from app.services import matchmaking from pydantic import BaseModel from typing import Optional, List router = APIRouter(prefix="/matches", tags=["matches"]) class CreateMatchRequest(BaseModel): title: str stage: str = "open" match_type: str = "doubles" creator_player_id: int elo_tolerance: float = 200 class JoinMatchRequest(BaseModel): player_id: int team: int class ScoreUpdateRequest(BaseModel): team1_score: int team2_score: int def match_to_dict(match: Match, db: Session) -> dict: players = db.query(MatchPlayer).filter(MatchPlayer.match_id == match.id).all() team1 = [] team2 = [] for mp in players: p = db.query(Player).filter(Player.id == mp.player_id).first() if p: pdata = { "id": p.id, "name": p.name, "elo": round(p.elo_rating, 1), "avatar_color": p.avatar_color, "elo_change": mp.elo_change, } if mp.team == 1: team1.append(pdata) else: team2.append(pdata) return { "id": match.id, "title": match.title, "stage": match.stage.value, "match_type": match.match_type.value, "status": match.status.value, "team1": team1, "team2": team2, "team1_score": match.team1_score, "team2_score": match.team2_score, "max_players": match.max_players, "current_players": len(players), "min_elo": match.min_elo, "max_elo": match.max_elo, "court_id": match.court_id, "court_name": match.court.name if match.court else None, "started_at": match.started_at.isoformat() if match.started_at else None, "ended_at": match.ended_at.isoformat() if match.ended_at else None, "created_at": match.created_at.isoformat() if match.created_at else None, } @router.get("/") def get_matches(status: Optional[str] = None, stage: Optional[str] = None, db: Session = Depends(get_db)): query = db.query(Match) if status: query = query.filter(Match.status == status) if stage: query = query.filter(Match.stage == stage) matches = query.order_by(Match.created_at.desc()).limit(50).all() return [match_to_dict(m, db) for m in matches] @router.get("/lobby") def get_lobby(db: Session = Depends(get_db)): matches = matchmaking.get_lobby_matches(db) return [match_to_dict(m, db) for m in matches] @router.get("/active") def get_active_matches(db: Session = Depends(get_db)): matches = db.query(Match).filter(Match.status == MatchStatus.IN_PROGRESS).all() return [match_to_dict(m, db) for m in matches] @router.get("/{match_id}") def get_match(match_id: int, db: Session = Depends(get_db)): match = db.query(Match).filter(Match.id == match_id).first() if not match: raise HTTPException(status_code=404, detail="Match not found") return match_to_dict(match, db) @router.post("/") def create_match(req: CreateMatchRequest, db: Session = Depends(get_db)): player = db.query(Player).filter(Player.id == req.creator_player_id).first() if not player: raise HTTPException(status_code=404, detail="Player not found") match_type = MatchType.DOUBLES if req.match_type == "doubles" else MatchType.SINGLES if req.stage == "open": match = matchmaking.create_open_match(db, req.title, match_type) elif req.stage == "skill_based": match = matchmaking.create_skill_match(db, req.title, player.elo_rating, match_type, req.elo_tolerance) else: raise HTTPException(status_code=400, detail="Invalid stage. Use 'open' or 'skill_based'") # Auto-join creator to team 1 matchmaking.join_match(db, match.id, player.id, 1) return match_to_dict(match, db) @router.post("/{match_id}/join") def join_match(match_id: int, req: JoinMatchRequest, db: Session = Depends(get_db)): result = matchmaking.join_match(db, match_id, req.player_id, req.team) if not result: raise HTTPException(status_code=400, detail="Cannot join match (full, ELO constraint, or already joined)") match = db.query(Match).filter(Match.id == match_id).first() return match_to_dict(match, db) @router.post("/{match_id}/start") def start_match(match_id: int, db: Session = Depends(get_db)): match = matchmaking.start_match(db, match_id) if not match: raise HTTPException(status_code=400, detail="Cannot start match (not enough players or already started)") return match_to_dict(match, db) @router.post("/{match_id}/score") def update_score(match_id: int, req: ScoreUpdateRequest, db: Session = Depends(get_db)): match = db.query(Match).filter(Match.id == match_id).first() if not match: raise HTTPException(status_code=404, detail="Match not found") if match.status != MatchStatus.IN_PROGRESS: raise HTTPException(status_code=400, detail="Match not in progress") match.team1_score = req.team1_score match.team2_score = req.team2_score db.commit() return match_to_dict(match, db) @router.post("/{match_id}/complete") def complete_match(match_id: int, req: ScoreUpdateRequest, db: Session = Depends(get_db)): match = matchmaking.complete_match(db, match_id, req.team1_score, req.team2_score) if not match: raise HTTPException(status_code=400, detail="Cannot complete match") return match_to_dict(match, db)