
    J!jR                     x    d dl Z d dlZd dlZd dlmZ d dlmZ d dlmZ  e j                  e
      Z G d d      Zy)    N)Session)settings)
OAuthTokenc                   &   e Zd Zd+defdZd ZdedefdZd Zd,d	e	d
e	de
de
dedefdZde	fdZdee	   fdZd-de	dede	de	fdZde	fdZde	fdZd.de	de	de	de	fdZde	fd Zd/d!Zd0dee	   fd"Zde	de	fd#Zd$e	fd%Zd&e	fd'Zde	d(ed)e
fd*Zy)1MeliApiServiceN
db_sessionc                 J    || _         d| _        | j                         | _        y )Nzhttps://api.mercadolibre.com)r   base_url_get_valid_tokenaccess_token)selfr   s     C/var/www/hypershopcomercio.com.br/hyper-ai/app/services/meli_api.py__init__zMeliApiService.__init__   s!    $6 113    c                 d   d}| j                   }|sddlm}  |       }d}	 |j                  t              j                  t        j                  dk(        j                         }|s*t        j                  d       	 |r|j                          yy|j                  rt        j                  j                         }|j                  }|j                  r|j                  d      }||t        j                   d	
      z   k  r:t        j#                  d       | j%                  ||      |r|j                          S S |j&                  |r|j                          S S # t(        $ r5}t        j                  d|        Y d}~|r|j                          yyd}~ww xY w# |r|j                          w w xY w)zO
        Retrieves a valid access token from DB. Refreshes if expired.
        Fr   SessionLocalTmercadolivrez=No OAuth token found in database for provider 'mercadolivre'.N)tzinfo   )minuteszToken expired. Refreshing...zError retrieving token: )r   app.core.databaser   queryr   filterproviderfirstloggererrorclose
expires_atdatetimeutcnowr   replace	timedeltainfo_refresh_tokenr   	Exception)r   local_sessiondbr   token_recordnowr    es           r   r   zMeliApiService._get_valid_token   sx   
 __6B M	88J/66z7J7Jn7\]cceL ]^& 
  &&''..0)44
$$!+!3!34!3!@Jx'9'9!'D!DDKK >?..r<@ 
   ,,
 
 	  	LL3A378
 		 
 s7   AE BE :E 	F"F:F FF F/r)   r*   c                 8   d}dt         j                  t         j                  |j                  d}	 t	        j
                  ||      }|j                          |j                         }|d   |_        |d   |_        |j                  dd      }t        j                  j                         t        j                  |      z   |_        |j                          t        j!                  d	       |d   S # t"        $ r}t        j%                  d
|         d }~ww xY w)Nz(https://api.mercadolibre.com/oauth/tokenrefresh_token)
grant_type	client_idclient_secretr.   )datar   
expires_ini`T  )secondsz&Token refreshed and saved to database.zFailed to refresh token: )r   MELI_APP_IDMELI_CLIENT_SECRETr.   requestspostraise_for_statusjsonr   getr!   r"   r$   r    commitr   r%   r'   r   )	r   r)   r*   urlr2   response
new_tokensr3   r,   s	            r   r&   zMeliApiService._refresh_token;   s    8)!--%88)77	
	}}St4H%%'!J )3>(BL%)3O)DL&#e<J&.&7&7&>&>&@8CUCU^hCi&iL#IIKKK@An-- 	LL4QC89	s   C C2 2	D;DDc                 $    dd| j                    iS )NAuthorizationzBearer )r   )r   s    r   get_headerszMeliApiService.get_headersW   s    74+<+<*=!>??r   methodendpointparams	json_datatimeoutmax_retriesc                    ddl }| j                   | }d}	t        |dz         D ]  }
	 t        j                  ||| j                         |||      }|j                  dk(  r`|
