o
    |g>                     @   s<  d dl Z d dlZd dlZd dlmZmZmZmZ d dlZ	d dl
mZ d dlmZ d dlmZ d dlmZmZ d dlmZ d dlmZmZ e  ed	Ze ZG d
d dZG dd dZdeeeef  defddZdeeeef  dee fddZddeeeef  de ddfddZ!dd Z"e#dkre"  dS dS )    N)ListDictAnyOptional)load_dotenv)Console)Table)PromptConfirm)deque)datetime	timedeltaAPOLLO_API_KEYc                   @   s>   e Zd ZdZdededefddZdd Zd	d
 Zdd ZdS )RateLimiterz+Rate limiter to enforce API request limits.minute_limithourly_limitdaily_limitc                 C   s.   || _ || _|| _t | _t | _t | _dS )z.Initialize rate limiter with specified limits.N)r   r   r   r   minute_requestshourly_requestsdaily_requests)selfr   r   r    r   C/var/www/html/XCapMarket/services/outreach/apollo_contact_finder.py__init__   s   zRateLimiter.__init__c                 C   s   t  }|tdd }| jr$| jd |k r$| j  | jr$| jd |k s|tdd }| jrD| jd |k rD| j  | jrD| jd |k s5|tdd }| jrf| jd |k rj| j  | jrh| jd |k sUdS dS dS dS )z-Remove expired requests from tracking queues.   minutesr   hoursdaysN)r   nowr   r   popleftr   r   )r   r!   
minute_agohour_agoday_agor   r   r   _clean_old_requests!   s   


$zRateLimiter._clean_old_requestsc                 C   s6  	 |    t| j| jkst| j| jkst| j| jkrg }t| j| jkr?| jd tdd t	
   }|td| t| j| jkr_| jd tdd t	
   }|td| t| j| jkr| jd tdd t	
   }|td| |rt|}|dkrtd|dd	 t| q d
S )z|
        Check if request can be made within rate limits.
        If not, wait until it's safe to make the request.
        Tr   r   r   r   r   z([yellow]Rate limit approaching. Waiting z.2fz seconds...[/yellow]N)r&   lenr   r   r   r   r   r   r   r   r!   total_secondsappendmaxminconsoleprinttimesleep)r   
wait_times	wait_timer   r   r   check_and_wait4   s<   
zRateLimiter.check_and_waitc                 C   s0   t  }| j| | j| | j| dS )zRecord a request being made.N)r   r!   r   r)   r   r   )r   r!   r   r   r   record_requesth   s   zRateLimiter.record_requestN)	__name__
__module____qualname____doc__intr   r&   r2   r3   r   r   r   r   r      s    4r   c                   @   s   e Zd ZdZdZddee fddZdeded	eee	f d
eee	f fddZ
	ddededed
eee	f fddZ	ddededed
eee	f fddZded
eee	f fddZdee d
eee	f fddZdS )ApolloClientz.Client for interacting with the Apollo.io API.zhttps://api.apollo.io/v1Napi_keyc                 C   s<   |pt | _| jstddd| jd| _tdddd| _d	S )
z*Initialize the Apollo client with API key.zVApollo API key is required. Set it as APOLLO_API_KEY in your environment or .env file.zapplication/jsonzno-cache)zContent-TypezCache-Controlz	X-API-Key   i  i  )r   r   r   N)r   r:   
ValueErrorheadersr   rate_limiter)r   r:   r   r   r   r   u   s   
zApolloClient.__init__methodendpointpayloadreturnc              	   C   s   | j   | dkrtj|| j|dd}n| dkr'tj|| j|dd}ntd| | j   |jd}|jd}|jd	}|d
urXt	
d| d| d| d |  | S )z
        Make an API request with rate limiting.

        Args:
            method: HTTP method (POST, GET, etc.)
            endpoint: API endpoint
            payload: Request payload

        Returns:
            API response as JSON
        POST
   )r=   jsontimeoutGET)r=   paramsrF   zUnsupported HTTP method: zX-RateLimit-Minute-RemainingzX-RateLimit-Hour-RemainingzX-RateLimit-Day-RemainingNz[dim]Remaining API calls: z	/minute, z/hour, z
