o
    'fja                     @   s
  d dl Z d dlZd dlZd dlmZmZmZ d dlmZ d dl	m
Z
 d dlmZ d dlmZ d dlmZ d dlmZ d d	lmZmZ d d
lmZmZ d dlmZmZ d dlmZ d dlmZ d dl m!Z! d dl"m#Z# e$e%Z&ee'e(ef ged f Z)G dd dZ*dS )    N)AnyCallable	Awaitable)	AriClient)log_ari_event)AriEventsListener)BotActionTrigger)	CallState)CallRegistry)CallStateMachinePROCESSED_CHANNELS)Settingsget_settings)CrmApiClientCrmApiError)create_crm_client)MediaManager)AgentSessionStore)BotSessionManagerc                	   @   s  e Zd Z			dQdedB dedB dedB ddfddZdedB d	eee	f de
dB fd
dZdeddfddZde
deddfddZde
deddfddZde
ddfddZde
ddfddZdRddZdRddZdRddZd	eee	f ddfd d!Zde
ddfd"d#Zde
ddfd$d%Zde
d&eddfd'd(ZdedB fd)d*Zdefd+d,ZdedB fd-d.Zd/ed0ed1edefd2d3Zdefd4d5Zd6ede
fd7d8Zdede
fd9d:Zd6ede fd;d<Z!de"e
 fd=d>Z#d6ede
dB fd?d@Z$d6ede
dB fdAdBZ%d6ededdfdCdDZ&d6ededdfdEdFZ'd6ede
fdGdHZ(e)de fdIdJZ*e)de fdKdLZ+e)de fdMdNZ,de fdOdPZ-dS )SCallServiceNsettings	broadcastcrmreturnc              
   C   s   |pt  | _t| j| _t | _t| j| _|d ur|nt| j| _	|d u | _
t| j| j| j| jd| _t| j| j| j| j| j| j| j| jd| _|| _t | _t| j| j| _i | _i | _t | _d S )N)	on_update	on_action)media_managercall_resolverget_agent_endpointget_registered_extensionon_bot_ready)r   r   r   arir
   registryr   r   r   r   	_owns_crmr   _notify_on_bot_actionbot_managerr   resolve_call_for_eventr   r   _on_bot_readystate_machine
_broadcastr   agent_sessionr   _handle_ari_eventari_listener_pending_outbound_outbound_callsset_cleaned_calls)selfr   r   r    r3   ?/var/www/html/bot-asterisk-ari/backend/services/call_service.py__init__   s8   

