o
    h><                     @  sV  d dl mZ d dlZd dlZd dlZd dl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 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"dBddZ#dCddZ$dDddZ%dCd d!Z&dedfdEd)d*Z'dFdGd-d.Z(	 dHd1d2Z)dId5d6Z*dJd7d8Z+efdKd:d;Z,efdKd<d=Z-dLdMd@dAZ.dS )N    )annotationsN)IterableListOptionalDictAny)HTTPAdapter)Retry)datetime)current_app)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   8/home/www/bk.finsightngr.online/FinSight/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
   
r2   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*   r0   r2   )r"   r%   r3   Zhas_msgZhas_lnkr   r   r    _validate_item,   s   

r7   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   )r9   r   r)   )jsonheaderstimeoutzcontent-typer5   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)r9   totalsentfailed	responseszPush summary: %s)$r7   r.   uuiduuid4hexupdater   requestsSessionr!   BATCH_LIMITr   RETRIESpostCONNECT_READstatus_coderA   r*   lowerr-   r@   textr/   appendRuntimeErrorloggingwarningRequestExceptionBASE_BACKOFFrandomuniformtimesleeperrorCHUNK_PAUSE_Sinfo)r   r9   r;   r<   itrA   rG   rH   rI   rJ   sessionchunkpayloadlast_excZattemptresprD   rE   eZsleep_ssummaryr   r   r    push_sms_batchB   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   )paramsrB   httpz[Shorten] failed for z: N)	rO   r*   okrW   r-   stripr\   rZ   r[   )rn   rB   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   r6   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/   )r6   partsidxr   r   r    _extract_content_id_from_link   s   

r~   c                  C  s   z:t jt jdt jd} |  }| jt jdidd tj	
  tjd| d |t  d dW S  tyS   tj	  tj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   rb   )r   queryfiltersubscribeStatusis_has_received_messagecountrN   r   rf   commitr   loggerrd   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	j
d dddS g }g }g }|D ]=}t|j}|s9q/t|j}|jpBd}d| }	||||	| d	 t|j}
|t|
|j|||| d
dd || q/|svd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	j
#d Y nw t	j
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   )rH   skippedr5   Your news link today is r%   r3   r4   r   Zqueued
content_idrecipient_idr3   r%   
short_linkr   provider_statusprovider_responsez	FIN-PUSH-r>   r9   rH   rq   rI   z,[MorningPush] DB error while marking/loggingz[MorningPush] batch_id= sent=z total=)r9   rG   rH   )$r   r   r   r   r   	news_linkisnotr   allr   r   rd   rv   phonert   servicePackrX   r~   r   idr   rK   rL   rM   rm   r*   r   rf   addr   r,   r   r   r   r   r   )r   targetsr   Zto_markZlogssubr%   shortr3   r4   r   r9   rl   rH   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jd Y nw q%tj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   TNFr5   r   pending)subscriber_idr%   r3   r   r4   rD   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   rf   r   r   r   r   r   r   r   r   rd   )r   ZPAGEaddedr   last_idpager   r%   r   r3   msgr   r   r    enqueue_sms_for_activesS  sP   




 r     r   c           	      C  s|  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jd Y nw tj 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   )rD   r   )ZpickedrH   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   rH   r)   TNrq   r   rI   z [SMSQueue] process commit failedz[SMSQueue] batch_id=r   z picked=)"r   r   	filter_byr   r   r   r   r   rm   r*   attemptsrD   r   r   r   r   rf   r   r   r~   r   r3   r%   r   r,   
last_errorr   r   r   r   r   r   rd   r   )	r   r   r   r9   rl   rq   rH   rs   r   r   r   r    process_sms_outbox_once  sV   





"r   )r   r   r   r   r   r   )r"   r#   r   r$   )r   r   )
r   r8   r9   r:   r;   r,   r<   r=   r   r#   )r>   )rn   r,   r   r,   )r%   ru   r   ru   )r6   ru   r   rw   )r   r#   )r   r,   r   r#   )r   )r   r   r   r#   )/
__future__r   rZ   r`   rK   r^   typingr   r   r   r   r   rO   Zrequests.adaptersr   Zurllib3.util.retryr	   r
   flaskr   Zsqlalchemy.excr   
app.modelsr   r   r   r   r   SENDER_DEFAULTZPUSH_URLrQ   rR   rT   r]   rc   r!   r.   r2   r7   rm   rt   rv   r~   r   r   r   r   r   r   r   r    <module>   sB    



h



V3