/day[/dim])r>   r2   upperrequestspostr=   getr<   r3   r,   r-   raise_for_statusrE   )r   r?   r@   rA   responseremaining_minuteremaining_hourremaining_dayr   r   r   _make_request   s(   



zApolloClient._make_requestr      querypageper_pagec                 C   s&   | j  d}|||d}| d||S )a  
        Search for organizations in Apollo.io.

        Args:
            query: Search query for organization name
            page: Page number for pagination
            per_page: Number of results per page

        Returns:
            Dictionary containing search results
        z/organizations/search)q_organization_namerU   rV   rC   BASE_URLrR   )r   rT   rU   rV   r@   rA   r   r   r   search_organizations   s   z!ApolloClient.search_organizationsorganization_idc                 C   s(   | j  d}|g||d}| d||S )a-  
        Search for people at a specific organization.

        Args:
            organization_id: Apollo.io organization ID
            page: Page number for pagination
            per_page: Number of results per page

        Returns:
            Dictionary containing people search results
        z/people/search)organization_idsrU   rV   rC   rX   )r   r[   rU   rV   r@   rA   r   r   r   search_people   s   zApolloClient.search_people	person_idc                 C   "   | j  d}d|i}| d||S )z
        Get detailed information for a specific person.

        Args:
            person_id: Apollo.io person ID

        Returns:
            Dictionary containing enriched person data
        z/people/enrichidrC   rX   )r   r^   r@   rA   r   r   r   enrich_person      
zApolloClient.enrich_person
person_idsc                 C   r_   )z
        Get detailed information for multiple people.

        Args:
            person_ids: List of Apollo.io person IDs

        Returns:
            Dictionary containing enriched people data
        z/people/bulk_enrichidsrC   rX   )r   rc   r@   rA   r   r   r   bulk_enrich_people   rb   zApolloClient.bulk_enrich_people)N)r   rS   )r4   r5   r6   r7   rY   r   strr   r   r   rR   r8   rZ   r]   ra   r   re   r   r   r   r   r9   p   sF    


/



"r9   organizationsrB   c                 C   s  t dd}|jddd |jddd |jdd	d |jd
dd |jddd t| dD ]\}}|t||dd|dd|dd|dd q-t| 	 z!tt	
d}d|  krgt| krnn n|d W S td W n ty   td Y nw qS)z
    Display a table of organizations and prompt user to select one.

    Args:
        organizations: List of organization data

    Returns:
        Index of selected organization
    Organizationstitle#cyanstyleNamegreenWebsiteblueIndustryyellowSizemagentar   nameN/Awebsite_urlindustryemployee_countTzSelect an organization (number)z9[bold red]Invalid selection. Please try again.[/bold red]z1[bold red]Please enter a valid number.[/bold red])r   
add_column	enumerateadd_rowrf   rL   r,   r-   r8   r	   askr'   r<   )rg   tableiorg	selectionr   r   r   display_organizations   s2   







r   peoplec                    s@  t dd}|jddd |jddd |jdd	d |jd
dd |jddd t dD ](\}}|t||dd d|dd |dd|dd|dd q-t| g }	 t	d}|
 dkrqttt S zdd |dD }t fdd|D r|W S td W n ty   td  Y nw q^)!z
    Display a table of people and prompt user to select multiple.

    Args:
        people: List of people data

    Returns:
        List of indices of selected people
    Peopleri   rk   rl   rm   ro   rp   Titlerr   	Seniorityrt   