zCallService.__init__
channel_ideventc                 C   s  | dpg }d}|D ]}t| d}|r!t|r!|} nq|r.| j|}|r.|S |rY| j |}|r:|S | j |}	|	rY| j|	 |rQ| j	|	| t
d| |	S |r| j D ]!\}
}||kr| j |
}	|	r| j|	 | j	|	| |	  S q`dS )uG   Busca la llamada por canal, call_id UUID en args o caché de salientes.argsNz'"z/Llamada %s re-sincronizada al registry (Stasis))getstrstripr   _looks_like_call_idr"   get_by_channelr/   addlink_channelloggerinfor.   items)r2   r6   r7   r8   call_idrawvalueby_chfoundcachedcidchr3   r3   r4   r'   =   sH   z"CallService.resolve_call_for_eventfnc                 C   s   || _ | j| j d S N)r*   r&   set_on_updater$   )r2   rK   r3   r3   r4   set_broadcastg   s   zCallService.set_broadcastcalltriggerc                    sv   |j pd  }|dkr| ||jI dH  dS |dkr0td|j | |jI dH  dS t	d|j |j dS )z=Acciones disparadas por frases del bot (TE TRANSFIERO, etc.). transferNupdate_planu.   Actualizando plan call=%s — cortando llamadau%   Acción de bot desconocida %r call=%s)
actionr;   lowertransfer_call_to_numbertargetr@   rA   rC   hangupwarning)r2   rO   rP   rT   r3   r3   r4   r%   k   s   zCallService._on_bot_actionnumberc              
      s  d dd |p	dD }|std|j dS |js|jr'td|j dS |jdv r.dS |js:td|j dS d	|_||_	d
|_
| |I dH  | j|}td|j|| | j|jI dH  |j  r|jrz| j|j I dH  W n ty } ztd|j| W Y d}~nd}~ww z| j I dH  W n ty } ztd|j| W Y d}~nd}~ww d|_d|_ |jv rЇ fdd|jD |_| j|jI dH  z+| jj|| jjd	|jdgdI dH }d	|_| j||d  td|j|d | W n. ty3 } z!d|_d|_d|_
td|j|| | |I dH  W Y d}~dS d}~ww d|_
| |I dH  dS )z?Detiene el bot, saca externalMedia y origina destino al puente.rQ   c                 s   s$    | ]}|  s|d kr|V  qdS )+N)isdigit.0cr3   r3   r4   	<genexpr>   s   " z6CallService.transfer_call_to_number.<locals>.<genexpr>u)   Transferencia sin destino válido call=%sNz'Transferencia ya en curso/hecha call=%sendedfailedz Transferencia sin bridge call=%sTtransferringu!   Transferencia call=%s → %s (%s)z.No se pudo quitar media del puente call=%s: %sz&Hangup media post-transfer call=%s: %sFc                    s   g | ]}| kr|qS r3   r3   r]   media_chr3   r4   
<listcomp>   s    z7CallService.transfer_call_to_number.<locals>.<listcomp>agent	caller_id
use_stasisapp_argsidu6   Pata transferencia originada call=%s channel=%s → %serroru/   Fallo originar transferencia call=%s → %s: %stransferred)joinr@   rn   rC   transfer_in_progressagent_leg_originatedrA   status	bridge_idtransfer_targetagent_stater$   r   format_endpointr&   stop_for_callexternal_media_channel_idr!   remove_from_bridge	ExceptionrY   rX   debugexternal_media_attachedchannel_idsr   close_sessionoriginate_channeloutbound_caller_idr"   r?   )r2   rO   rZ   rW   endpointexcchannelr3   re   r4   rV   }   s   


z#CallService.transfer_call_to_numberc                    s   t d j| jj| jj| jj |  I d H  | j	 I d H  t
 fdd jD  jr4 jd nd }|rMtj|  j|d jd d  d |  I d H  d S )NuB   Iniciando conversación bot call=%s providers stt=%s tts=%s llm=%sc                 3   s    | ]
}| j kr|V  qd S rL   )ry   )r^   rI   rO   r3   r4   r`      s    
z,CallService._on_bot_ready.<locals>.<genexpr>r   z
watch-bot-   name)r@   rA   rC   r   stt_providertts_providerllm_provider_load_poliza_for_callr&   start_for_callnextr~   asynciocreate_task_watch_bot_channelr$   )r2   rO   customer_channelr3   r   r4   r(      s,   
zCallService._on_bot_readyc              
      s   |j pd }|std|j dS | jjr| jjs$t	d|j dS z| j
|I dH }W n tyK } ztd||j| W Y d}~dS d}~ww |sXtd||j dS | |_t	d|j||j|j|j dS )zEGET /polizas/{document_id} y guarda el resultado en call.poliza_data.rQ   u7   Bot call=%s sin document_id: no se consulta póliza CRMNu4   CRM deshabilitado: se omite carga de póliza call=%sz Error CRM polizas/%s call=%s: %su,   Póliza no encontrada CRM polizas/%s call=%su?   Póliza cargada call=%s doc=%s poliza=%s producto=%s cliente=%s)document_idr;   r@   rY   rC   r   crm_api_enabledr   enabledrA   
get_polizar   rn   	to_publicpoliza_datapolizaproductonombre_completo)r2   rO   docr   r   r3   r3   r4   r      sN   
z!CallService._load_poliza_for_callc              
      s   z
| j  I d H  W n ty# } ztd| W Y d }~nd }~ww t  | j  | jj	s7t
d n| jjsFtd|  I d H  tj|  dd d S )Nz!ARI cleanup on startup failed: %sz-No se pudo iniciar la tarea del WebSocket ARIu4   WebSocket ARI conectando… (HTTP ARI alcanzable=%s)zllm-warmup-startupr   )r!   cleanup_allr{   r@   rY   r   clearr-   start
task_alivern   	connectedrA   ari_healthyr   r   _warmup_llm_background)r2   r   r3   r3   r4   startup!  s(   

zCallService.startupc                    s8   z| j  I d H  W d S  ty   td Y d S w )Nu   Warmup LLM en startup falló)r&   
warmup_llmr{   r@   	exceptionr2   r3   r3   r4   r   5  s   z"CallService._warmup_llm_backgroundc                    sP   | j  I d H  | j I d H  | j I d H  | jr&| j I d H  d S d S rL   )r&   closer-   stopr!   r#   r   r   r3   r3   r4   shutdown;  s   zCallService.shutdownc                    s   | j |I dH }|r|jdv r| |I dH  |r#| |I dH  |ddkrF|dp0i d}| ||}|rH| |I dH  dS dS dS )z
        if self.settings.ari_debug:
            log_ari_event(
                event,
                full=self.settings.ari_debug_full_events,
            )
        Nra   typeStasisStartr   rm   )r)   handle_eventrs   _cleanup_after_remote_endr$   r9   r'   )r2   r7   rO   r6   r3   r3   r4   r,   B  s   zCallService._handle_ari_eventc                    s*   | j r|  d| dI d H  d S d S )Ncall_update)r   rO   )r*   r   )r2   rO   r3   r3   r4   r$   V  s   zCallService._notifyc                    s`  |j }|| jv rdS | j| | j|I dH  t|j}|jr(||j |D ]'}z| j	|I dH  W q* t