|k  r?|	|
dz   z  }t        j                  d| d| d	|
dz    d
| d	       |j                  |       t        j                  d| d| d       |j                  dk(  r3t        j                  d| d       d}| j                  }|sddlm}  |       }d}	 |j                  t              j!                  t        j"                  dk(        j%                         }|r| j'                  ||      | _        |
|k\  r[t        j+                  d| d       t        j                  ||| j                         |||      }||r|j-                          c S c S 	 |r|j-                          t        j                  d       	 |r|j-                          	 |c S  y# t.        $ r"}t        j                  d|        Y d}~?d}~ww xY w# |r|j-                          w w xY w# t        j0                  $ r_}|
|k  r6t        j                  d| d| d|	 d       |j                  |	       Y d}~zt        j                  d| d| d|         d}~ww xY w) z
        Generic request method with automatic token refresh and 429 (Rate Limit) retry.
        Endpoint should be relative, e.g. '/orders/search'
        r   N
      )headersrE   r:   rG   i  zRate limit (429) hit for z. Retrying in zs... (Attempt /)z!Rate limit (429) persisted after z retries for .i  z401 Unauthorized for z. Refreshing token...Fr   Tr   z	Retrying z4 once more after token refresh (fast mode override).zNo token to refresh.zRefresh failed: zRequest error for : zs...zRequest Error for z after z
 retries: )timer
   ranger7   requestrB   status_coder   warningsleepr   r   r   r   r   r   r   r   r   r&   r   r%   r   r'   RequestException)r   rC   rD   rE   rF   rG   rH   rQ   r=   retry_delayattemptresp
