Ë
    F()j‡5  ã                   óî  — d dl mZ d dlmZ d dlZd dlZd dlmZ  ej                  e«      Z	ej                  ej                  d<    ej                  ej                  ej                  ¬«      Zd deded	efd
„Zd!dedededededed	efd„Zdefd„Zd"dededefd„Zd"defd„Zd#defd„Zdedefd„Zd#dedefd„Zdefd„Zdefd„Zd#ded	efd„Zd#dededed	efd„Zd#deded	efd„Zd#dededed	efd„Z y)$é    )Ú	firestore)ÚsettingsN)ÚdatetimeÚGOOGLE_APPLICATION_CREDENTIALS)ÚprojectÚdatabaseÚ	tenant_idÚ	thread_idÚuser_idc                 óÆ  — 	 | › d|› �}t         j                  d«      j                  |«      }|j                  «       }|j                  rf|j                  «       }|rB|j                  d«      r1|j                  d«      |k7  rt        j                  d|› d|› �«       g S |j                  dg «      S g S # t        $ r&}t        j                  d|› �d¬	«       g cY d
}~S d
}~ww xY w)zN
    Fetches chat history for a given tenant, thread, and user isolation.
    Ú_Úchat_historyr   z'Unauthorized access attempt for thread z	 by user Úmessagesz,Error fetching chat history from Firestore: T©Úexc_infoN)
ÚdbÚ
collectionÚdocumentÚgetÚexistsÚto_dictÚloggerÚwarningÚ	ExceptionÚerror)r	   r
   r   Údoc_idÚdoc_refÚdocÚdataÚes           ú=/var/www/html/strategist-ai/app/services/firestore_service.pyÚget_chat_historyr"      sÑ   € ðØ�;˜a 	˜{Ð+ˆÜ—-‘- Ó/×8Ñ8¸Ó@ˆØ�k‰k‹mˆØ�:Š:Ø—;‘;“=ˆDá˜4Ÿ8™8 IÔ.°4·8±8¸IÓ3FÈ'Ò3QÜ—‘Ð!HÈÈÐS\Ð]dÐ\eÐfÔgØ�	Ø—8‘8˜J¨Ó+Ð+Øˆ	øÜò Ü�‰ÐCÀAÀ3ÐGÐRVˆÔWØ�	ûðús*   ‚BB1 ÂB1 Â/B1 Â1	C Â:CÃC ÃC ÚroleÚmessageÚ	user_nameÚsummaryc                 óÔ  — 	 | › d|› �}t         j                  d«      j                  |«      }||t        j                  «       j                  «       dœ}	t        j                  «       j                  «       }
t        j                  |	g«      |
| |dœ}	 |j                  «       j                  s|
|d<   |r||d<   |r||d	<   |r||d
<   |j                  |d¬«       t        j                  d|› d| › d|› d|› d�	«       |dk(  rt        | «       yy# t        $ r"}t        j                  d|› �«       Y d}~Œ‚d}~ww xY w# t        $ r$}t        j!                  d|› �d¬«       Y d}~yd}~ww xY w)zM
    Saves a new chat message to Firestore with optional user isolation.
    r   r   )r#   ÚcontentÚ	timestamp)r   Ú
updated_atr	   r
   Ú
created_atz4Could not check existing chat document before save: Nr   r%   r&   T©ÚmergezSaved z message to Firestore for z (User: z
, Thread: ú)Ú	assistantz(Error saving chat message to Firestore: r   )r   r   r   r   ÚutcnowÚ	isoformatr   Ú
ArrayUnionr   r   r   r   r   ÚsetÚinfoÚincrement_ai_response_countr   )r	   r#   r$   r
   r%   r&   r   r   r   Únew_msgÚnowÚupdate_dataÚread_errr    s                 r!   Úsave_chat_messager:   %   s�  € ð'TØ�;˜a 	˜{Ð+ˆÜ—-‘- Ó/×8Ñ8¸Ó@ˆð ØÜ!Ÿ™Ó*×4Ñ4Ó6ñ