yQ } ztd||| W Y d}~q*d}~ww | j|I dH  | j|d | j|d |jrz| j|jI dH  W n t
y } ztd|j| W Y d}~nd}~ww d|_d|_d|_d|_d|_| j| td| dS )z<Detiene bot, cuelga canales restantes y libera media/bridge.NzHangup residual %s call=%s: %szBridge %s ya eliminado: %sFz9Cleanup completo call=%s (bot detenido, canales colgados))rC   r1   r>   r&   rx   r0   r~   ry   r!   rX   r{   r@   r|   r   r   r.   popr/   rt   delete_bridger}   bot_media_readyrv   r"   removerA   )r2   rO   rC   r~   r6   r   r3   r3   r4   r   \  sL   

z%CallService._cleanup_after_remote_endreasonc                    sN   |j dv rd S |d td|j| | |I d H  | |I d H  d S )Nra   rb   zColgado remoto call_id=%s (%s))rs   finalizer@   rA   rC   r   r$   )r2   rO   r   r3   r3   r4   _remote_hangup_detected  s   

z#CallService._remote_hangup_detectedc                 C   s2   | j jr| j jS | jj}|sd S | j jj|jdS )N	extension)r   agent_endpointr+   currentagent_endpoint_templateformatr   r2   rh   r3   r3   r4   r     s   zCallService.get_agent_endpointc                 C   s(   | j j}|r|j d|j dS | jjS )Nz <>)r+   r   usernamer   r   r   r   r3   r3   r4   get_outbound_caller_id  s   z"CallService.get_outbound_caller_idc                 C   s   | j j}|r	|jS d S rL   )r+   r   r   r   r3   r3   r4   r     s   z$CallService.get_registered_extensionr   r   passwordc              
      s  |  }|  }|r|r|std|  I d H }|s td| jjj|d}d}|dra|ddd }z| j	
