U
     h؍                     @   sz  d dl mZmZmZmZm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 d dlmZmZ d dlmZ d dlZ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 d d	lm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* d dl+m,Z, d dl-m.Z. d dlmZ d dl/Z/d dl0m1Z1 d dl2Z2d dl m3Z3mZ d dl
mZ d dlm4Z4 d dlZe3 Zej5j6j7ed edddZ8d dl9m:Z: d dl;m<Z< e<= rddddd d!d"gZ>nd#gZ>e8j?e:e>d$d#gd#gd%d& e8j@ed'd(gd) d dlAmZB e8@eB d dlCmZD e8j@eDd*gd+ d dlEmZF e8j@eFd,gd+ d dlGmZH e8j@eHd-gd+ d dlImZJ e8j@eJd.gd+ d dlKmZL e8j@eLd/gd+ d dlMmZN e8j@eNd0gd+ d dlOmZP e8j@ePd1gd+ d dlQmZR e8j@eRd2gd+ d dlSmZT e8j@eTd3gd+ ed4d5ZUd6d7 Z4G d8d9 d9e.ZVeeUee4feWed:d;d<ZXe8jYd=ejZd>ee4fej[ed?d@dAZ\e8jYdBej]d>e ee4fe	edCdDdEZ^e8j_dFejZd>eeXfejZdGdHdIZ`e8_dJdKdL Zae8_dMddeeXee4feeW eeW ejZedNdOdPZbe8_dQddeeXee4feeW eeW ejZedNdRdSZce8_dTddeeXee4feeW eeW ejZedNdUdVZde8_dWddeeXee4feeW eeW ejZedNdXdYZee8_dZddd[eeXfeeW eeW efejZd\d]d^Zge8_dJd_d` Zhe8_dadbdc Zie8_dddedf Zje8_dgeeXee4fejZedhdidjZke8_dkee4fedldmdnZle_doee4feWeWedpdqdrZmeWeWdsdtduZne8_dvdeWeWefdxdydzZoe8_d{d|d} Zpe8Yd~ee4feVedddZqe8_d{dd} Zpe8_dee4feWedddZre8_dee4fedlddZsetdkrvejue8ddd dS )    )FastAPIDependsHTTPExceptionstatusRequestN)OAuth2PasswordBearerOAuth2PasswordRequestForm)Session)datetime	timedeltatimezone)ListOptional)router)SessionLocalengine)ActivityWatchClient)ProductivityCalculator)RealisticHoursCalculator)DeveloperDiscovery)DiscoveredDeveloperActivityRecord)ThreadPoolExecutorFileResponse)StaticFiles)	BaseModel)r   text)	APIRouterr   )get_db)ZbindzTimesheet APIz1.0.0)titleversion)CORSMiddleware)Configzhttp://localhost:3000z!http://timesheet.firsteconomy.comz"https://timesheet.firsteconomy.comz%http://api-timesheet.firsteconomy.comz&https://api-timesheet.firsteconomy.comz%http://timesheet-api.firsteconomy.comz&https://timesheet-api.firsteconomy.com*T  )Zallow_originsZallow_credentialsZallow_methodsZallow_headersZmax_agez/api/v1zstateless-webhook)prefixtagszdynamic-developers)r(   Ztestzdevelopers-simplezdevelopers-ormproductivityzactivity-categorizationsyncactivitydebugtoken)ZtokenUrlc                  c   s    t  } z
| V  W 5 |   X d S N)r   closedb r2   	./main.pyr       s    
r    c                   @   s|   e Zd ZU eed< eed< dZee ed< dZee ed< dZ	ee ed< dZ
ee ed< dZee ed	< G d
d dZdS )DeveloperRegistrationdeveloper_name	api_tokenN
ip_address  activitywatch_porthostnamebrowser_inforegistration_timec                   @   s   e Zd Zedd iZdS )zDeveloperRegistration.Configc                 C   s   |   S r.   )	isoformat)vr2   r2   r3   <lambda>       z%DeveloperRegistration.Config.<lambda>N)__name__
__module____qualname__r
   Zjson_encodersr2   r2   r2   r3   r$      s    r$   )rA   rB   rC   str__annotations__r7   r   r9   intr:   r;   r<   r$   r2   r2   r2   r3   r4      s   