ˆô �o‰oÓ×)Ñ)Ó+ˆä!×,Ñ,¨g¨YÓ7ØØ"Ø"ñ	
ˆð	^Ø—;‘;“=×'Ò'Ø,/�˜LÑ)ñ Ø%,ˆK˜	Ñ"ÙØ'0ˆK˜Ñ$ÙØ%,ˆK˜	Ñ"à�‰�K tˆÔ,ä�‰�f˜T˜FÐ"<¸Y¸KÀxÐPWÈyÐXbÐclÐbmÐmnÐoÔpð �;ÒÜ'¨	Õ2ð øô ò 	^Ü�N‰NÐQÐRZÐQ[Ð\×]Ñ]ûð	^ûô" ò TÜ�‰Ð?À¸sÐCÈdˆ×SÑSûðTúsC   ‚BD: ÂD Â0AD: Ä	D7ÄD2Ä-D: Ä2D7Ä7D: Ä:	E'ÅE"Å"E'c                 ó0  — 	 t        j                  «       }|j                  d«      }t        j	                  d«      j                  | «      }|j                  dt        j                  d«      id¬«       |j	                  d«      j                  |«      }|j                  t        j                  d«      ||j                  «       dœd¬«       t        j                  d	|› d
| › �«       y# t        $ r%}t        j                  d| › d|› �«       Y d}~yd}~ww xY w)zT
    Increments the global AI response counter AND a daily bucket for a tenant.
    ú%Y-%m-%dÚtenant_statsÚtotal_ai_responsesé   Tr,   Údaily_stats)Úai_responsesÚdater*   z/Incremented AI response count (Global & Daily: z) for z)Error incrementing AI response count for ú: N)r   r0   Ústrftimer   r   r   r3   r   Ú	Incrementr1   r   r4   r   r   )r	   r7   Údate_strÚ	stats_refÚ	daily_refr    s         r!   r5   r5   R   sý   € ðSÜ�o‰oÓˆØ—<‘< 
Ó+ˆô —M‘M .Ó1×:Ñ:¸9ÓEˆ	Ø�‰Ð+¬Y×-@Ñ-@ÀÓ-CÐDÈDˆÔQð ×(Ñ(¨Ó7×@Ñ@ÀÓJˆ	Ø�‰Ü%×/Ñ/°Ó2ØØŸ-™-›/ñ
ð ð	 	ô 	ô 	�‰ÐEÀhÀZÈvÐV_ÐU`ÐaÕbøÜò SÜ�‰Ð@ÀÀÈ2ÈaÈSÐQ×RÑRûðSús   ‚C$C' Ã'	DÃ0DÄDÚ
start_dateÚend_datec                 ó˜  — 	 t         j                  d«      j                  | «      }|j                  d«      }|r|j                  dd|«      }|r|j                  dd|«      }|j	                  «       }d}|D ]%  }||j                  «       j                  dd«      z  }Œ' |S # t        $ r!}t        j                  d| › d	|› �«       ‚ d
}~ww xY w)zQ
    Sums AI responses for a tenant within a date range using daily buckets.
    r=   r@   rB   z>=ú<=r   rA   z#Error aggregating AI responses for rC   N)
r   r   r   ÚwhereÚstreamr   r   r   r   r   )	r	   rI   rJ   rG   ÚqueryÚdocsÚtotalr   r    s	            r!   Úget_aggregated_ai_responsesrR   j   sÈ   € ðÜ—M‘M .Ó1×:Ñ:¸9ÓEˆ	Ø×$Ñ$ ]Ó3ˆáØ—K‘K ¨¨jÓ9ˆEÙØ—K‘K ¨¨hÓ7ˆEà�|‰|‹~ˆØˆØò 	:ˆCØ�S—[‘[“]×&Ñ& ~°qÓ9Ñ9‰Eð	:àˆøÜò Ü�‰Ð:¸9¸+ÀRÈÀsÐKÔLØûðús   ‚BB Â	C	Â(CÃC	c                 ó  — 	 |r|j                  «       nd}|r|j                  «       nd}t        j                  d«      j                  dd| «      }|j	                  «       }d}|D ]l  }|j                  «       xs i }	|	j                  dg «      D ]A  }
|
j                  d«      dk7  rŒ|
j                  d	«      }|r|r||k  rŒ3|r|r||kD  rŒ=|d
z  }ŒC Œn |S # t        $ r#}t        j                  d| › d|› �d¬«       ‚ d}~ww xY w)z†
    Counts assistant messages directly from chat_history.
    This is the authoritative source for enforcing AI response limits.
    Nr   r	   ú==r   r   r#   r/   r)   r?   z(Error counting AI response messages for rC   Tr   ©
r1   r   r   rM   rN   r   r   r   r   r   )r	   rI   rJ   Ú	start_isoÚend_isoÚthreads_refrP   Úcountr   r   Úmsgr)   r    s                r!   Úget_ai_response_message_countr[   €   s  € ð
Ù.8�J×(Ñ(Ô*¸dˆ	Ù*2�(×$Ñ$Ô&¸ˆÜ—m‘m NÓ3×9Ñ9¸+ÀtÈYÓWˆØ×!Ñ!Ó#ˆàˆØò 
	ˆCØ—;‘;“=Ò& BˆDØ—x‘x 
¨BÓ/ò �Ø—7‘7˜6“? kÒ1ØØŸG™G KÓ0�	Ù¡i°9¸yÒ3HØÙ™y¨Y¸Ò-@ØØ˜‘
‘ñð
	ð ˆøÜò Ü�‰Ð?À	¸{È"ÈQÈCÐPÐ[_ˆÔ`Øûðús   ‚CC Ã	DÃC<Ã<Dc                 óš  — 	 |r|j                  «       nd}t        j                  d«      j                  dd| «      }|j	                  «       }d}|D ]I  }|j                  «       xs i }|j                  d«      xs |j                  d«      }|r|r||k  rŒE|dz  }ŒK |S # t        $ r#}	t        j                  d	| › d