sleep_timer(   r)   r   r*   r,   s                     r   rS   zMeliApiService.requestZ   s   
 	z*[1_- 4	G3''T=M=M=OX^enx  A ##s*,%0GaK%@
)B8*N[eZfftu|  @A  vA  uB  BC  DO  CP  PQ  (R  S

:. 'HUbckbllm%no ##s*NN%:8*DY#Z[$)MBC*n)-'')xx
';'B'B:CVCVZhCh'i'o'o'q'040C0CB0UD-&+5 &izAu,v w'/'7'7TM]M]M_hnu~  IP  (Q'+ )HHJ ) % )HHJ #LL)?@ )HHJY4	L % ='7s%;<<= )HHJ )
 ,, [(NN%7zA3nU`Taae#fgJJ{+1(7;-zZ[Y\]^st   A;I&A$IB$H/IIH1H92I	H6H1,H91H66H99IIK$5J>J>>Kuser_idc                 F   d| d}ddd}g }	 | j                  d||      }|j                          |j                         }|j                  |j	                  dg              |j	                  d	i       j	                  d
      xs |j	                  d
      }|s	 |S ||d
<   )N/users/z/items/searchscand   )search_typelimitGETrE   resultspaging	scroll_id)rS   r9   r:   extendr;   )r   r\   rD   rE   itemsr>   r2   rg   s           r   get_user_itemszMeliApiService.get_user_items   s    WI]3!'#6||E8F|CH%%'==?DLL)R01 2.22;?X488KCXI #,F; r   item_idsc                 v   d}g }t        dt        |      |      D ]  }||||z    }dj                  |      }d}d|i}| j                  d||      }	|	j                  dk(  r5|	j                         }
|
D ]  }|d	   dk(  s|j                  |d
          ! zt        j                  d|	j                           |S )N   r   ,z/itemsidsrc   rd      codebodyzError fetching chunk: )	rR   lenjoinrS   rT   r:   appendr   r   )r   rk   
chunk_sizeall_detailsichunkids_strrD   rE   r>   re   ress               r   get_item_detailszMeliApiService.get_item_details   s    
q#h-4 	OAQq|,EhhuoGHW%F||E8F|CH##s*"--/" 9CFs*$++CK89  6x7K7K6LMN	O r   item_idlastunitendingc                     d| d}||d}|r||d<   	 | j                  d||      }|j                  dk(  r|j                         S y# t        $ r%}t        j                  d	| d
|        Y d}~yd}~ww xY w)z
        Fetch visits for a specific time window.
        ending: Optional 'YYYY-MM-DD'. If not provided, defaults to today/now.
        /items/z/visits/time_window)r~   r   r   rc   rd   rp   Nz&Error fetching visits time window for rP   )rS   rT   r:   r'   r   r   )	r   r}   r~   r   r   rD   rE   r>   r,   s	            r   get_visits_time_windowz%MeliApiService.get_visits_time_window   s    
 WI%89-%F8	UHVDX$$+ ' 	\\B7)2aSQR	s   2A 	A6A11A6c                     	 | j                  dd| d      }|j                  dk(  r"|j                         }|j                  dd      S y# t        $ r%}t
        j                  d| d|        Y d	}~yd	}~ww xY w)
z
        Fetches the total lifetime visits for an item.
        Reliable way: /items/{id}/visits which typically returns total.
        rc   r   z/visitsrp   total_visitsr   z Error fetching total visits for rP   N)rS   rT   r:   r;   r'   r   r   )r   r}   r>   r2   r,   s        r   get_total_visitszMeliApiService.get_total_visits   sz    
	||EWWIW+EFH##s*}}xx22 	LL;G9BqcJK	s   AA
 
	A8A33A8c                     	 | j                  dd| d      }|j                  dk(  r|j                         }|j                  dg       }t	        d |D        d      }t	        d |D        d      }dddd	}|r|j                  d
      |d<   |r?|j                  d
      |d<   |j                  d      xs |r|j                  d
      nd|d<   |S y# t
        $ r%}t        j                  d| d|        Y d}~yd}~ww xY w)zJ
        Fetches detailed pricing info to find active promotions.
        rc   r   z/pricesrp   pricesc              3   J   K   | ]  }|j                  d       dk(  s|  yw)typestandardNr;   .0ps     r   	<genexpr>z2MeliApiService.get_item_pricing.<locals>.<genexpr>   s      RqaeeFmz6Q R   ##Nc              3   J   K   | ]  }|j                  d       dk(  s|  yw)r   	promotionNr   r   s     r   r   z2MeliApiService.get_item_pricing.<locals>.<genexpr>   s     !TquuV}7S!!Tr   )original_pricepromotion_pricepriceamountr   r   regular_amountr   zError fetching pricing for rP   )rS   rT   r:   r;   nextr'   r   r   )	r   r}   r>   r2   r   r   r   r{   r,   s	            r   get_item_pricingzMeliApiService.get_item_pricing   s	   	||EWWIW+EFH##s*}}(B/ RF RTXY !TV!TVZ[	)-$QUVHLL,BS\-6]]8-DC)*,5MM:J,K,}jrPXP\P\]ePfx|C()
 	LL6wir!EF	s   CC 	C=C88C=	seller_id	date_fromdate_toc                    d}|ddd}|r||d<   |r||d<   |r||d<   g }	 | j                  d||	      }|j                  d
k7  r%t        j                  d|j                          	 |S |j	                         }	|	j                  dg       }
|j                  |
       |	j                  di       }|j                  dd      }t        |      |k\  r	 |S |j                  dd      dz   |d<   |d   dkD  r	 |S )z1
        Search orders with retry logic.
        z/orders/search	date_desc2   )sellersortrb   qzorder.date_created.fromzorder.date_created.torc   rd   rp   zOrder search returned re   rf   totalr   offset  )rS   rT   r   rU   r:   r;   rh   rs   )r   r   r}   r   r   rD   rE   ordersr>   r2   re   rf   r   s                r   
get_orderszMeliApiService.get_orders   s    $%{RH'F3K)f67gF23||E8F|CH##s*!78L8L7MNO  ==?Dhhy"-GMM'"XXh+FJJw*E6{e#U   &zz(A6;F8h$&! r   order_idc                 l    | j                  dd|       }|j                  dk(  r|j                         S y)zFetch single order by ID.rc   z/orders/rp   NrS   rT   r:   )r   r   rZ   s      r   	get_orderzMeliApiService.get_order  s5    ||EXhZ#89s"499;$6r   c                 f   | j                   }d}|sddlm}  |       }d}	 ddlm} |j                  |      j                  d      j                         }|r+|j                  r|j                  |r|j                          S S |j                  t              j                  d	      j                         }|r|j                  s|j                  rt        |j                  xs |j                        }|r||_        n|j                   |d|d
             |j                          t         j#                  d| d       ||r|j                          S S |r*t         j%                  d       	 |r|j                          yy| j'                  ddddi      }	|	r|	j(                  dk(  r|	j+                         j-                  dg       }
|
rft        |
d   j-                  d            }|r||_        n|j                   |d|d
             |j                          ||r|j                          S S t         j/                  d       	 |r|j                          yy# t0        $ r5}t         j/                  d|        Y d}~|r|j                          yyd}~ww xY w# |r|j                          w w xY w)z
        Gets the advertiser_id for Product Ads.
        Priority: DB cache -> oauth_tokens seller_id -> API call (last resort).
        For ML PADS, advertiser_id == seller_id.
        Fr   r   T)SystemConfigml_advertiser_id)keyr   )r   cache)r   valuegroupzCached advertiser_id=z from oauth_tokens.zQget_advertiser_id: Fast mode enabled and no cache/token found. Skipping API call.Nrc   z/advertising/advertisers
product_idPADSrd   rp   advertisersadvertiser_idz2Could not determine advertiser_id from any source.zError getting advertiser_id: )r   r   r   app.models.system_configr   r   	filter_byr   r   r   r   r   r\   straddr<   r   r%   rU   rS   rT   r:   r;   r   r'   )r   fastr)   r(   r   r   cachedtokenr   r>   r   r,   s               r   get_advertiser_idz MeliApiService.get_advertiser_id  sY    __6B M-	= XXl+55:L5MSSUF&,,||L 
 G HHZ(22N2KQQSE%//U]] #EOO$Du}} E#0FLFF<,>m[bcd		3M?BUVW$2 
 - rs( 
 % ||E+E|]cNd|eHH00C7&mmo11-D$'A(:(:?(K$LM'4|0B-_fghIIK( 
  LLMN
 
 	  	LL8<=
 		 
 sD   AI B>I I BI 0I 	J#J;J JJ J0c                 P   | j                  |      }|sg S d| d}t        |d      r|j                  d      n|}t        |d      r|j                  d      n|}||ddd}	g }
d	}|rd
nd}|rd	nd}	 	 ||	d<   | j                  d||	||      }|r|j                  dk7  rn{|j                         }|j                  dg       }|
j                  |       |j                  di       }t        |
      |j                  dd	      k\  s|sn|t        |      z  }|dkD  rng }|rt        |      nd}|
D ]  }|r|j                  d      |v s|j                  di       }|j                  |j                  d      t        |j                  dd	      xs d	      t        |j                  dd	      xs d	      t        |j                  dd	      xs d	      t        |j                  dd	      xs d	      d        |S # t        $ r$}t        j                  d|        g cY d}~S d}~ww xY w)z
        Fetches Product Ads performance metrics for items.
        If fast=True, uses a very short timeout and 0 retries (ideal for dashboard load).
        )r   z/advertising/MLB/advertisers/z/product_ads/ads/searchstrftimez%Y-%m-%dz'clicks,prints,cost,cpc,acos,roas,amountr`   )r   r   metricsrb   r   r         r   rc   )rE   rG   rH   rp   re   rf   r   r   Nr}   r   costr   clicksprints)r}   r   r   r   r   z Error fetching ads performance: )r   hasattrr   rS   rT   r:   r;   rh   rs   setru   floatintr'   r   r   )r   rk   r   r   r   r   rD   d_fromd_torE   all_resultsr   request_timeoutrequest_retriesr>   r2   re   rf   filtereditem_ids_setadmr,   s                          r   get_ads_performancez"MeliApiService.get_ads_performanceW  s;   
 ..D.9Ri2=/AXY3:9j3Q##J/W`/6w
