o
    LeiB                  
   @   sd  d dl Z d dlZd dlZd dlZd dlZd dlmZmZ eeZ	e
dejejejeddZdZejejedZejejejeddZd	d
 Zdd Zdd Zdd Zd3defddZdedefddZdd Zdd ZdefddZdedB fdd Zd!d" Zd#d$ Z d%d& Z!d'ed(efd)d*Z"d+d, Z#d-d. Z$d/d0 Z%d1d2 Z&dS )4    N)datetimetimezoneDATABASE_URLz..zdeals.dbdealszheaders.jsonzwatermark.jsonc                 C   sF   |  dd dd dd} tdd| } tdd| } | d} | S )	z4Sanitizes a string to be a valid SQLite column name.%Percent&and._z[^a-zA-Z0-9_]z__+)replaceresubstrip)name r   //var/www/agentarbitrage/keepa_deals/db_utils.pysanitize_col_name   s
   
r   c                 C   s$   |  d| d dd |  D S )z+Fetches the column names for a given table.zPRAGMA table_info()c                 S   s   g | ]}|d  qS    r   .0rowr   r   r   
<listcomp>       z%get_table_columns.<locals>.<listcomp>)executefetchall)cursor
table_namer   r   r   get_table_columns   s   r    c              
   C   s   zF|  d| d dd |  D }|D ].}|d }|  d| d |  }t|dkrC|d d d	krCtd
| d  W dS qW dS  tjyb } ztd|  W Y d}~dS d}~ww )z3Checks if a unique index exists on the ASIN column.zPRAGMA index_list('z')c                 S   s   g | ]
}|d  dkr|qS )   r   r   r   r   r   r   r   "   s    z,has_unique_index_on_asin.<locals>.<listcomp>r   zPRAGMA index_info('r   r!   ASINzFound existing unique index 'z
' on ASIN.TFz!Error checking for unique index: N)r   r   lenloggerinfosqlite3Errorerror)r   r   indexesindex
index_namecolumnser   r   r   has_unique_index_on_asin   s"   r.   c               
   C   s   t dt d z'tt} |  }|d |   W d   W dS 1 s)w   Y  W dS  tjyH } zt j	d| dd  d}~ww )z{
    Ensures the 'system_state' table exists.
    Schema: key (TEXT PRIMARY KEY), value (TEXT), updated_at (TIMESTAMP)
    z2Database check: Ensuring table 'system_state' at '	' exists.z
                CREATE TABLE IF NOT EXISTS system_state (
                    key TEXT PRIMARY KEY,
                    value TEXT,
                    updated_at TIMESTAMP
                )
            Nz#Error creating system_state table: Texc_info)
r$   r%   DB_PATHr&   connectr   r   commitr'   r(   connr   r-   r   r   r   'create_system_state_table_if_not_exists/   s   

&
r7   keyc              
   C   s   z8t t(}| }|d| f | }|r$|d W  d   W S |W  d   W S 1 s1w   Y  W dS  t jyZ } ztjd|  d| dd |W  Y d}~S d}~ww )z.Retrieves a value from the system_state table.z,SELECT value FROM system_state WHERE key = ?r   Nz'Error retrieving system state for key '': Tr0   )	r&   r3   r2   r   r   fetchoner'   r$   r(   )r8   defaultr6   r   r   r-   r   r   r   get_system_stateD   s   (r<   valuec              
   C   s   z@t t0}| }ttj }|	d| t
||f |  td|  d|  W d   W dS 1 s9w   Y  W dS  t jya } ztjd|  d| dd W Y d}~dS d}~ww )	z'Sets a value in the system_state table.z{
                INSERT OR REPLACE INTO system_state (key, value, updated_at)
                VALUES (?, ?, ?)
            zSystem state updated: =Nz$Error setting system state for key 'r9   Tr0   )r&   r3   r2   r   r   nowr   utc	isoformatr   strr4   r$   r%   r'   r(   )r8   r=   r6   r   
updated_atr-   r   r   r   set_system_stateR   s   &	(rD   c               
   C   s  t   tdt dt d ztt} |  }|dt d |	 s=t
dt d t  	 W d   W dS tdt d	 t|t}d
|vr]td |dt d d|vrotd |dt d d|vrtd |dt d d|vrtd |dt d d|vrtd |dt d t|tst
d |   td W d   W dS 1 sw   Y  W dS  tjtfy } ztjd| dd  d}~ww )zt
    Ensures the 'deals' table exists and has the correct schema.
    Also ensures 'system_state' table exists.
     Database check: Ensuring table '' at '' is correctly configured.<SELECT name FROM sqlite_master WHERE type='table' AND name=''Table 'z8' not found. Calling recreate_deals_table() to build it.Nz'' exists. Verifying schema and indexes.last_seen_utczAdding 'last_seen_utc' column.zALTER TABLE z# ADD COLUMN last_seen_utc TIMESTAMPsourcezAdding 'source' column.z ADD COLUMN source TEXTDropszAdding 'Drops' column.z ADD COLUMN Drops INTEGEROfferszAdding 'Offers' column.z ADD COLUMN Offers TEXTAMZzAdding 'AMZ' column.z ADD COLUMN AMZ TEXTzSNo unique index found on ASIN column. This should have been created with the table.zDatabase schema check complete.z2An unexpected error occurred during schema check: Tr0   )r7   r$   r%   
