
    #i+                         S r SSKrSSKrSSKJr  SSKJrJr  SSKJr  SSK	J
r
  SSKJr  SSKJrJr  \" \5      r " S	 S
\
5      r " S S\
5      r " S S\
5      r " S S\
5      r " S S5      r\" 5       rS\S\\   S\4S jrS\SS4S jrg)a  WebSocket connection manager and live race updates for TipSharks API.

Provides a ConnectionManager for race-room-scoped WebSocket connections
and a background simulation task that generates mock odds/result updates
as a placeholder for future live data integration.
    N)defaultdict)UTCdatetime)	WebSocket)	BaseModel)
get_logger)RaceStarterc                   B    \ rS rSr% Sr\\S'   \\S'   Sr\	S-  \S'   Sr
g)	WSMessage   z4Schema for incoming WebSocket messages from clients.typerace_idNdata )__name__
__module____qualname____firstlineno____doc__str__annotations__intr   dict__static_attributes__r       ?/root/tipsharks/tipsharks-elo-api/apps/backend/api/websocket.pyr   r      s    >
ILD$+r   r   c                   L    \ rS rSr% SrSr\\S'   \\S'   \\S'   \	\
   \S'   Srg	)
OddsUpdateMessage!   zOdds update broadcast message.odds_updater   r   	timestampoddsr   Nr   r   r   r   r   r   r   r   r   listr   r   r   r   r   r   r   !   s#    (D#LN
t*r   r   c                   L    \ rS rSr% SrSr\\S'   \\S'   \\S'   \	\
   \S'   Srg	)
ResultUpdateMessage*   z Result update broadcast message.result_updater   r   r"   resultsr   Nr$   r   r   r   r'   r'   *   s#    *D#LN$Zr   r'   c                   <    \ rS rSr% SrSr\\S'   \\S'   \	\S'   Sr
g)	InitialStateMessage3   z&Initial race state sent on connection.initial_stater   r   r   r   N)r   r   r   r   r   r   r   r   r   r   r   r   r   r   r,   r,   3   s    0D#L
Jr   r,   c                       \ rS rSrSrSS jrS\S\SS4S jrS\S\SS4S	 jr	S
\
S\SS4S jrS
\
S\SS4S jrS\S\4S jrS\S\4S jrS\SS4S jrS\SS4S jrSS jrSrg)ConnectionManager>   zManages WebSocket connections grouped by race room.

Each race has its own "room" identified by race_id. Operations
are thread-safe via an asyncio lock.
returnNc                 n    [        [        5      U l        [        R                  " 5       U l        0 U l        g )N)r   set_connectionsasyncioLock_lock_simulation_tasks)selfs    r   __init__ConnectionManager.__init__E   s$    7B37G\\^
:<r   	websocketr   c                 N  #    UR                  5       I Sh  vN   U R                   ISh  vN   U R                  U   R                  U5        SSS5      ISh  vN   [        R                  SUU R                  U5      S.S9  g Nn N[ N/! , ISh  vN  (       d  f       ND= f7f)zAccept a WebSocket and add it to the race room.

Args:
    websocket: The WebSocket connection to register.
    race_id: Race room identifier.
NzWebSocket connected for race)r   connection_countextra)acceptr8   r5   addloggerinfoget_connection_countr:   r=   r   s      r   connectConnectionManager.connectJ   sz         :::g&**95 :*"$($=$=g$F 	 	
 	!:::sT   B%BB%BB%BB%B	,B%B%	B%B"BB"B%c                 Z  #    U R                    ISh  vN   U R                  U   R                  U5        U R                  U   (       d  U R                  U	 SSS5      ISh  vN   [        R	                  SX R                  U5      S.S9  g Ny N,! , ISh  vN  (       d  f       NA= f7f)zRemove a WebSocket from the race room.

Cleans up the room entry when the last client disconnects.

Args:
    websocket: The WebSocket connection to remove.
    race_id: Race room identifier.
Nz WebSocket disconnected from race)r   	remainingr@   )r8   r5   discardrD   rE   rF   rG   s      r   
disconnectConnectionManager.disconnect\   s~      :::g&..y9$$W-%%g. : 	.%4M4Mg4VW 	 	
	 :::sE   B+BB+A BB+"B#+B+B+B(BB($B+messagec                    #     UR                  U5      I Sh  vN   g N! [         a    [        R                  SSS9   gf = f7f)zSend a JSON message to a single WebSocket client.