|	› �d¬«       ‚ d}	~	ww xY w)zl
    Counts tenant conversation threads from Firestore, the same store used to detect new chat threads.
    Nr   r	   rT   r   r+   r*   r?   z(Error counting conversation threads for rC   Tr   rU   )
r	   rI   rV   rX   rP   rY   r   r   r+   r    s
             r!   Úget_conversation_thread_countr]   œ   sÕ   € ðÙ.8�J×(Ñ(Ô*¸dˆ	Ü—m‘m NÓ3×9Ñ9¸+ÀtÈYÓWˆØ×!Ñ!Ó#ˆàˆØò 	ˆCØ—;‘;“=Ò& BˆDØŸ™ ,Ó/ÒI°4·8±8¸LÓ3IˆJÙ¡*°
¸YÒ0FØØ�Q‰J‰Eð	ð ˆøÜò Ü�‰Ð?À	¸{È"ÈQÈCÐPÐ[_ˆÔ`Øûðús   ‚BB Â	C
Â'CÃC
Ú
size_bytesc           	      óÂ  — 	 t        j                  «       }|j                  d«      }t        j	                  d«      j                  | «      }|j	                  d«      j                  |«      }|j                  |||j                  «       dœd¬«       t        j                  d| › d|› d	|› d
�«       y# t        $ r%}t        j                  d| › d|› �«       Y d}~yd}~ww xY w)zV
    Logs the current storage size into the daily bucket for historical tracking.
    r<   r=   r@   )Ústorage_bytesrB   r*   Tr,   zLogged storage snapshot for z (z): z bytesz#Error logging storage snapshot for rC   N)r   r0   rD   r   r   r   r3   r1   r   r4   r   r   )r	   r^   r7   rF   rG   rH   r    s          r!   Úlog_storage_snapshotra   ±   sÔ   € ðMÜ�o‰oÓˆØ—<‘< 