d|I d H }|d u}W n ty` }	 ztd	||	 W Y d }	~	nd }	~	ww | j|||}
td
|||| d|
 ||| jjd| j dS )Nz/username, extension y password son obligatorioszARI no disponibler   FzPJSIP//   PJSIPz$No se pudo verificar endpoint %s: %sz8Agente registrado ext=%s user=%s endpoint=%s verified=%sT)r   rh   r   endpoint_verifiedwebrtc_enabledinbound_readysip)r;   
ValueErrorr   RuntimeErrorr   r   r   
startswithsplitr!   get_endpointr{   r@   rY   r+   registerrA   r   r   get_sip_config)r2   r   r   r   ari_okr   r   resourcerA   r   rh   r3   r3   r4   register_agent  sN   
zCallService.register_agentc                 C   s@   | j j}|  }|d u|r| nd || jj|d u| j dS )N)r   rh   r   r   r   r   )r+   r   r   r   r   r   r   )r2   rh   r   r3   r3   r4   get_agent_status  s   zCallService.get_agent_statusrC   c              
      sj   |  |}|st|z| j|I d H  W n ty* } ztt||d }~ww | |I d H  |S rL   )get_callKeyErrorr)   answer_inbound_callr   r:   r$   r2   rC   rO   r   r3   r3   r4   answer_inbound  s   
zCallService.answer_inboundc              
      s$  | j |}tdd|d}| j| || j|j< | j|j t	
d|jt| j  z9| jj||  d|jdgdI d H }|d }| j|| || j|j< tj| |j|d	|jd d
  d W n  ty } zt	d| d|_|d W Y d }~nd }~ww | |I d H  |S )Noutboundringing)	directionrs   rZ   z4Llamada saliente registrada call_id=%s (registry=%d)Tcustomerri   rm   zwatch-outbound-r   r   zOriginate failed: %src   )r   rw   r	   r"   r>   r/   rC   r1   discardr@   rA   lenlist_allr!   r   r   r?   r.   r   r   _watch_outbound_channelr{   rn   rs   r   r$   )r2   rZ   r   rO   r   r6   r   r3   r3   r4   start_outbound  sJ   
zCallService.start_outboundc                    s   | j |}|sdS t|jD ]&}z| j|I d H  W q ty6 } ztd|| W Y d }~qd }~ww |j	rg|j	|jvrgz| j|j	I d H  W n tyf } ztd|j	| W Y d }~nd }~ww |
d | |I d H  | |I d H  dS )NFzHangup channel %s failed: %sz"Hangup externalMedia %s failed: %srb   T)r"   r9   listr~   r!   rX   r{   r@   rY   ry   r   r   r$   )r2   rC   rO   r6   r   r3   r3   r4   rX   	  s4   
zCallService.hangupc                 C   s
   | j  S rL   )r"   r   r   r3   r3   r4   
list_calls"  s   
zCallService.list_callsc                 C   s   | j |p| j|S rL   )r"   r9   r/   )r2   rC   r3   r3   r4   r   %  s   zCallService.get_callc              
      s  |  |}|r|jdkr|S |jr|jr|S | j|p&|jr%|jd nd}|s+|S z| j|I dH }W n, t	yb } z t
d|| |jsM|jdv rV| |dI dH  |W  Y d}~S d}~ww |ddkrl|S d	|d
g|d}| j|I dH }|r| |I dH  |S |S )uN   Si el WS ARI falla, configura puente/media vía HTTP cuando el canal está Up.r   r   Nzsync_outbound_media %s: %s)answeredtalkingzcanal no existe en ARIstateUpr   r   )r   r8   r   )r   r   r}   rt   r.   r9   r~   r!   get_channelr{   r@   r|   outbound_stasis_setuprs   r   r)   r   r$   )r2   rC   rO   r6   rJ   r   r7   updatedr3   r3   r4   sync_outbound_media(  s>   
zCallService.sync_outbound_mediac              
      s  d}t dD ]}tdI dH  | |}|r|jdv r  dS z| j|I dH }W n tyH   |s7|jrD| 	|d| dI dH  Y  dS w |
d}|d	kru|setd
|| | |I dH  d}| |}|rt|jrt|jrtd}q|r|dv r| 	|d| I dH   dS qdS )zHPolling ARI: setup al contestar y detectar colgado remoto sin WebSocket.FiX        ?Nra   zcanal z cerrado en Asteriskr   r   u.   Canal %s Up — setup por polling (call_id=%s)T)DownRingingu   canal cliente pasó a )ranger   sleepr   rs   r!   r   r{   r   r   r9   r@   rA   r   r}   rt   )r2   rC   r6   
setup_done_rO   rJ   r   r3   r3   r4   r   I  sJ   






z#CallService._watch_outbound_channelc              
      s   t dD ]E}tdI dH  | |}|r|jdv r dS | j|s' dS z| j|I dH  W q t	yJ   | 
|d| dI dH  Y  dS w dS )zJPolling del canal cliente del bot: fallback si el WS ARI pierde el hangup.i   r   Nra   zcanal bot cliente z cerrado en Asterisk (watchdog))r   r   r   r   rs   r&   
is_runningr!   r   r{   r   )r2   rC   r6   r   rO   r3   r3   r4   r   n  s$   


zCallService._watch_bot_channelc              
      s   | j |p| j|}|st|| j |s| j | | jjs%|S | j|I dH  |j	s\z| j
|| j| j I dH  W |S  ty[ } ztd|| W Y d}~|S d}~ww |S )u@   Sesión WebRTC lista y reintento de externalMedia si hace falta.Nz#externalMedia pendiente para %s: %s)r"   r9   r/   r   r>   r   r   r   prepare_sessionr}   attach_external_mediar!   r{   r@   rY   r   r3   r3   r4   ensure_webrtc_ready  s2   
	zCallService.ensure_webrtc_readyc                 C   s   | j jS rL   )r-   r   r   r3   r3   r4   ari_connected  s   zCallService.ari_connectedc                 C   s    | j j}|d uot | dk S )Ng     F@)r-   last_event_attime	monotonic)r2   lastr3   r3   r4   ari_events_recent  s   zCallService.ari_events_recentc                 C   s   | j jp| jS rL   )r-   r   r   r   r3   r3   r4   ari_ws_healthy  s   zCallService.ari_ws_healthyc                    s   | j  I d H S rL   )r!   health_checkr   r3   r3   r4   r     s   zCallService.ari_healthy)NNN)r   N).__name__
__module____qualname__r   BroadcastFnr   r5   r:   dictr   r	   r'   rN   r   r%   rV   r(   r   r   r   r   r,   r$   r   r   r   r   r   r   r   r   r   boolrX   r   r   r   r   r   r   r   propertyr   r   r   r   r3   r3   r3   r4   r      s    
#

*

c
'

*
,&!%r   )+r   loggingr   typingr   r   r   
ari.clientr   ari.debug_logr   
ari.eventsr   bot_actionsr   calls.modelsr	   calls.registryr
   calls.state_machiner   r   configr   r   
crm.clientr   r   crm.factoryr   media.managerr   services.agent_sessionr   services.bot_sessionr   	getLoggerr   r@   r  r:   r  r   r3   r3   r3   r4   <module>   s(    