Args:
    message: JSON-encoded message string.
    websocket: The target WebSocket connection.
NzFailed to send personal messageT)exc_info)	send_text	ExceptionrD   warning)r:   rO   r=   s      r   send_personal_message'ConnectionManager.send_personal_messagen   s@     	M%%g... 	MNN<tNL	Ms0   A    A  AA AAc                   #    U R                    ISh  vN   U R                  R                  U[        5       5      R	                  5       nSSS5      ISh  vN   W H  n UR                  U5      I Sh  vN   M     g Nj N*! , ISh  vN  (       d  f       N?= f N'! [         a    [        R                  SSU0SS9   Mf  f = f7f)zBroadcast a JSON message to all clients in a race room.

Args:
    message: JSON-encoded message string.
    race_id: Target race room identifier.
Nz&Failed to broadcast to client for racer   T)rA   rQ   )	r8   r5   getr4   copyrR   rS   rD   rT   )r:   rO   r   connectionswss        r   broadcast_to_race#ConnectionManager.broadcast_to_racez   s      :::++//?DDFK :Bll7+++  ::: , <$g.!  sz   CA>C3B
CB 	C B4B5B9C CBB	BCB!C?CCCc                 \    [        U R                  R                  U[        5       5      5      $ )z2Return the number of connections for a given race.)lenr5   rX   r4   r:   r   s     r   rF   &ConnectionManager.get_connection_count   s"    4$$((#%899r   c                     XR                   ;   $ )z9Check if a simulation task is already running for a race.)r9   r`   s     r   is_simulation_running'ConnectionManager.is_simulation_running   s    0000r   c                     XR                   ;  aD  [        R                  " [        U5      5      nX R                   U'   [        R                  SSU0S9  gg)ztStart the background simulation for a race if not already running.

Args:
    race_id: Race identifier to simulate.
zStarted race simulationr   r@   N)r9   r6   create_tasksimulate_race_updatesrD   rE   r:   r   tasks      r   start_simulation"ConnectionManager.start_simulation   sO     000&&'<W'EFD.2""7+KK1)W9MKN 1r   c                     U R                   R                  US5      nUb'  UR                  5         [        R	                  SSU0S9  gg)zeCancel the background simulation for a race.

Args:
    race_id: Race identifier to stop simulating.
NzStopped race simulationr   r@   )r9   popcancelrD   rE   rh   s      r   stop_simulation!ConnectionManager.stop_simulation   sF     %%))'48KKMKK1)W9MKN r   c                   #    U R                    ISh  vN   [        U R                  5       H  nU R                  U5        M     [        U R                  5       H2  nU R                  U    H  n UR                  5       I Sh  vN   M     M4     U R                  R                  5         U R                  R                  5         SSS5      ISh  vN   g N NS! [         a     Mz  f = f N! , ISh  vN  (       d  f       g= f7f)zVClose all active connections and cancel all simulations.