Ó+ˆä—M‘M .Ó1×:Ñ:¸9ÓEˆ	Ø×(Ñ(¨Ó7×@Ñ@ÀÓJˆ	à�‰Ø'ØØŸ-™-›/ñ
ð ð	 	ô 	ô 	�‰Ð2°9°+¸RÀ¸zÈÈZÈLÐX^Ð_Õ`øÜò MÜ�‰Ð:¸9¸+ÀRÈÀsÐK×LÑLûðMús   ‚B-B0 Â0	CÂ9CÃCc                 óÜ  — 	 t         j                  d«      j                  | «      }|j                  d«      j                  dt        j
                  j                  ¬«      }|r|j                  dd|«      }|j                  d«      j                  «       }|D ]#  }|j                  «       j                  dd«      c S  y# t        $ r%}t        j                  d	| › d
|› �«       Y d}~yd}~ww xY w)zT
    Retrieves the latest available storage snapshot before or on the end_date.
    r=   r@   rB   )Ú	directionrL   r?   r`   r   z*Error getting latest storage snapshot for rC   N)r   r   r   Úorder_byr   ÚQueryÚ
DESCENDINGrM   ÚlimitrN   r   r   r   r   r   )r	   rJ   rG   rO   rP   r   r    s          r!   Úget_latest_storage_snapshotrh   Æ   sÍ   € ðÜ—M‘M .Ó1×:Ñ:¸9ÓEˆ	Ø×$Ñ$ ]Ó3×<Ñ<¸VÌyÏÉ×OiÑOiÐ<ÓjˆáØ—K‘K ¨¨hÓ7ˆEà�{‰{˜1‹~×$Ñ$Ó&ˆØò 	9ˆCØ—;‘;“=×$Ñ$ _°aÓ8Ò8ð	9àøÜò Ü�‰ÐAÀ)ÀÈBÈqÈcÐRÔSÜûðús   ‚B8B= Â;B= Â=	C+ÃC&Ã&C+c                 ó@  — 	 t         j                  d«      j                  | «      }|j                  «       }|j                  r"|j                  «       j                  dd«      }|S t        | «      S # t        $ r%}t        j                  d| › d|› �«       Y d}~yd}~ww xY w)zƒ
    Retrieves the total AI response count from the global counter.
    Triggers a sync if the counter document doesn't exist.
    r=   r>   r   z*Error getting total AI response count for rC   N)
r   r   r   r   r   r   Úsync_ai_responses_from_historyr   r   r   )r	   rG   r   rY   r    s        r!   Úget_total_ai_responses_countrk   Ù   sŠ   € ð
Ü—M‘M .Ó1×:Ñ:¸9ÓEˆ	Ø�m‰m‹oˆØ�:Š:Ø—K‘K“M×%Ñ%Ð&:¸AÓ>ˆEØˆLô 2°)Ó<Ð<øÜò Ü�‰ÐAÀ)ÀÈBÈqÈcÐRÔSÜûðús   ‚A!A/ Á$
A/ Á/	BÁ8BÂBc                 ó.  — 	 t         j                  d| › d�«       t        j                  d«      j	                  dd| «      }|j                  «       }d}|D ]7  }|j                  «       j                  dg «      }|t        d„ |D «       «      z  }Œ9 t        j                  d	«      j                  | «      }|j                  d
|id¬«       t         j                  d| › d|› d�«       |S # t        $ r%}t         j                  d| › d|› �«       Y d}~yd}~ww xY w)zž
    Scans all historical threads to calculate and initialize the global counter.
    This handles "past ones" by calculating the sum once and saving it.
    z8Initializing/Syncing AI response count from history for ú...r   r	   rT   r   r   c              3   óJ   K  — | ]  }|j                  d «      dk(  sŒd–— Œ y­w)r#   r/   r?   N)r   )Ú.0Úms     r!   ú	<genexpr>z1sync_ai_responses_from_history.<locals>.<genexpr>ø   s   è ø€ ÒS Q°a·e±e¸F³mÀ{Ó6RœqÑSùs   ‚#œ#r=   r>   Tr,   zSync complete for rC   z responses found.zError syncing AI responses for N)r   r4   r   r   rM   rN   r   r   Úsumr   r3   r   r   )r	   rX   rP   Útotal_countr   r   rG   r    s           r!   rj   rj   ë   s  € ð