/Kw
+QX  D@
   $!#!	#)x <<xP_m|<}8#7#73#>}}((9b1""7+(B/{#vzz'1'==We#g,&D=%  H,43x=$L! 	#rvvi'8L'Hy"-AOO#%66)#4 %aeeFA&6&;! <"'h(:(?a"@"%aeeHa&8&=A">"%aeeHa&8&=A">% 	 O 	LL;A3?@I	s&   3CG8 B/G8 8	H%H H% H%c                 L   	 | j                  dd| dd|i      }|j                  dk(  rIt        |j                         j	                  di       j	                  di       j	                  d	d
            S y
# t
        $ r%}t        j                  d| d|        Y d}~y
d}~ww xY w)z<
        Fetches free shipping cost for the seller.
        rc   r^   z/shipping_options/freer}   rd   rp   coverageall_country	list_costg        z!Error fetching shipping cost for rP   N)rS   rT   r   r:   r;   r'   r   r   )r   r}   r   r>   r,   s        r   get_shipping_costz MeliApiService.get_shipping_cost  s    	||EWYK?U+V`ikr_s|tH##s*X]]_00R@DD]TVW[[\gilmnn 	LL<WIRsKL	s   A1A5 5	B#>BB#shipment_idc                 l    | j                  dd|       }|j                  dk(  r|j                         S y)zFetch shipment details.rc   z/shipments/rp   Nr   )r   r   rZ   s      r   get_shipmentzMeliApiService.get_shipment  s5    ||E[#>?s"499;$6r   inventory_idc                 :   	 | j                  dd| d      }|j                  dk(  r|j                         S |j                  dv ryt        j	                  d| d|j                          y# t
        $ r%}t        j                  d	| d|        Y d}~yd}~ww xY w)