Departmentrv   r   
first_name  	last_namerj   rx   	seniority
departmentTz9Select people (comma-separated numbers, or 'all' for all)allc                 S   s   g | ]
}t | d  qS )r   )r8   strip).0xr   r   r   
<listcomp>K  s    z"display_people.<locals>.<listcomp>,c                 3   s,    | ]}d |  kot  k n  V  qdS )r   N)r'   )r   selr   r   r   	<genexpr>L  s   * z!display_people.<locals>.<genexpr>zH[bold red]Some selections are out of range. Please try again.[/bold red]zD[bold red]Please enter valid numbers separated by commas.[/bold red])r   r|   r}   r~   rf   rL   r,   r-   r	   r   lowerlistranger'   splitr   r<   )r   r   r   person
selectionsselection_inputr   r   r   display_people$  sF   





r   Fexportc                 C   sf  t dd}|jddd |jddd |jdd	d |jd
dd |jddd g }| D ]X}|di }|dd d|dd }|dd}|dd}|dd}	|dd}
|||||	|
 |r|||||	|
|di dd|dd|ddd q,t| |r|rt|}dt	t

  d}|j|dd  td!| d" d#S d#S d#S )$z
    Display detailed information for enriched people.

    Args:
        people: List of enriched people data
        export: Whether to export data to CSV
    zContact Informationri   ro   rp   rm   r   rr   Emailrt   Phonerv   LinkedInrl   r   r   r   r   r   rj   rx   emailphone_numberlinkedin_urlorganizationrw   r   r   )ro   r   r   r   r   Companyr   r   apollo_contacts_z.csvF)indexz[bold green]Data exported to [/bold green]N)r   r|   rL   r~   r)   r,   r-   pd	DataFramer8   r.   to_csv)r   r   r   data_for_exportr   person_datarw   rj   r   phonelinkedindffilenamer   r   r   display_enriched_peopleW  sF   




r   c               
      s4  t jddd zt } td}t d | |}W d   n1 s%w   Y  |dg }|s:t d W dS t|}|| }t d	|d
  d t d | 	|d }W d   n1 sdw   Y  |dg   syt d W dS t
 } fdd|D }|st d W dS dd |D }	t d* t|	dkrd| |	d di i}
|
g}n| |	}
|
dg }W d   n1 sw   Y  td}t||d W dS  tjjy } zt dt| d W Y d}~dS d}~w ty } zt dt| d W Y d}~dS d}~ww )z2Main function to run the Apollo.io contact finder.z%[bold]Apollo.io Contact Finder[/bold]rr   rm   zEnter company name to searchz7[bold green]Searching for organizations...[/bold green]Nrg   zK[bold red]No organizations found. Please try a different search.[/bold red]z[bold green]Selected: rw   r   z0[bold green]Searching for people...[/bold green]r`   r   z:[bold red]No people found at this organization.[/bold red]c                    s   g | ]} | qS r   r   )r   idxr   r   r   r         zmain.<locals>.<listcomp>z7[bold yellow]No people selected. Exiting.[/bold yellow]c                 S   s   g | ]}|d  qS )r`   r   )r   r   r   r   r   r     r   z@[bold green]Getting detailed contact information...[/bold green]r   r   r   zExport results to CSV?)r   z[bold red]API Error: z[/bold red]z[bold red]Error: )r,   r-   r9   r	   r   statusrZ   rL   r   r]   r   r'   ra   re   r
   r   rJ   
exceptionsRequestExceptionrf   	Exception)clientrT   org_resultsrg   selected_org_idxselected_orgpeople_resultsselected_people_idxselected_peoplerc   enriched_resultsenriched_peopleexport_to_csver   r   r   main  sb   




$$r   __main__)F)$rJ   osr.   typingr   r   r   r   pandasr   dotenvr   rich.consoler   
rich.tabler   rich.promptr	   r
   collectionsr   r   r   getenvr   r,   r   r9   rf   r8   r   r   boolr   r   r4   r   r   r   r   <module>   s0    
] "'$33F