TABLE_NAMEr2   r&   r3   r   r   r:   warningrecreate_deals_tabler    r.   r4   r'   	Exceptionr(   )r6   r   existing_columnsr-   r   r   r    create_deals_table_if_not_existsa   sL   







&,rU   c            
   
      s  t dt dt d ztt} |  }|dt  t dt d tt	}t
|}W d   n1 s<w   Y  g d}g }|D ]= t }d	}t fd
d|D r_d}nd v skd v skd v rmd}|dkr{|d| d qI|d| d|  qIdt dd| d}|| t dt d |dt d t d |   t d W d   W dS 1 sw   Y  W dS  tjtt
jfy }	 zt jd|	 d d!  d}	~	ww )"z
    Destroys and recreates the 'deals' table to ensure a fresh schema.
    This is the authoritative function for creating the table and should be
    used by the backfiller.
    Recreating '' table at 'z3'. This will delete all existing data in the table.DROP TABLE IF EXISTS Dropped existing '' table.N)PriceCostFeeProfitMarginzList atTEXTc                 3   s    | ]}| v V  qd S Nr   )r   keywordheaderr   r   	<genexpr>   s    z'recreate_deals_table.<locals>.<genexpr>REALRankCountrM   INTEGERr"   "z" TEXT NOT NULL UNIQUEz" zCREATE TABLE z( (id INTEGER PRIMARY KEY AUTOINCREMENT, , r   Successfully recreated 'z' table with up-to-date schema.z'CREATE UNIQUE INDEX idx_asin_unique ON z(ASIN)zCreated unique index on ASIN.z$Database schema recreation complete.z3A critical error occurred during table recreation: Tr0   )r$   r%   rP   r2   r&   r3   r   r   openHEADERS_PATHjsonloadr   anyappendjoinr4   r'   IOErrorJSONDecodeErrorr(   )
r6   r   fheadersexplicit_real_typescols_sqlsanitized_headercol_typecreate_table_sqlr-   r   rc   r   rR      sD   


&+rR   	timestampc                 C   s   t d|   td|  dS )z=Saves the given ISO 8601 timestamp to the system_state table.z Saving new watermark timestamp: watermark_isoN)r$   r%   rD   )r}   r   r   r   save_watermark   s   r   returnc               
   C   s   t d} | r| S tjtrptd z:ttd+}t	|}|
d}|r=td| d t| |W  d   W S W d   n1 sGw   Y  W n" ttjfyo } ztjdt d	| d
d W Y d}~nd}~ww td dS )z
    Loads the watermark timestamp from the system_state table.
    Migrates from 'watermark.json' if the DB entry is missing.
    r~   z0Watermark missing in DB. Checking legacy file...r
lastUpdatezFound legacy watermark: z. Migrating to DB.NzError loading watermark from z: Tr0   zBWatermark not found in DB or file. Assuming this is the first run.)r<   ospathexistsWATERMARK_PATHr$   r%   rm   ro   rp   getr   rt   ru   r(   rQ   )valrv   datar}   r-   r   r   r   load_watermark   s.   


&
r   c               
   C   s  d} t d|  dt d z_ttO}| }|d|  d | r9t d|  d 	 W d	   W d	S t d|  d
 d|  d}|| t d|  d |  W d	   W d	S 1 sfw   Y  W d	S  tj	t
fy } zt jd|  d| dd  d	}~ww )z
    Ensures the 'user_restrictions' table exists with the correct schema.
    This is a non-destructive function safe for frequent checks.
    user_restrictionsrE   rF   rG   rH   rI   rJ   z' already exists.N' not found. Creating it now.z
            CREATE TABLE a@   (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                user_id TEXT NOT NULL,
                asin TEXT NOT NULL,
                is_restricted INTEGER,
                approval_url TEXT,
                last_checked_timestamp TIMESTAMP,
                UNIQUE(user_id, asin)
            )
            Successfully created table ''.z%An unexpected error occurred during 'z' table creation: Tr0   )r$   r%   r2   r&   r3   r   r   r:   r4   r'   rS   r(   )r   r6   r   r|   r-   r   r   r   ,create_user_restrictions_table_if_not_exists   s.   	

