o
    }i:                     @  sT  d dl mZ d dlZd dlZd dlZd dlZeeZd dl	m
Z
mZmZmZmZ d dl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mZmZmZ d	Zd
ZdZdZ dZ!dZ"dZ#dAddZ$dBddZ%dCddZ&dBdd Z'dedfdDd(d)Z(dEdFd,d-Z)	 dGd0d1Z*dHd4d5Z+dId6d7Z,efdJd9d:Z-efdJd;d<Z.dKdLd?d@Z/dS )M    )annotationsN)IterableListOptionalDictAny)HTTPAdapter)Retry)datetime)SQLAlchemyError)db
SubscriberContentSentMessage	SMSOutboxZ7996z3https://fin.finsightngr.online:30701/callbacks/pushd      )      g      ?)g      ?g333333?items
List[dict]sizeintreturnIterable[List[dict]]c                 c  s.    t dt| |D ]}| |||  V  q	d S )Nr   )rangelen)r   r   i r   :/home/www/bk.finsightngr.online/finsigh_v2/app/sms_push.py_chunk   s   r    itemDict[str, Any]Nonec                 C  sN   |  d}t|tr!|dr#t|dkr%d|dd   | d< d S d S d S d S )Nmsisdn0   234   )get
isinstancestr
startswithr   )r!   r$   r   r   r   _normalize_msisdn_inline"   s   
 r-   c                 C  s"   zt | W S  ty   tdw )Nz%pcode must be an integer (e.g., 2273))r   	Exception
ValueError)valr   r   r   _to_int'   s
   
r1   c                 C  sx   |  d}|  d}d| v o| d dv}d| v o| d dv}|s$td|du r,tdt|| d< ||A s:td	dS )
z
    Provider schema requires:
      - msisdn (E.164 / 234XXXXXXXXXX)
      - pcode (INTEGER)
      - exactly one of: message OR link (provider examples show message)
    r$   pcodemessage)N linkzEach item must include msisdn.Nz'Each item must include pcode (integer).z4Each item must have exactly one of: message OR link.)r)   r/   r1   )r!   r$   r2   Zhas_msgZhas_lnkr   r   r   _validate_item-   s   

r6   List[Dict[str, Any]]batch_idOptional[str]endpoint_urlr+   extra_headersOptional[Dict[str, str]]c                 C  s  | D ]
}t | t| q|sdt jdd  }ddd}|r'|| t| }d }}g }	t }
t	| t
D ]}||d}d}tdtd D ]}zb|
j|||td	}|j}z|jd
d drk| }nd|ji}W n ty   d|ji}Y nw |	||d d|  krdk rn n	|t|7 }W  qtd| }td|t|| W n tjy } z|}td|t| W Y d}~nd}~ww |tk rt| tdd }t | qJ|t|7 }t!dt|| t tjt"  q:|||||	d}t#d| |S )z
    Send items to /callbacks/push in controlled chunks with manual retry/backoff.
    Returns:
    {
      "batch_id": "...",
      "total": N,
      "sent": x,
      "failed": y,
      "responses": [ { "status": 200, "body": {...} }, ... ]
    }
    zFIN-N   zapplication/json)zContent-TypeAcceptr   )r8   r   r(   )jsonheaderstimeoutzcontent-typer4   raw)statusbody   i,  zHTTP z,Push attempt %s/%s failed (HTTP %s). Body=%szPush attempt %s/%s raised %rg?z-Giving up on chunk (%s items). Last error: %r)r8   totalsentfailed	responseszPush summary: %s)$r6   r-   uuiduuid4hexupdater   requestsSessionr    BATCH_LIMITr   RETRIESpostCONNECT_READstatus_coder@   r)   lowerr,   r?   textr.   appendRuntimeErrorloggingwarningRequestExceptionBASE_BACKOFFrandomuniformtimesleeperrorCHUNK_PAUSE_Sinfo)r   r8   r:   r;   itr@   rF   rG   rH   rI   sessionchunkpayloadlast_excattemptresprC   rD   eZsleep_ssummaryr   r   r   push_sms_batchC   s   





rm   r=   urlc              
   C  s   z%t jdd| i|d}|jr|jr |jdr#|j W S W | S W | S W | S  t jyD } ztd|  d|  W Y d}~| S d}~ww )z?
    Try TinyURL; fall back to original link on any error.
    z"https://tinyurl.com/api-create.phprn   )paramsrA   httpz[Shorten] failed for z: N)	rN   r)   okrV   r,   stripr[   rY   rZ   )rn   rA   rrk   r   r   r   shorten_link   s    rt   r$   
str | Nonec                 C  s:   | sdS |   } | drt| dkrd| dd  S | S )z@Ensure 234XXXXXXXXXX style; returns None if input is None/empty.Nr%   r&   r'   r(   )rr   r,   r   )r$   r   r   r   _normalize_msisdn   s   rv   r5   Optional[int]c                 C  sR   | sdS z|  d}d|v r|d}t||d  W S W dS  ty(   Y dS w )z7Expect .../content/<content_id>/<subscriber_id>/<hash>.N/contentr(   )splitindexr   r.   )r5   partsidxr   r   r   _extract_content_id_from_link   s   

r~   c                  C  s   z9t jt jdt jd} |  }| jt jdidd tj	
  td| d |t  d dW S  tyQ   tj	  td d	d