Ü�‰ÐNÈyÈkÐY\Ð]Ô^Ü—m‘m NÓ3×9Ñ9¸+ÀtÈYÓWˆØ×!Ñ!Ó#ˆàˆØò 	TˆCØ—{‘{“}×(Ñ(¨°RÓ8ˆHØœ3ÑS¨(ÔSÓSÑS‰Kð	Tô
 —M‘M .Ó1×:Ñ:¸9ÓEˆ	Ø�‰Ð+¨[Ð9ÀˆÔFÜ�‰Ð(¨¨°2°k°]ÐBSÐTÔUØÐøÜò Ü�‰Ð6°y°kÀÀAÀ3ÐGÔHÜûðús   ‚C#C& Ã&	DÃ/DÄDc                 ó$  — 	 t         j                  d«      j                  dd| «      }|r|j                  dd|«      }|j                  «       }g }|D ]ö  }|j	                  «       }|j                  d«      xs |j                  d«      }|sŒ:|j                  d«      r|j                  dg «      d   nd	}|j                  ||j                  d«      |j                  d
d«      |j                  dd«      |j                  dd«      |j                  dd«      |j                  d«      |j                  d«      |r|d   d	d dz   nddœ	«       Œø |j                  d„ d¬«       |S # t        $ r&}	t        j                  d|	› �d¬«       g cY d	}	~	S d	}	~	ww xY w)z[
    Retrieves all chat threads for a specific tenant, optionally filtered by user_id.
    r   r	   rT   r   r
   Ú
session_idr   éÿÿÿÿNr%   Ú	Anonymousr&   Ú Útakeover_activeFÚintervention_requestedr+   r*   r(   éd   rm   )	r
   r   r%   r&   ry   rz   r+   r*   Úlast_message_previewc                 ó   — | d   xs dS )Nr*   rx   © )Úxs    r!   ú<lambda>z!get_all_threads.<locals>.<lambda>#  s   €  1 \¡?Ò#8°b€ ó    T)ÚkeyÚreversez&Error listing threads from Firestore: r   )r   r   rM   rN   r   r   ÚappendÚsortr   r   r   )
r	   r   rX   rP   Úthreadsr   r   Úthread_id_valÚlast_msgr    s
             r!   Úget_all_threadsr‰     s}  € ð Ü—m‘m NÓ3×9Ñ9¸+ÀtÈYÓWˆÙØ%×+Ñ+¨I°t¸WÓEˆKà×!Ñ!Ó#ˆàˆØò 	ˆCØ—;‘;“=ˆDØ ŸH™H [Ó1ÒK°T·X±X¸lÓ5KˆMÙ Øà7;·x±xÀ
