o
    Bpj$S                     @  s  U d Z ddlmZ ddlZddlZddlZddlmZmZm	Z	m
Z
 ddlmZmZ ddlmZ eeZdZdZg d	Zd
dgZdg ddddddddeeeedZded< d\ddZd]d d!Zd^d$d%Zd_d'd(Zdd)d`d+d,Zdadbd0d1Zdcd2d3Z ddd7d8Z!daded:d;Z"dfd=d>Z#dgdAdBZ$ddCdhdEdFZ%dadidGdHZ&djdJdKZ'dkdLdMZ(dldOdPZ)dmdTdUZ*dndXdYZ+dodZd[Z,dS )pu  Configurable email alerts for business usage-limit and validity events.

This feature owns its own SMTP transport config, fully independent of the
support-ticket email settings. It only borrows the generic ``_smtp_send``
transport helper (a pure sender that takes a config dict).

Two levels of configuration:
  * Global   (settings key ``usage_validity_alert_email``): SMTP transport,
    master enable, global recipient list, and default thresholds
    (usage %, validity warn-days).
  * Per-business (table ``pca_usage_alert_prefs``): optional own enable flag,
    extra recipients (added to global), and threshold overrides. Also stores
    the per-event "already sent" state so each threshold alerts at most once
    per fresh transition.

Events are dynamic:
  * ``usage_<t>``       — transcribed minutes reached t% of the limit (e.g. 90/95/100)
  * ``validity_<d>d``   — d days (or fewer) remain before validity expiry (e.g. 7/1)
  * ``validity_expired``— validity period has passed
    )annotationsN)AnyDictListOptional)
_smtp_send#ensure_notification_settings_schema)evaluate_usage_allocationusage_validity_alert_emailz********)Z   _   d         F iK  Tz
PCA Alerts)enabled
recipients	smtp_host	smtp_port	smtp_usersmtp_passwordsmtp_use_tls
from_email	from_namereply_to_emailusage_thresholdsvalidity_warn_daysDict[str, Any]DEFAULT_CONFIG
table_namestrreturnboolc                 C  s   |  d|f |  d uS )Nz}
        SELECT 1 FROM information_schema.tables
        WHERE table_schema = DATABASE() AND table_name = %s LIMIT 1
        )executefetchone)cursorr    r&   G/home/aiteam/pcaa-dev/dashboard-backend/usage_validity_alert_service.py_table_exists>   s
   r(   valuer   lointhiOptional[List[int]]c             
   C  s   | d u rd S t | ttfsd S g }| D ])}zt|}W n ttfy&   Y qw ||  kr1|kr<n q||vr<|| qt|S N)
isinstancelisttupler+   	TypeError
ValueErrorappendsorted)r)   r*   r,   outitemnr&   r&   r'   _clean_int_listI   s    
r9   rawOptional[Dict[str, Any]]c                 C  s   t t t}| s|S |  D ]\}}|||< qt|dddd}|d ur)|ntt|d< t|dddd}|d ur?|ntt	|d< |S )Nr   r   r   r*   r,   r   r   m  )
jsonloadsdumpsr   itemsr9   getr0   DEFAULT_USAGE_THRESHOLDSDEFAULT_VALIDITY_WARN_DAYS)r:   basekeyr)   utvdr&   r&   r'   _merge_defaultsY   s   
rI   hostc                 C  sH   t | pd } | stdd| v rtdd| v sd| v r"td| S )Nr   z8SMTP host is not configured for usage & validity alerts.@zSMTP host must be a server name (e.g. smtp.office365.com or smtp.gmail.com), not an email address. Put the email in Username / From email instead. /z@SMTP host looks invalid. Use a hostname like smtp.office365.com.)r    stripr3   )rJ   r&   r&   r'   _validate_smtp_hostg   s   rO   include_secretsrQ   c                C  s   t |  i }t| dr-| dtf |  }|r-|dr-|d }t|tr(|nt	|}t
|}|r5|S t|}|drHt|d< d|d< |S d|d< |S )Npcaa_notification_settingszQSELECT config_json FROM pcaa_notification_settings WHERE setting_key = %s LIMIT 1config_jsonr   Thas_smtp_passwordF)r   r(   r#   SETTING_KEYr$   rB   r/   dictr>   r?   rI   PASSWORD_MASK)r%   rQ   storedrowr:   configmaskedr&   r&   r'   get_usage_alert_configx   s*   

r\   payload
updated_byOptional[str]c                 C  s   t |  t| dd}t|pi }dd |dpg D |d< t|dp%d }|r/|tkr8|dp5d|d< t|dp?d }|rKt||d< | d	t	t
||f t| d
dS )NTrP   c                 S  (   g | ]}t |pd  rt | qS r   r    rN   .0rr&   r&   r'   
<listcomp>   
    