Used for graceful shutdown.
N)r8   r%   r9   ro   r5   closerS   clear)r:   r   r[   s      r   	close_allConnectionManager.close_all   s     
 ::: 6 67$$W- 8 1 12++G4B hhj(( 5 3 ##%""((* :: )$  :::s{   DCDAC,0CC
C<C,DC*DC
C'	#C,&C'	'C,*D,D2C53D?D)r5   r8   r9   )r2   N)r   r   r   r   r   r;   r   r   rH   rM   r   rU   r\   rF   boolrc   rj   ro   rt   r   r   r   r   r0   r0   >   s    =

y 
3 
4 
$
) 
c 
d 
$
M3 
M9 
MQU 
Ms S T &:C :C :1S 1T 1	O 	O 	O	Os 	Ot 	O+r   r0   racestartersr2   c                 n   / nU H  nUR                   (       a  UR                   R                  OSnUR                  (       a  UR                  R                  OSnUR                  (       a  UR                  R                  OSnUR	                  UR
                  UR                  UUR                  UUR                  UUR                  UR                  UR                  UR                  UR                  S.5        M     U R                  (       a  U R                  R                  OSnU R                  (       a?  U R                  R                   (       a$  U R                  R                   R#                  5       OSnU R$                  n	U	(       a  U	R#                  5       OSn
U R
                  U R&                  U R(                  U R*                  U R,                  U R.                  U R0                  U R2                  U
UUS.U[5        U5      S.n[7        [9        U R
                  5      US9nUR;                  5       $ )zBuild the initial race state JSON string.

Args:
    race: The Race ORM instance.
    starters: List of Starter ORM instances.

Returns:
    JSON-encoded InitialStateMessage.
N)idhorse_id
horse_name	driver_iddriver_name
trainer_idtrainer_namerunner_numberbarrier
handicap_mplacingdid_not_finish)rz   
meeting_idrace_number
distance_m
start_typegaitweathertrack_conditionrace_datetimevenuemeeting_date)rw   rx   starter_count)r   r   )horsenamedrivertrainerappendrz   r{   r}   r   r   r   r   r   r   meetingr   r   	isoformatr   r   r   r   r   r   r   r   r_   r,   r   model_dump_json)rw   rx   starter_listsr|   r~   r   r   r   race_dtrace_dt_isor   msgs                r   _build_initial_stater      s    L%&WWQWW\\$
'(xxahhmmT)*qyy~~ddJJ([[*ll ,!"99ll99"#"2"2	
	 * #',,DLLDE<@LLT\\MfMf4<<,,668lpL  G)0'##%dK ''//++////II||#33((
 !XD$ c$''l
>C  r   r   c                 p  #    [         R                  SSU 0S9  [        R                  " [        5      nSn[
        R                  " SS5      n US:  GaN  [        R                  U 5      S:X  a  [         R                  S	SU 0S9  g
[
        R                  " SS5      n[        U5       Vs/ s H*  nUS-   [        [
        R                  " SS5      S5      S.PM,     nn[        SU [        R                  " [        5      R                  5       US9n[        R                  UR                  5       U 5      I S
h  vN   [
        R                  " SS5      n[         R"                  " U5      I S
h  vN   [        R                  " [        5      U-
  R%                  5       nUS:  a  GMN  [        R                  U 5      S:  a  ['        SU [        R                  " [        5      R                  5       [        U5       Vs/ s H  nUS-   US-   SS.PM     snS9n[        R                  UR                  5       U 5      I S
h  vN   [         R                  SSU 0S9  g
s  snf  GN% Ns  snf  N*! [         R(                   a    [         R                  SSU 0S9   g
[*         a    [         R-                  SSU 0S9   g
f = f7f)ai  Simulate live race updates for a race room.

Broadcasts mock odds updates every 5-10 seconds for 60 seconds,
then sends a final result update. This is a placeholder until
real live data integration is built.

The function checks for active connections before each broadcast
and exits early if the room is empty.

Args:
    race_id: Race identifier to simulate.
zRace simulation task startedr   r@   g              <   r   z0No more connections for race, simulation exitingN   g      ?g      I@   )r{   r#   r!   )r   r   r"   r#   g      @g      $@r)   T)r{   r   finished)r   r   r"   r*   zRace simulation completedzRace simulation cancelledzRace simulation error)rD   rE   r   nowr   randomrandintmanagerrF   rangerounduniformr   r   r\   r   r6   sleeptotal_secondsr'   CancelledErrorrS   	exception)	r   
start_timeelapsed
num_horsesi	odds_listrO   delayresult_messages	            r   rg   rg     sv     KK.y'6JKKc"JG2&J.Nl++G49F$g.     2.J z**A UE&..d2KQ,OP*  
 ("",,s+557	G ++G,C,C,EwOOO NN3-E--&&&||C(:5DDFG5 l: ''0140$",,s+557 #:.. "#Q1q5dK.	N ++N,J,J,LgVVV/	77KL? P '
 W !! M/	77KL N0G8LMNs   A
J66I% J6$I% (1IAI% 4I54I% )I*5I% "AI% 6I,I% 8I#9I% J6I% I% I% %*J3J6J30J62J33J6)r   r6   r   collectionsr   r   r   fastapir   pydanticr   packages.core.common.loggingr   packages.core.storage.modelsr	   r
   r   rD   r   r   r'   r,   r0   r   r%   r   r   r   rg   r   r   r   <module>r      s      # "   3 6	H		 	 ) ) |+ |+@ 
9!t 9!tG} 9! 9!~@N @N @Nr   