o
    ]Ni                     @   s4   d dl Z d dlZd dlZeeZG dd dZdS )    Nc                   @   sH   e Zd ZdZdd Zdd Zdd Zdd	 Zd
d Zdd Z	dd Z
dS )TokenManagerz?
    Manages Keepa API tokens, rate limiting, and refills.
    c                 C   sL   || _ d| _d| _d| _d| _d| _t | j | _t | _t	
d d S )N   <   2   d   i,  z5TokenManager initialized without a blocking API call.)api_keyREFILL_RATE_PER_MINUTEMIN_TIME_BETWEEN_CALLS_SECONDSMIN_TOKEN_THRESHOLDtokens
max_tokenstimelast_api_call_timestamplast_refill_timestamploggerinfo)selfr    r   4/var/www/agentarbitrage/keepa_deals/token_manager.py__init__   s   
zTokenManager.__init__c                 C   sr   t   }|| j }|dkr5|d | j }|dkr7t| j| j| | _|| _td|dd| jd dS dS dS )zZ
        Calculates and adds tokens that have been refilled since the last check.
        r   r   z	Refilled .2fz tokens. Current tokens: N)r   r   r   minr   r   r   debug)r   nowseconds_elapsedrefill_amountr   r   r   _refill_tokens   s   
 zTokenManager._refill_tokensc                 C   s   |    | j|kS )zu
        Checks if there are enough tokens for a call without waiting.
        Also triggers a refill check.
        )r   r   )r   estimated_costr   r   r   has_enough_tokens*   s   
zTokenManager.has_enough_tokensc                 C   sX  t   }|| j }|| jk r"| j| }td|dd t | |   d}| jdkrKd| j }t	|| j
 d }td| jdd| d	 n1| j| jk r{| jd
 }|| j }t	|| j
 d }td| j d| jdd| d| d	 n	 |dkr| j
dkrt | |   ntd t d |   td| d| jd dS )a  
        Checks if an API call can be made and waits if necessary. This method
        implements a "controlled deficit" strategy based on Keepa API behavior.

        Keepa API Rule: A call can be made as long as the token balance is positive.
        The call is allowed to drive the balance into a negative value.

        Our Strategy (Optimized):
        1.  **Hard Stop at Zero:** If tokens are <= 0, we must wait until they
            refill to a positive number.
        2.  **Aggressive Consumption:** If `current_tokens` > `MIN_TOKEN_THRESHOLD` (50),
            we allow the call immediately, even if it creates a deficit. We do NOT wait
            for a full bucket. This prevents "starvation" when an upserter task is
            simultaneously consuming the refill trickle.
        3.  **Smart Recovery:** If `current_tokens` drops below the threshold, we wait
            only until it recovers to (`threshold + buffer`), not `max_tokens`.
        zRate limit: Pausing for r   z	 seconds.r   
   r   zZero or negative tokens. Have: z. Waiting for z! seconds to recover to 10 tokens.r   zLow tokens (Below Threshold z	). Have: z seconds to recover to .zOZero refill rate, cannot wait for tokens. Pausing for 15 minutes as a fallback.i  z1Permission granted for API call. Estimated cost: z. Current tokens: N)r   r   r	   r   r   sleepr   r   mathceilr   warningr
   error)r   r   r   time_since_last_callwait_durationwait_time_secondstokens_neededrecovery_targetr   r   r   request_permission_for_call2   sH   







	




z(TokenManager.request_permission_for_callc                 C   sL   ddl m} td || j}|rd|v r| |d  dS td dS )z}
        Authoritatively fetches the current token status from the Keepa API
        and updates the internal state.
           )get_token_statusz5Performing authoritative token sync with Keepa API...
tokensLeftz;Failed to sync tokens. API did not return valid token data.N)	keepa_apir-   r   r   r   _sync_tokens_from_responser%   )r   r-   status_datar   r   r   sync_tokensu   s   

zTokenManager.sync_tokensc                 C   s:   | j }t|| _ t | _td|dd| j d dS )zZ
        Authoritatively sets the token count from a provided API response value.
        zIToken count authoritatively synced from API response. Previous estimate: r   z, New value: N)r   floatr   r   r   r   )r   tokens_left_from_apiold_token_countr   r   r   r0      s   

z'TokenManager._sync_tokens_from_responsec                 C   s   t   | _| | dS )zk
        Updates the token count and timestamp after an API call using the authoritative response.
        N)r   r   r0   )r   r4   r   r   r   update_after_call   s   
zTokenManager.update_after_callN)__name__
__module____qualname____doc__r   r   r   r+   r2   r0   r6   r   r   r   r   r      s    Cr   )r   r"   logging	getLoggerr7   r   r   r   r   r   r   <module>   s
    