z+save_usage_alert_config.<locals>.<listcomp>r   r   r   r   z
        INSERT INTO pcaa_notification_settings (setting_key, config_json, updated_by)
        VALUES (%s, %s, %s)
        ON DUPLICATE KEY UPDATE config_json = VALUES(config_json), updated_by = VALUES(updated_by)
        F)r   r\   rI   rB   r    rN   rW   rO   r#   rU   r>   r@   )r%   r]   r^   currentrZ   passwordrJ   r&   r&   r'   save_usage_alert_config   s"   
rj   c                 C  sr   | sdS t | tjr|  }n%t | tjr| }nztjt| d d }W n ty3   t|  Y S w |dS )NN/A
   z%d %b %Y)r/   datetimedatefromisoformatr    r3   strftime)r)   dr&   r&   r'   _format_date_human   s   

rr   namebidusagec                 C  s   | dpd}| d}t|}| dpd}| dpd}| d}|d ur-|ddnd	}	|d u r6d	}
n|dk rCd
t| d}
n| d}
d|  d| d}d|  d| d| dt| d|
 d|dd|dd|	 d}||fS )Nvalidity_monthsr   validity_expiry_dateused_minutesmonthly_minute_limitusage_percentz.2f%rk   zExpired z day(s) agoz Daysu-   [PCA TEST] Subscription & Usage Reminder —  (BID )uZ  Dear Team,
This is a friendly reminder that your PCA (Post Call Analytics) subscription is approaching its expiry date.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
SUBSCRIPTION DETAILS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Business Name       : z
Business ID         : z

Subscription Validity : z  Months
Expiry Date           : z
Days Remaining        : u   

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
CURRENT USAGE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Current Usage       : ,z Minutes
Allocated Limit     : z Minutes
Usage Percentage    : u  

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
ACTION REQUIRED
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Your subscription is nearing expiration.
To ensure uninterrupted access to PCA services, please renew or extend your subscription before the expiry date.
If your renewal has already been processed, kindly ignore this email.

For assistance or renewal support, please contact our support team.

Thank you for choosing PCA.

Regards,

PCA Support Team

[This is a TEST email sent via the PCA usage & validity alert test path. No thresholds were marked as already-sent by this test.])rB   _days_until_expiryabsrr   )rs   rt   ru   rv   expirydays_remainingusedlimitpctpct_displaydays_displaysubjectbodyr&   r&   r'   "_build_subscription_reminder_email   s@   


	r   to_emailc                 C  s   t | dd}|dstdt|d|d< |r3t| t|}t| t|}t|t||\}}nd}d}t||g|| d||rIt|dS d dS )	NTrP   r   z>Usage & validity alert emails are disabled. Enable them first.r   u*   PCA Usage & Validity Alerts — test emailzMThis is a test email from the PCA usage & validity alert notification system.)successsent_tort   )	r\   rB   r3   rO   _business_namer    r	   r   r   )r%   r   rt   rZ   rs   ru   r   r   r&   r&   r'   send_test_usage_alert_email   s   
r   Nonec                 C  s   |  d d S )Na  
        CREATE TABLE IF NOT EXISTS pca_usage_alert_prefs (
            bid VARCHAR(64) NOT NULL PRIMARY KEY,
            enabled TINYINT NULL,
            recipients_json JSON NULL,
            usage_thresholds_json JSON NULL,
            validity_warn_days_json JSON NULL,
            sent_state_json JSON NULL,
            updated_by VARCHAR(100) NULL,
            updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
        )r#   )r%   r&   r&   r'   ensure_alert_prefs_table  s   r   rY   rF   c              	   C  sN   |  |}|d u rd S t|ttfr|S zt|W S  ttfy&   Y d S w r.   )rB   r/   rV   r0   r>   r?   r2   r3   )rY   rF   r:   r&   r&   r'   _load_json_field  s   