d Y S w )zQ
    Set has_received_message=False for all active subscribers (idempotent).
    TF)synchronize_sessionz.[MorningReset] Reset has_received_message for z active subscribers.Z)resetatz[MorningReset] DB errorr   Zdb_error)r   ra   )r   queryfiltersubscribeStatusis_has_received_messagecountrM   r   re   commitloggerrc   r
   utcnow	isoformatr   rollback	exception)qr   r   r   r   &reset_has_received_message_for_actives   s   


r   senderc                 C  s  t jt jdt jdt jd }|s&t	
d dddS g }g }g }|D ]=}t|j}|s8q.t|j}|jpAd}d| }	||||	| d	 t|j}
|t|
|j|||| d
dd || q.|sudt|dS dt jdd  }t||d}|dd}z,|D ]}d|_tj| q|D ]}|rdnd|_t||_tj| qtj  W n t y   tj!  t	"d Y nw t	
d| d| dt|  |t||dS )ae  
    Send 1 SMS per eligible subscriber immediately (no queue).
    Eligibility: active, has news_link, has_received_message=False.
    Message: "Your news link today is <short_link>"
    pcode = servicePack; msisdn = phone.

    Scales to tens of thousands (push helper chunks and retries),
    but for very large lists prefer the queue version below.
    TNFz8[MorningPush] Nothing to send (no eligible subscribers).r   )rG   skippedr4   Your news link today is r$   r2   r3   r   Zqueued
content_idrecipient_idr2   r$   
short_linkr   provider_statusprovider_responsez	FIN-PUSH-r=   r8   rG   rq   rH   z,[MorningPush] DB error while marking/loggingz[MorningPush] batch_id= sent=z total=)r8   rF   rG   )#r   r   r   r   r   	news_linkisnotr   allr   rc   rv   phonert   servicePackrW   r~   r   idr   rJ   rK   rL   rm   r)   r   re   addr   r+   r   r   r   r   r   )r   targetsr   Zto_markZlogssubr$   shortr2   r3   r   r8   rl   rG   logr   r   r   push_news_links_to_actives   sr   










 r   c                 C  s&  d}d}t jt jdt jdt jdt j	
 }d}	 |t j	|k| }|s5q|D ]2}t|j}|sD|j	}q7t|j}|jpMd}	d| }
tjt|j	||	| |
dd	 |d
7 }|j	}q7ztj  W n ty   tj  td Y nw q%td| d d|iS )z
    Enqueue one outgoing SMS per eligible subscriber (fast DB inserts).
    A separate worker drains the queue in batches (process_sms_outbox_once).
    i  r   TNFr4   r   pending)subscriber_idr$   r2   r   r3   rC   r(   z*[SMSQueue] enqueue commit failed mid-batchz[SMSQueue] Enqueued z items.Zenqueued)r   r   r   r   r   r   r   r   order_byr   asclimitr   rv   r   rt   r   r   re   r   r   r   r   r   r   r   rc   )r   ZPAGEaddedr   last_idpager   r$   r   r2   msgr   r   r   enqueue_sms_for_activesT  sP   




 r     r   c           	      C  sx  t jjddt j |  }|sdddS dd |D }d|d j d|d	 j }t||d
}|	dddk}d}zS|D ]I}| j
d7  _
|r|d|_tj	|j}|rw|jswd|_tjtt|j|j|j|jd|jdt|d |d7 }nd|_t||_tj| qAtj  W n ty   tj  td Y nw td| d| dt |  t ||dS )z
    Drain up to `limit` pending items from SMSOutbox:
    - send via push_sms_batch (which chunks & retries)
    - mark Subscriber.has_received_message = True on success
    - log into SentMessage
    r   )rC   r   )ZpickedrG   c                 S  s"   g | ]}|j |j|j|jd qS )r   r   ).0rs   r   r   r   
<listcomp>  s   " z+process_sms_outbox_once.<locals>.<listcomp>z
FIN-PUSHQ--r   rG   r(   TNrq   r   rH   z [SMSQueue] process commit failedz[SMSQueue] batch_id=r   z picked=)!r   r   	filter_byr   r   r   r   r   rm   r)   attemptsrC   r   r   r   r   re   r   r   r~   r   r2   r$   r   r+   
last_errorr   r   r   r   r   rc   r   )	r   r   r   r8   rl   rq   rG   rs   r   r   r   r   process_sms_outbox_once  sV   





 r   )r   r   r   r   r   r   )r!   r"   r   r#   )r   r   )
r   r7   r8   r9   r:   r+   r;   r<   r   r"   )r=   )rn   r+   r   r+   )r$   ru   r   ru   )r5   ru   r   rw   )r   r"   )r   r+   r   r"   )r   )r   r   r   r"   )0
__future__r   rY   r_   rJ   r]   	getLogger__name__r   typingr   r   r   r   r   rN   Zrequests.adaptersr   Zurllib3.util.retryr	   r
   Zsqlalchemy.excr   
app.modelsr   r   r   r   r   SENDER_DEFAULTZPUSH_URLrP   rQ   rS   r\   rb   r    r-   r1   r6   rm   rt   rv   r~   r   r   r   r   r   r   r   r   <module>   sB    




h



V3