&r   c               
   C   s   d} t d|  dt d z=tt!}| }|d|   |  t d|  d W d   n1 s8w   Y  t  t d	|  d W dS  tj	yf } zt j
d
|  d| dd  d}~ww )zd
    Destroys and recreates the 'user_restrictions' table.
    Used during a full system reset.
    r   rV   rW   z2'. This will delete all existing restriction data.rX   rY   rZ   Nrl   zError recreating 'r9   Tr0   )r$   r%   r2   r&   r3   r   r   r4   r   r'   r(   r   r6   r   r-   r   r   r    recreate_user_restrictions_table  s    r   c               
   C   s   d} t d|  dt d zSttC}| }|d|  d | sNt d|  d |d	|  d
 |  t d|  d W d   W dS W d   W dS 1 sZw   Y  W dS  tj	y| } zt j
d|  d| dd  d}~ww )z
    Ensures the 'user_credentials' table exists to persist SP-API tokens.
    Required for background tasks that cannot access Flask session.
    user_credentialsrE   rF   r/   rH   rI   rJ   r   z"
                    CREATE TABLE z (
                        user_id TEXT PRIMARY KEY,
                        refresh_token TEXT NOT NULL,
                        updated_at TIMESTAMP
                    )
                r   r   NzError creating 'z	' table: Tr0   )r$   r%   r2   r&   r3   r   r   r:   r4   r'   r(   r   r   r   r   +create_user_credentials_table_if_not_exists-  s*   
&r   user_idrefresh_tokenc                 C   sr   t t*}| }ttj }|	d| ||f |
  td|   W d   dS 1 s2w   Y  dS )z)Saves or updates user SP-API credentials.z
            INSERT OR REPLACE INTO user_credentials (user_id, refresh_token, updated_at)
            VALUES (?, ?, ?)
        zSaved credentials for user_id: N)r&   r3   r2   r   r   r?   r   r@   rA   r   r4   r$   r%   )r   r   r6   r   rC   r   r   r   save_user_credentialsG  s   "r   c               
   C   s   z/t t} t j| _|  }|d dd | D W  d   W S 1 s(w   Y  W dS  t jyN } zt	j
d| dd g W  Y d}~S d}~ww )z9Retrieves all user credentials for background processing.z3SELECT user_id, refresh_token FROM user_credentialsc                 S   s   g | ]}t |qS r   )dictr   r   r   r   r   [  r   z,get_all_user_credentials.<locals>.<listcomp>Nz#Error retrieving user credentials: Tr0   )r&   r3   r2   Rowrow_factoryr   r   r   r'   r$   r(   r5   r   r   r   get_all_user_credentialsT  s   
(r   c               
   C   s   z9t t)} |  }|dt  | }|r%|d W  d   W S 	 W d   W dS 1 s2w   Y  W dS  t jyW } ztj	d| dd W Y d}~dS d}~ww )z4Returns the total number of rows in the deals table.zSELECT COUNT(*) FROM r   NzError counting deals: Tr0   )
r&   r3   r2   r   r   rP   r:   r'   r$   r(   )r6   r   r   r-   r   r   r   get_deal_count`  s   (r   c                 C   s  | sdS t t}| }|d dd | D }| D ]S}i }| D ]\}}t|}||v r5|||< q%|sEt	d|
d d qdd	d
 | D }	ddgt| }
t| }d|	 d|
 d}||| q|  |  tdt|  d dS )zBSaves a list of deal dictionaries to the deals.db SQLite database.NzPRAGMA table_info(deals)c                 S   s   h | ]}|d  qS r   r   r   r   r   r   	<setcomp>x  r   z#save_deals_to_db.<locals>.<setcomp>zSkipping deal for ASIN r"   z% because no valid columns were found.rk   c                 S   s   g | ]}d | d qS )rj   r   )r   kr   r   r   r     s    z$save_deals_to_db.<locals>.<listcomp>?zINSERT OR REPLACE INTO deals (z
) VALUES (r   zSuccessfully saved/updated z deals to the database.)r&   r3   r2   r   r   r   itemsr   r$   rQ   r   rs   keysr#   listvaluesr4   closeloggingr%   )
deals_datar6   r   table_columnsdealsanitized_dealr   vsanitized_keyr,   placeholdersr   sqlr   r   r   save_deals_to_dbn  s0   

r   c                  C   s:   t t} |  }|d |   |   td d S )NzDELETE FROM dealszDeals table cleared.)	r&   r3   r2   r   r   r4   r   r   r%   )r6   r   r   r   r   clear_deals_table  s   

r   ra   )'r&   ro   r   r   r   r   r   	getLogger__name__r$   getenvr   rs   dirnameabspath__file__r2   rP   rn   r   r   r    r.   r7   rB   r<   rD   rU   rR   r   r   r   r   r   r   r   r   r   r   r   r   r   r   <module>   s:   
( :7%'