
    <j!                         d Z ddlZddlZddlmZmZ ddlmZ ddlm	Z	m
Z
mZmZ ddlZ ej                  e      Z G d d      Zd Zed	k(  r ej&                   e              yy)
z
FX service for TripTalley.
Fetches historical exchange rates from Frankfurter with business-day fallback.
Rates are cached in the database by date, from_currency, to_currency.
    N)date	timedelta)Decimal)DictListOptionalTuplec                       e Zd ZdZdZd Z	 ddedededede	f
d	Z
deded
ede	fdZdedededee	   fdZdededede	ddf
dZdedededee	   fdZ	 ddededeee	f   fdZy)	FXServicea  
    Historical FX rate service using Frankfurter API with caching.
    
    Frankfurter API: https://www.frankfurter.dev/
    Supports: 34 currencies, historical rates, business day lookup
    
    Strategy:
    1. Try to get rate from database cache
    2. If not found, fetch from Frankfurter API
    3. Cache the result for future use
    4. Use business-day fallback: if rate not found for exact date, look backwards
    zhttps://api.frankfurter.dev/v1c                     || _         y)z|
        Initialize FX service.
        
        Args:
            db_session: SQLAlchemy session for caching rates
        N)db)self
db_sessions     ;/srv/projects/triptalley/backend/app/services/fx_service.py__init__zFXService.__init__"   s         from_currencyto_currencyon_datemax_lookback_daysreturnc                 h  K   |j                         }|j                         }||k(  rt        d      S | j                  |||       d{   }||S | j                  |||       d{   }|| j	                  ||||       d{    |S t        d|dz         D ]o  }|t        |      z
  }| j                  |||       d{   }||c S | j                  |||       d{   }|Q| j	                  ||||       d{    |c S  t        d| d| d| d| d		      7 7 7 7 g7 H7 +w)
a  
        Get historical FX rate with business-day fallback.
        
        Args:
            from_currency: Source currency (e.g., "USD")
            to_currency: Target currency (e.g., "EUR")
            on_date: The date to get rate for
            max_lookback_days: Maximum days to look back for fallback
            
        Returns:
            FX rate as Decimal
            
        Raises:
            ValueError: If no rate found within lookback period
        z1.0N   )dayszNo FX rate found for    →z on or near z (looked back z days))upperr   _get_cached_rate_fetch_rate_cache_rateranger   
ValueError)	r   r   r   r   r   cached_raterate	days_backfallback_dates	            r   get_ratezFXService.get_rate+   s    . &++-!'')K'5>! !11-gVV" %%m['JJ""=+wMMMK q"3a"78 	I#iY&??M !% 5 5{M! K &"" ))-mTTD&&!;t   !	$ #M?#k] C!.1B0C6K
 	
; W
 KM Usr   AD2	D&
D2(D()D2D*=D2D, D2%D.&D2-D2D0"D2(D2*D2,D2.D20D2spent_onc                 D   K   | j                  |||       d{   S 7 w)a  
        Get FX rate for an expense, using spent_on date.
        
        This is a convenience wrapper that doesn't require max_lookback_days.
        
        Args:
            from_currency: Expense currency
            to_currency: Target currency (settlement or display)
            spent_on: Date expense was spent
            
        Returns:
            FX rate as Decimal
        N)r&   )r   r   r   r'   s       r   get_rate_for_expensezFXService.get_rate_for_expensek   s!     & ]]=+xHHHHs     c                    K   yw)zk
        Get rate from database cache.
        
        Returns Decimal rate or None if not found.
        N )r   r   r   r   s       r   r   zFXService._get_cached_rate   s      s   r#   Nc           
      N   K   t         j                  d| d| d| d|        yw)z/
        Cache a rate in the database.
        zCached FX rate: r    on z = N)loggerdebug)r   r   r   r   r#   s        r   r   zFXService._cache_rate   s0      	}oST'#dVT	