z
        Fetch detailed fulfillment stock, including 'transfer' (incoming) status.
        Endpoint: /inventories/{inventory_id}/stock/fulfillment
        rc   z/inventories/z/stock/fulfillmentrp   )i  i  Nz#Fulfillment stock fetch failed for rP   z%Error fetching fulfillment stock for )rS   rT   r:   r   rU   r'   r   )r   r   rZ   r,   s       r   get_fulfillment_stockz$MeliApiService.get_fulfillment_stock  s    
	LL-~EW(XYT  C'		#""j0"El^SUVZVfVfUg hi 	\\A,rRSQTUV	s"   4A, A, %A, ,	B5BB	new_pricereturnc                    ddl }|j                  dd      j                         dk(  }|sdddd	d
S dd|dd}	 | j                  dd|       }|r3|j                  dk(  r$|j                         }|j                  d      |d<   | j                  dd| d|i      }|j                  dk(  r(d|d<   t        j                  d| d|d    d|        |S 	 |j                         }	|	j                  dg       }
|
rY|
D cg c]9  }|j                  dd       d|j                  d|j                  dd             ; }}ddj                  |       }n|	j                  d|j                  dd       }||d!<   t        j                  d"| d|        	 |S c c}w #  d |j                   d|j                  dd  }Y KxY w# t        $ r4}t        |      |d!<   t        j                  d#| d|        Y d}~|S d}~ww xY w)$aD  
        Updates the price of an item on Mercado Livre.
        
        Args:
            item_id: The MLB item ID (e.g., 'MLB1234567890')
            new_price: The new price in BRL
            
        Returns:
            dict with 'success': bool, 'old_price': float|None, 'new_price': float, 'error': str|None
        r   NML_PRICE_WRITE_ENABLEDfalsetrueFlockedPRICE_WRITE_LOCKEDu<   Escrita de preço no Mercado Livre bloqueada por segurança.)successstatusr   message)r   	old_pricer   r   rc   r   rp   r   r   PUT)rF   Tr   zPrice updated for rP   z -> causerq   unknownr   r    z
ML Error: z; zAPI returned r   zFailed to update price for zException updating price for )osgetenvlowerrS   rT   r:   r;   r   r%   rt   textr   r'   r   )r   r}   r   r   write_enabledresultcurrent_respcurrent_dataupdate_resp
error_datacausesc
cause_msgs	error_msgr,   s                  r   update_item_pricez MeliApiService.update_item_price  sW    			":GDJJLPVV "-Y	  "	
	I<<	/BCL 8 8C ?+002&2&6&6w&?{# ,,uy.AgW`Ma,bK&&#-$(y!0	F;<O;PPTU^T_`a* %	d!,!1!1!3J'^^GR8Ftz%{opvy)A(B"QUU9VWV[V[\bdfVgEhDi&j%{
%{&0:1F0G$H	$.NN9k>N>NtPS>T$U	 #,w:7)2i[QR  &|d"/0G0G/H;K[K[\`]`KaJb cI
  	I!!fF7OLL8	A3GHH		IsB   BF> (F <>F:6F 0 F> F !F;9F> >	G;)G66G;)N)NNr   r   )r   dayN)NNN)F)NNNF)__name__
__module____qualname__r   r   r   r   r&   rB   r   dictr   rS   rj   listr|   r   r   r   r   r   r   r   r   r   r   r   r    r   r   r   r   
   sD   47 4(T 
 8@>c >S >$ >RV >hk >  @C >@c "c &c   ^a &  0C #  ^a :# :x8DI 8t   # &= = =4 =r   r   )loggingr7   r!   sqlalchemy.ormr   app.core.configr   app.models.oauth_tokenr   	getLoggerr  r   r   r  r   r   <module>r     s5       " $ -			8	$j jr   