r4   )r-   r1   c                 C   sh   t tjdddid}z$t| }|d}|d kr6|W n   |Y nX tj||d}|d krd||S )NzCould not validate credentialsWWW-AuthenticateBearerstatus_codedetailZheaderssubusername)r   r   HTTP_401_UNAUTHORIZEDauthZverify_tokengetcrudget_user_by_username)r-   r1   Zcredentials_exceptionZpayloadrN   userr2   r2   r3   get_current_user   s     


rU   z	/register)Zresponse_model)rT   r1   c                 C   s.   t j|| jd}|r tdddt j|| dS )NrM     zUsername already registeredrJ   rK   )r1   rT   )rR   rS   rN   r   create_user)rT   r1   Zdb_userr2   r2   r3   rX      s    rX   z/token)	form_datar1   c                 C   sT   t || j| j}|s*ttjdddidtt jd}t j	d|ji|d}|dd	S )
NzIncorrect username or passwordrG   rH   rI   )ZminutesrL   )dataZexpires_deltaZbearer)access_token
token_type)
rP   Zauthenticate_userrN   Zpasswordr   r   rO   r   ZACCESS_TOKEN_EXPIRE_MINUTESZcreate_access_token)rY   r1   rT   Zaccess_token_expiresr[   r2   r2   r3   login_for_access_token   s     r]   z	/users/mecurrent_userc                 C   s   | S r.   r2   r^   r2   r2   r3   read_users_me   s    r`   z/activity-trackerc                      s   t dS )Nzactivity_tracker.htmlr   r2   r2   r2   r3   get_activity_tracker   s    ra   z/activity-data)
start_dateend_dater_   r1   c              
   C   s   zt  }| r t| dd}nttjjddddd}|rRt|dd}nttj}|||}|D ]}t	|||j
 qnt||j
||}	|	tdd |	D d | | dd	W S  tk
r }
 ztd
dt|
 dW 5 d}
~
X Y nX dS )z:Get activity data from ActivityWatch and store in databaseZ+00:00r   ZhourZminutesecondZmicrosecondc                 s   s   | ]}|d  V  qdS ZdurationNr2   .0itemr2   r2   r3   	<genexpr>  s     z$get_activity_data.<locals>.<genexpr>  startendrZ   Z
total_time
date_range  zError fetching activity data: rW   N)r   r
   fromisoformatreplacenowr   utcget_activity_datarR   Zcreate_activity_recordidget_activity_summarysumr=   	Exceptionr   rD   )rb   rc   r_   r1   	aw_clientro   rp   Zactivity_datar+   Zprocessed_dataer2   r2   r3   rx      s$    rx   z/activity-summaryc              
   C   s   z| rt | dd}nt tjjddddd}|rLt |dd}nt tj}t||j||}|t	dd |D d |
 |
 dd	W S  tk
r } ztd
dt| dW 5 d}~X Y nX dS )z"Get activity summary from databaserd   re   r   rf   c                 s   s   | ]}|d  V  qdS rh   r2   ri   r2   r2   r3   rl   <  s     z'get_activity_summary.<locals>.<genexpr>rm   rn   rq   rs   z Error getting activity summary: rW   N)r
   rt   ru   rv   r   rw   rR   rz   ry   r{   r=   r|   r   rD   )rb   rc   r_   r1   ro   rp   Zsummaryr~   r2   r2   r3   rz   %  s    rz   z/productivity-analysisc           	   
   C   s   z| rt | dd}nt tjjddddd}|rLt |dd}nt tj}t }|||}|| | ddW S  t	k
r } zt
ddt| d	W 5 d
}~X Y nX d
S )z6Get productivity analysis for the specified date rangerd   re   r   rf   rn   )Zproductivity_analysisrr   rs   z Error calculating productivity: rW   N)r
   rt   ru   rv   r   rw   r   Z/calculate_productivity_score_from_activitywatchr=   r|   r   rD   )	rb   rc   r_   r1   ro   rp   
calculatorZanalysisr~   r2   r2   r3   get_productivity_analysisC  s    r   z/daily-hoursc           	   
   C   s   z| rt | dd}n(t tjtdd }|jddddd}|rZt |dd}nt tj}t }|||j	||}||
 |
 ddW S  tk
r } ztd	d
t| dW 5 d}~X Y nX dS )z0Get daily working hours report with color codingrd   re      )Zdaysr   rf   rn   )Zdaily_hours_reportrr   rs   zError calculating daily hours: rW   N)r
   rt   ru   rv   r   rw   r   r   Zcalculate_daily_reportry   r=   r|   r   rD   )	rb   rc   r_   r1   ro   rp   r   reportr~   r2   r2   r3   get_daily_hoursa  s    r   z/top-window-titles2   )rb   rc   limitr_   c           
   
   C   s   zt  }| r t| dd}nttjjddddd}|rRt|dd}nttj}||||}|D ].}|d d dd|d	< |d
 d|d< qp|t	||
 |
 ddW S  tk
r }	 ztddt|	 dW 5 d}	~	X Y nX dS )z4Get top window titles by duration from ActivityWatchrd   re   r   rf   Ztotal_durationr&   z.2fhZduration_formatted	last_seen%Y-%m-%d %H:%M:%SZlast_seen_formattedrn   )Ztop_window_titlesZtotal_titlesrr   rs   z"Error fetching top window titles: rW   N)r   r
   rt   ru   rv   r   rw   get_top_window_titlesstrftimelenr=   r|   r   rD   )
rb   rc   r   r_   r}   ro   rp   Z
top_titlesr!   r~   r2   r2   r3   r     s$    r   c                      s   t dS )z$Serve the developer selection portalzdeveloper_selection_portal.htmlr   r2   r2   r2   r3    serve_developer_selection_portal  s    r   z/developer-setupc                      s   t dS )Nzdeveloper-setup.htmlr   r2   r2   r2   r3   serve_developer_setup  s    r   z/developers-listc                      s   t dS )Nzdevelopers_list.htmlr   r2   r2   r2   r3   serve_developers_list  s    r   z/api/admin/developers-overview)r_   r1   c              
      s  zddl m }m} ddlm} ||jjddddd}|d}||d|i }g }d}	d}
|D ]}|d }|d }|d pd}|d	 }|d
 }|	|7 }	d}|r||j|j|jd }|	 dk }|r|
d7 }
t
dtdt|d }|rP||j|j|jd }|	 dk r8t|	 d  d}nt|	 d  d}nd}|||||||d qf|rtdd |D t| nd}t||
|	|d|dW S  tk
r } ztddt| dW 5 d}~X Y nX dS )zAAdmin endpoint to get overview of all developers (for the portal)r   r
   r   r   rf   a  
            SELECT 
                ar.developer_id,
                COUNT(*) as activity_count,
                SUM(ar.duration) as total_duration,
                MAX(ar.timestamp) as last_activity
            FROM activity_records ar
            WHERE ar.developer_id IS NOT NULL
            AND ar.timestamp >= :today
            GROUP BY ar.developer_id
            ORDER BY total_duration DESC
        today         g      @FZtzinfo  _      r&   <    min agoh agoNevername	is_activehours_todayr)   activities_countr   c                 s   s   | ]}|d  V  qdS )r)   Nr2   )rj   Zdevr2   r2   r3   rl     s     z0get_admin_developers_overview.<locals>.<genexpr>Ztotal_developersZactive_developerstotal_hoursavg_productivityZoverview
developersrs   z#Error getting developers overview: rW   N)r
   r   
sqlalchemyr   rv   rw   ru   executefetchalltotal_secondsminmaxrF   appendr{   r   r|   r   rD   )r_   r1   r
   r   r   r   Zoverview_queryresultr   r   active_countrowdeveloper_idZactivity_countZduration_secondslast_activityr   r   	time_diffr)   r   r   r~   r2   r2   r3   get_admin_developers_overview  s^    


$
r   z/api/public/developers-listr0   c              
      sL  z
ddl m }m} ddlm} |d}| | }g }|D ]}|d }|d }	|d }
|
r||j|
j|jd }|	 dk rt
|	 d	  d
}n.|	 dk rt
|	 d  d}n
|
d}|	 dk }nd}d}|||	|
r|
 nd||d q>d|iW S  tk
rF } ztddt| dW 5 d}~X Y nX dS )z3Public endpoint to list developers (for portal use)r   r   r   ah  
            SELECT DISTINCT 
                ar.developer_id,
                COUNT(*) as total_activities,
                MAX(ar.timestamp) as last_activity
            FROM activity_records ar
            WHERE ar.developer_id IS NOT NULL
            GROUP BY ar.developer_id
            ORDER BY last_activity DESC NULLS LAST
            LIMIT 50
        r   r   r   r&   r   r   iQ r   z%Y-%m-%dr   r   FN)r   total_activitiesr   r   r   r   rs   zError listing developers: rW   )r
   r   r   r   r   r   rv   rw   ru   r   rF   r   r   r=   r|   r   rD   )r1   r
   r   r   Zdevelopers_queryr   r   r   r   r   r   r   r   r   r~   r2   r2   r3   get_public_developers_list  s<    


r   z/api/developers/real-summary)rb   rc   r1   c                 C   sx   | tj }tdd |D }|r@tdd |D t| nd}tdd |D }t||||ddd |D d	S )
Nc                 s   s   | ]}|j V  qd S r.   )r   rj   dr2   r2   r3   rl   K  s     z)get_developers_summary.<locals>.<genexpr>c                 s   s   | ]}|j V  qd S r.   )r)   r   r2   r2   r3   rl   L  s     r   c                 s   s   | ]}|j rd V  qdS )r   N)r   r   r2   r2   r3   rl   M  s      r   c              
   S   s:   g | ]2}|j |j|j|j|j|jr.|jd nddqS )r   r   r   )r   r   r   r)   r   r   r   r   r2   r2   r3   
<listcomp>V  s   	z*get_developers_summary.<locals>.<listcomp>r   )ZquerymodelsZ	Developerallr{   r   )rb   rc   r1   r   r   r   Zactive_devsr2   r2   r3   get_developers_summaryG  s    "	r   )r   returnc                 C   sD   t dd| }t dd|  }|dd }|s@tddd	|S )
z'Generate a clean developer ID from namez[^a-zA-Z0-9\s] z\s+_Nr   rV   z-Invalid name: could not generate developer IDrW   )rerL   striplowerr   )r   Z
clean_namer   r2   r2   r3   generate_developer_id  s    r   z/api/test-developer-connectionr8   )r   r7   r9   c           	   
      s$  zd| d| }t j| ddd}|jdkrBtdd| d	| }t j| d
dd}|jdkrttddd	| }dd|ddt|t| dd |dW S  t jj	k
r } ztddt
| d	W 5 d}~X Y n: tk
r } ztddt
| d	W 5 d}~X Y nX dS )z5Test connection to developer's ActivityWatch instancezhttp://:z/api/0/info   )timeout   rV   z ActivityWatch not responding at rW   z/api/0/bucketsz&Could not access ActivityWatch bucketsTz#ActivityWatch connection successfulr"   unknownN)successmessageZactivitywatch_versionZavailable_bucketsZbucket_namesZconnection_urlzConnection failed: rs   zTest failed: )requestsrQ   rJ   r   jsonr   listkeys
exceptionsZRequestExceptionrD   r|   )	r   r7   r9   Zaw_urlZinfo_responseZ	info_dataZbuckets_responseZbuckets_datar~   r2   r2   r3   test_developer_connection  sB    


	r   z/register-developerc                      s   t dS )%Serve the developer registration formz"simple_developer_registration.htmlr   r2   r2   r2   r3   serve_registration_form  s    r   z/api/register-developer)registrationr1   c                    s  z$t | j}| jds&tddd|tdd|i }|rVtdd| dd|td	d
| ji }|rtdd| j dd|tdd| ji }|rtdddtd}|||| j| dd| jt	t
jd |  td| d| j d dd|| jdd| j ddW S  tk
r>    Y nX tk
r } z8|  td| j d|  tddt| dW 5 d}~X Y nX dS ) z2Developer registration with proper table structureZAWToken_rV   z=Invalid access token format. Token must start with 'AWToken_'rW   z6SELECT id FROM developers WHERE developer_id = :dev_iddev_idzDeveloper ID 'z.' already exists. Please use a different name.z,SELECT id FROM developers WHERE name = :namer   zDeveloper name 'z2SELECT id FROM developers WHERE api_token = :tokenr-   z9This access token is already in use by another developer.a  
            INSERT INTO developers (
                developer_id,
                name,
                email,
                active,
                api_token,
                created_at
            ) VALUES (
                :developer_id,
                :name,
                :email,
                :active,
                :api_token,
                :created_at
            )
        z@company.comT)r   r   Zemailactiver6   Z
created_atu   ✅ Developer registered: z ()z!Developer registered successfullyz&Data collection will begin immediatelyz$Developer will appear in portal as '')r   r   r   r5   Zmonitoring_startsZportal_accessu   ❌ Registration failed for z: rs   zRegistration failed: N)r   r5   r6   
startswithr   r   r   fetchoner
   rv   r   rw   commitprintr|   rollbackrD   )r   r1   r   Zexisting_devZexisting_nameZexisting_tokenZinsert_queryr~   r2   r2   r3   register_developer*  sz    


		r   c                      s   t dS )r   zregister-developer.htmlr   r2   r2   r2   r3   r     s    z$/api/developer-status/{developer_id})r   r1   c                    s   zt d}||d| i }|s.tddd|d |d |d |d	 rT|d	  nd
|d rj|d  nd
|d |d |d |d |d dkrdndd
W S  tk
r    Y n8 tk
r } ztddt| dW 5 d
}~X Y nX d
S )z,Get current status of a registered developera  
            SELECT 
                d.developer_id,
                d.name,
                d.active,
                d.created_at,
                d.last_sync,
                d.ip_address,
                d.activitywatch_port,
                d.activitywatch_status,
                COUNT(ar.id) as recent_activities
            FROM developers d
            LEFT JOIN activity_records ar ON d.developer_id = ar.developer_id 
                AND ar.created_at > NOW() - INTERVAL '1 hour'
            WHERE d.developer_id = :dev_id
            GROUP BY d.developer_id, d.name, d.active, d.created_at, d.last_sync, 
                     d.ip_address, d.activitywatch_port, d.activitywatch_status
        r   i  zDeveloper not foundrW   r   r   r   r   N   r      r      Z
monitoringZ
registered)
r   r   r   Zregistered_atZ	last_syncr7   r9   Zactivitywatch_statusZrecent_activitiesr   rs   z Error getting developer status: )r   r   r   r   r=   r|   rD   )r   r1   Zstatus_queryr   r~   r2   r2   r3   get_developer_status  s.    r   z/setup-databasec              
      s|   z:dddddg}|D ]}|  t| q|   dddW S  tk
rv } z|   d	t|d
 W Y S d}~X Y nX dS )z'One-time setup for new database columnszZALTER TABLE developers ADD COLUMN IF NOT EXISTS ip_address VARCHAR(45) DEFAULT '127.0.0.1'zWALTER TABLE developers ADD COLUMN IF NOT EXISTS activitywatch_port INTEGER DEFAULT 5600zWALTER TABLE developers ADD COLUMN IF NOT EXISTS hostname VARCHAR(255) DEFAULT 'unknown'zIALTER TABLE developers ADD COLUMN IF NOT EXISTS browser_info VARCHAR(255)zbALTER TABLE developers ADD COLUMN IF NOT EXISTS activitywatch_status VARCHAR(50) DEFAULT 'unknown'Tz$Database schema updated successfully)r   r   F)r   errorN)r   r   r   r|   r   rD   )r1   Zupdatesupdater~   r2   r2   r3   setup_database_schema  s    r   __main__z0.0.0.0i@  )ZhostZport)r8   )vZfastapir   r   r   r   r   r   Zfastapi.securityr   r   Zsqlalchemy.ormr	   r
   r   r   typingr   r   Zstateless_webhookr   Zstateless_routerZuvicornr   ZschemasrR   rP   Zdatabaser   r   Zmy_activitywatch_clientr   Zproductivity_calculatorr   Zrealistic_hours_calculatorr   Zdeveloper_discoveryr   r   r   concurrent.futuresr   Zfastapi.responsesr   Zfastapi.staticfilesr   Zpydanticr   r   r   r   r   r   r    ZBaseZmetadataZ
create_allZappZfastapi.middleware.corsr#   Zconfigr$   Zis_productionZallowed_originsZadd_middlewareZinclude_routerZsimple_multi_dev_apiZmulti_dev_routerZfixed_dynamic_developer_apiZdynamic_developer_routerZtest_developers_endpointZtest_routerZall_developers_simpleZsimple_dev_routerZdevelopers_orm_apiZorm_dev_routerZproductivity_apiZproductivity_routerZactivity_categorization_apiZcategorization_routerZfixed_sync_endpointZfixed_sync_routerZenhanced_activity_apiZenhanced_activity_routerZdebug_activity_endpointZdebug_activity_routerZoauth2_schemer4   rD   rU   ZpostZUserZ
UserCreaterX   Tokenr]   rQ   r`   ra   rx   rz   r   r   rF   r   r   r   r   r   r   r   r   r   r   r   r   r   rA   runr2   r2   r2   r3   <module>   sn  (
	

	5
((


W7  2
h
4