r   include_stater   c             	   C  s   t |  | dt|f |  pi }|d}t||d u r!d nt|t|dp*g tt|ddddtt|dd	d
dd}|rJt|dpGi |d< |S )Nz:SELECT * FROM pca_usage_alert_prefs WHERE bid = %s LIMIT 1r   recipients_jsonusage_thresholds_jsonr   r   r<   validity_warn_days_jsonr   r=   )rt   r   r   r   r   sent_state_json
sent_state)r   r#   r    r$   rB   r"   r   r9   )r%   rt   r   rY   r   prefsr&   r&   r'   get_business_alert_prefs  s    
r   c           	   
   C  s   t |  |d}|d u rd nt|rdnd}dd |dp g D }t|dddd	}t|d
ddd	}| dt||t||d urKt|nd |d urUt|nd |f t| |S )Nr   r   r   c                 S  r`   ra   rb   rc   r&   r&   r'   rf   7  rg   z-save_business_alert_prefs.<locals>.<listcomp>r   r   r   r<   r   r=   a  
        INSERT INTO pca_usage_alert_prefs
            (bid, enabled, recipients_json, usage_thresholds_json, validity_warn_days_json, updated_by)
        VALUES (%s, %s, %s, %s, %s, %s)
        ON DUPLICATE KEY UPDATE
            enabled = VALUES(enabled),
            recipients_json = VALUES(recipients_json),
            usage_thresholds_json = VALUES(usage_thresholds_json),
            validity_warn_days_json = VALUES(validity_warn_days_json),
            updated_by = VALUES(updated_by)
        )	r   rB   r"   r9   r#   r    r>   r@   r   )	r%   rt   r]   r^   r   enabled_valr   r   r   r&   r&   r'   save_business_alert_prefs3  s&   

r   r   c                 C  s&   t |  | dt|t|f d S )Nz
        INSERT INTO pca_usage_alert_prefs (bid, sent_state_json)
        VALUES (%s, %s)
        ON DUPLICATE KEY UPDATE sent_state_json = VALUES(sent_state_json)
        )r   r#   r    r>   r@   )r%   rt   r   r&   r&   r'   _save_sent_stateU  s
   r   c                 C  sR   t | ds
d| S | dt|f |  pi }t|dp d p(d| S )N
businesses	Business z2SELECT name FROM businesses WHERE bid = %s LIMIT 1rs   r   )r(   r#   r    r$   rB   rN   )r%   rt   rY   r&   r&   r'   r   d  s
   

 r   Optional[int]c                 C  s|   |  d}|s	d S t|tjr| }n"t|tjr|}nztjt|d d }W n
 ty5   Y d S w |tj  jS )Nrw   rl   )	rB   r/   rm   rn   ro   r    r3   todaydays)ru   r   expiry_dater&   r&   r'   r   l  s   

r   r   	List[int]r   c           
      C  s   g }|  d}|  ds'|dur'tt|D ]}|d| ||kd|f q|  drot|  d}|  d}tt|D ]'}|d	krEq>| oW|duoWd	|  koU|kn  }	|d
| d|	d|f q>|d|dd	f |S )z*Yield (event_key, condition, kind, value).rz   	unlimitedNusage_ru   rw   validity_expiredvalidity_days_remainingr   	validity_rq   validity_warn)rB   r5   setr4   r"   )
ru   r   r   eventsuptexpiredr   rq   condr&   r&   r'   _events_for|  s   


&r   	event_keykindc                 C  s  | d}| d}| d}| d}	| d}
| d}| ds.t|p'd|p*d dnd	}|d ur9| d
nd}|	rAt|	nd}|rN|dkrN| dnd}|	sUd}n| dr]d}n|
d urhd|
 d}nd}d| d| dsu|nd	 d| d| d| d| d| }|dkr|dkrd| d| d}d| d| d | d!}||fS d"| d#| d| d}d| d| d$| d%| d&	}||fS |d'krd(| d)| d| d}d| d| d*| d+}||fS d,| d| d}d| d| d-| d.}||fS )/Nrx   ry   rz   rw   r   rv   r   r   	Unlimitedr{   zN/A (unlimited)zNot configuredz month(s) from onboardingr   ExpiredzActive (z day(s) remaining)ActivezUsed minutes: z
Total allocated minutes: z
Remaining minutes: z
Usage: z
Validity period: z
Validity expiry: z
Validity status: ru   r   u    [PCA] Usage limit exhausted — r|   r}   r   z() has exhausted its allocated minutes.

zh

The pipeline for this business may be auto-paused. Open PCA Master Panel > Usage to review and top up.z[PCA] Usage at u   % — z) has reached z% of its allocated minutes.

z[

Consider topping up before the limit is reached. Open PCA Master Panel > Usage to review.r   z[PCA] Validity expiring in ~u    day(s) — z() will reach its validity expiry soon.

zE

Open PCA Master Panel > Usage to extend validity before it expires.u   [PCA] Validity expired — z.) has passed its configured validity period.

zq

The pipeline for this business may be auto-paused. Open PCA Master Panel > Usage to review and extend validity.)rB   maxr    )r   r   r)   rs   rt   ru   r   r   r   r   r   rv   	remaining
usage_textexpiry_textvalidity_periodvalidity_statusaccount_detailsr   r   r&   r&   r'   _build_email  s~   





$


r   c                 C  s2  zt | ds	W dS t| dd}|dsW dS t|dpd s%W dS t| |dd}|dd	u r6W dS ttd
d |dpBg D dd |dpMg D  }|sXW dS |dpc|dpct	}|dpo|dpot
}t|}t||d< t|||}t|dpi }	t| |}
d	}|D ]]\}}}}|	|}|r|sz)t||||
||\}}t|||| tj jdd|	|< d}td||| W q ty } ztd||| W Y d}~qd}~ww |s|rd|	|< d}q|rt| ||	 W dS W dS  ty } ztd|| W Y d}~dS d}~ww )a  Send at most one email per threshold per fresh transition into that state.

    Called once per supervisor sweep per bid. Each event's sent-flag is cleared
    when its condition resolves (admin tops up minutes / extends validity) so a
    future re-occurrence alerts again.
    pca_business_allocationsNTrP   r   r   r   r   Fc                 S     g | ]}|r|qS r&   r&   rc   r&   r&   r'   rf         z3maybe_send_usage_validity_alert.<locals>.<listcomp>r   c                 S  r   r&   r&   rc   r&   r&   r'   rf     r   r   r   r   r   seconds)timespecz/usage/validity alert sent event=%s bid=%s to=%sz/usage/validity alert failed event=%s bid=%s: %sz5maybe_send_usage_validity_alert failed for bid=%s: %s)r(   r\   rB   r    rN   r   r0   rV   fromkeysrC   rD   r   r   r   r   r   rm   now	isoformatloggerinfo	Exceptionwarningr   )r%   rt   ru   
global_cfgr   r   r   r   r   r   rs   changedrF   	conditionr   r)   alreadyr   r   excr&   r&   r'   maybe_send_usage_validity_alert  sh   



r   )r   r    r!   r"   )r)   r   r*   r+   r,   r+   r!   r-   )r:   r;   r!   r   )rJ   r    r!   r    )rQ   r"   r!   r   r.   )r]   r   r^   r_   r!   r   )r!   r    )rs   r    rt   r    ru   r   )r   r    rt   r_   r!   r   )r!   r   )rY   r   rF   r    )rt   r    r   r"   r!   r   )rt   r    r]   r   r^   r_   r!   r   )rt   r    r   r   r!   r   )rt   r    r!   r    )ru   r   r!   r   )ru   r   r   r   r   r   )r   r    r   r    r)   r+   rs   r    rt   r    ru   r   )rt   r    ru   r   r!   r   )-__doc__
__future__r   rm   r>   loggingtypingr   r   r   r   ticket_notification_servicer   r   usage_allocation_utilr	   	getLogger__name__r   rU   rW   rC   rD   r0   r   __annotations__r(   r9   rI   rO   r\   rj   rr   r   r   r   r   r   r   r   r   r   r   r   r   r&   r&   r&   r'   <module>   sZ    






1


"



I