Ô7K�t—x‘x 
¨BÓ/°Ò3ÐQUˆHØ�N‰NØ*ØŸ8™8 IÓ.Ø!ŸX™X k°;Ó?ØŸ8™8 I¨rÓ2Ø#'§8¡8Ð,=¸uÓ#EØ*.¯(©(Ð3KÈUÓ*SØ"Ÿh™h |Ó4Ø"Ÿh™h |Ó4ÙMU¨°Ñ(;¸D¸SÐ(AÀEÒ(IÐ[]ñ
õ 
ð	ð( 	�‰Ñ8À$ˆÔGØˆøÜò Ü�‰Ð=¸a¸SÐAÈDˆÔQØ�	ûðús   ‚EE  Å 	FÅ)F
ÆFÆ
FÚstatusc                 ót  — 	 | › d|› �}t         j                  d«      j                  |«      }|t        j                  «       j                  «       dœ}|du rd|d<   |j                  |d¬«       t        j                  d|› d	|› �«       y# t        $ r$}t        j                  d
|› �d¬«       Y d}~yd}~ww xY w)zB
    Toggles the human takeover status for a specific thread.
    r   r   )ry   r*   TFrz   r,   zUpdated takeover_active to ú for z,Error setting takeover status in Firestore: r   N©r   r   r   r   r0   r1   r3   r   r4   r   r   ©r	   r
   rŠ   r   r   r   r8   r    s           r!   Úset_takeover_statusr�   )  s¼   € ðØ�;˜a 	˜{Ð+ˆÜ—-‘- Ó/×8Ñ8¸Ó@ˆð  &Ü"Ÿ/™/Ó+×5Ñ5Ó7ñ
ˆð
 �T‰>Ø49ˆKÐ0Ñ1à�‰�K tˆÔ,Ü�‰Ð1°&°¸¸v¸hÐGÔHØøÜò Ü�‰ÐCÀAÀ3ÐGÐRVˆÔWÜûðúó   ‚BB
 Â
	B7ÂB2Â2B7c                 ó4  — 	 | › d|› �}t         j                  d«      j                  |«      }|j                  «       }|j                  r |j                  «       j                  dd«      S y# t        $ r$}t        j                  d|› �d¬«       Y d}~yd}~ww xY w)	z=
    Retrieves the current takeover status for a thread.
    r   r   ry   Fz.Error getting takeover status from Firestore: Tr   N)	r   r   r   r   r   r   r   r   r   )r	   r
   r   r   r   r   r    s          r!   Úget_takeover_statusr’   @  s‹   € ð	Ø�;˜a 	˜{Ð+ˆÜ—-‘- Ó/×8Ñ8¸Ó@ˆØ�k‰k‹mˆØ�:Š:Ø—;‘;“=×$Ñ$Ð%6¸Ó>Ð>ØøÜò Ü�‰ÐEÀaÀSÐIÐTXˆÔYÜûðús   ‚A&A* Á*	BÁ3BÂBc                 ót  — 	 | › d|› �}t         j                  d«      j                  |«      }| ||t        j                  «       j                  «       dœ}|r||d<   |j                  |d¬«       t        j                  d|› d|› �«       y# t        $ r$}t        j                  d	|› �d¬
«       Y d}~yd}~ww xY w)zJ
    Toggles the intervention_requested status for a specific thread.
    r   r   )r	   r
   rz   r*   r   Tr,   z"Updated intervention_requested to rŒ   z0Error setting intervention status in Firestore: r   NFr�   rŽ   s           r!   Úset_intervention_statusr”   O  s½   € ðØ�;˜a 	˜{Ð+ˆÜ—-‘- Ó/×8Ñ8¸Ó@ˆð #Ø"Ø&,Ü"Ÿ/™/Ó+×5Ñ5Ó7ñ	
ˆñ Ø%,ˆK˜	Ñ"à�‰�K tˆÔ,Ü�‰Ð8¸¸ÀÀfÀXÐNÔOØøÜò Ü�‰ÐGÈÀsÐKÐVZˆÔ[Üûðúr�   )ÚdefaultN)r•   NNN)NN)N)!Úgoogle.cloudr   Úapp.core.configr   ÚloggingÚosr   Ú	getLoggerÚ__name__r   r   ÚenvironÚClientÚFIRESTORE_PROJECTÚFIRESTORE_DATABASEr   Ústrr"   r:   r5   rR   r[   r]   Úintra   rh   rk   rj   r‰   Úboolr�   r’   r”   r~   r�   r!   ú<module>r£      s´  ðÝ "Ý $Û Û 	Ý à	ˆ×	Ñ	˜8Ó	$€ð 08×/VÑ/V€‡
�
Ð+Ñ ,à€Y×ÑØ×&Ñ&Ø×(Ñ(ô€ñ
 ð °ð È#ó ñ(+T ð +T¨Cð +T¸#ð +TÈ#ð +TÐfið +TÐ{~ð +Tð  QTó +TðZS¨3ó Sñ0¨3ð ¸Cð ÐRUó ñ,¨Só ñ8¨Só ð*M Cð M°Só Mñ*¨3ð ¸#ó ð&¨Có ð$¨có ñ0$˜sð $¨Só $ñL 3ð °3ð Àð Èsó ñ. 3ð °3ð Àó ñ sð °sð ÀDð ÐSVô r�   