s   #%c           
      L  K   | j                    d|j                  d       }||d}t        j                         4 d{   }	 |j	                  ||d       d{   }|j
                  dk(  r	 ddd      d{    y|j                          |j                         }|j	                  di       }	||	v r(t        t        |	|               cddd      d{    S t        j                  d	| d
|        	 ddd      d{    y7 7 7 7 87 # t        j                  $ r2 t        j                  d| d| d|        Y ddd      d{  7   yt        j                  $ r}
t        j                  d|
         d}
~
ww xY w# 1 d{  7  sw Y   yxY ww)z
        Fetch rate from Frankfurter API.
        
        Returns Decimal rate or None if API returns 404 (no rate for date).
        Raises other HTTP errors.
        /%Y-%m-%dfromtoN      $@paramstimeouti  rateszFrankfurter API missing z in response: zTimeout fetching FX rate for r   r-   zHTTP error fetching FX rate: )FRANKFURTER_APIstrftimehttpxAsyncClientgetstatus_coderaise_for_statusjsonr   strr.   warningTimeoutException	HTTPErrorerror)r   r   r   r   urlr8   clientresponsedatar:   es              r   r   zFXService._fetch_rate   s     %%&a(8(8(D'EF "

 $$&  	  	&!'C!MM''3. 	  	  	 ))+}} "-%'"3u['9#:;! 	  	  	$ .{m>$P + 	  	  	M 	  	  	. )) 3M?#k]RVW^V_` 7 	  	  	8 ?? 3A37 	9 	  	  	s   >F$ DF$FDDD2F$=D>F$ADF$DF$!D=F$D	F$DF$F$F$1F	F
F$EF$F.FFFF!FF!F$base_currencyc                   K   | j                    d|j                  d       }|dj                  g d      d}t        j                         4 d{   }|j                  ||d       d{   }|j                          |j                         }|j                  d	i       }|j                         D 	
ci c]  \  }	}
|	t        t        |
             c}
}	cddd      d{    S 7 7 c c}
}	w 7 # 1 d{  7  sw Y   yxY ww)
a`  
        Get all rates for a specific date relative to a base currency.
        
        This is more efficient than fetching individual rates.
        
        Args:
            on_date: The date to get rates for
            base_currency: Base currency for rates
            
        Returns:
            Dict mapping currency codes to rates
        r1   r2   ,)EURGBPJPYAUDCADCHFCNYSEKNOKDKKNZDMXNSGDHKDKRWINRBRLZARRUBTRYBHDKWDOMRJODr3   Nr6   r7   r:   )r;   r<   joinr=   r>   r?   rA   rB   itemsr   rC   )r   r   rM   rH   r8   rI   rJ   rK   r:   kvs              r   get_rates_for_datezFXService.get_rates_for_date   s     " %%&a(8(8(D'EF "((  
 $$& 	B 	B&#ZZFDZIIH%%'==?DHHWb)E38;;=A41aAws1v&A	B 	B 	BI B	B 	B 	B 	Bsm   ADC+DC7-C-.A	C77C/C7D%C5&D-C7/C75D7D	=D >D	D)   )USD)__name__
__module____qualname____doc__r;   r   rC   r   intr   r&   r)   r   r   r   r   r   rl   r+   r   r   r   r      sG    7O "#>
>
 >
 	>

 >
 
>
@II I 	I
 
I*  	
 
'	

 
 	

 
 

 44 4 	4
 
'	4r ##B#B #B 
c7l		#Br   r   c            	      F  K   t        d      } | j                  ddt        ddd             d{   }t        d|        	 | j                  dd	t        ddd             d{   }t        d
|        y7 H7 # t        $ r}t        d|        Y d}~yd}~ww xY ww)zTest the FX service.N)r   rn   rP   i  r      u   USD→EUR on 2024-01-15: rd   u$   USD→BHD on 2024-01-01 (New Year): zFallback result: )r   r&   r   printr!   )servicer#   rate_bhdrL   s       r   mainry      s      4(G !!%T1b0ABBD	%dV
,-' ))%T1a8HII4XJ?@ C
 J '!!%&&'sK   .B!A9B!!A= %A;&A= 8B!;A= =	BBB!BB!__main__)rr   asynciologgingdatetimer   r   decimalr   typingr   r   r   r	   r=   	getLoggerro   r.   r   ry   runr+   r   r   <module>r      sf      $  . . 			8	$fB fBT'" zGKK r   