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)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)   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 r4   '/var/www/html/timesheet/backend/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 r0   )	isoformat)vr4   r4   r5   <lambda>       z%DeveloperRegistration.Config.<lambda>N)__name__
__module____qualname__r
   Zjson_encodersr4   r4   r4   r5   r%      s    r%   )rC   rD   rE   str__annotations__r9   r   r;   intr<   r=   r>   r%   r4   r4   r4   r5   r6      s   
r6   )r/   r3   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detailheaderssubusername)r   r   HTTP_401_UNAUTHORIZEDauthZverify_tokengetcrudget_user_by_username)r/   r3   Zcredentials_exceptionpayloadrQ   userr4   r4   r5   get_current_user   s     


rY   z	/register)Zresponse_model)rX   r3   c                 C   s.   t j|| jd}|r tdddt j|| dS )NrP     zUsername already registeredrL   rM   )r3   rX   )rU   rV   rQ   r   create_user)rX   r3   Zdb_userr4   r4   r5   r\      s    r\   z/token)	form_datar3   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 passwordrI   rJ   rK   )minutesrO   )dataZexpires_deltaZbearer)access_token
token_type)
rS   Zauthenticate_userrQ   passwordr   r   rR   r   ZACCESS_TOKEN_EXPIRE_MINUTESZcreate_access_token)r]   r3   rX   Zaccess_token_expiresr`   r4   r4   r5   login_for_access_token   s     rc   z	/users/mecurrent_userc                 C   s   | S r0   r4   rd   r4   r4   r5   read_users_me   s    rf   z/activity-trackerc                      s   t dS )Nzactivity_tracker.htmlr   r4   r4   r4   r5   get_activity_tracker   s    rg   z/activity-data)
start_dateend_datere   r3   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   hourminutesecondmicrosecondc                 s   s   | ]}|d  V  qdS ZdurationNr4   .0itemr4   r4   r5   	<genexpr>  s     z$get_activity_data.<locals>.<genexpr>  startendr_   Z
total_time
date_range  zError fetching activity data: r[   N)r   r
   fromisoformatreplacenowr   utcget_activity_datarU   Zcreate_activity_recordidget_activity_summarysumr?   	Exceptionr   rF   )rh   ri   re   r3   	aw_clientrx   ry   Zactivity_datar-   Zprocessed_dataer4   r4   r5   r      s$    r   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 databaserj   rk   r   rl   c                 s   s   | ]}|d  V  qdS rq   r4   rr   r4   r4   r5   ru   <  s     z'get_activity_summary.<locals>.<genexpr>rv   rw   rz   r|   z Error getting activity summary: r[   N)r
   r}   r~   r   r   r   rU   r   r   r   r?   r   r   rF   )rh   ri   re   r3   rx   ry   Zsummaryr   r4   r4   r5   r   %  s    r   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 rangerj   rk   r   rl   rw   )Zproductivity_analysisr{   r|   z Error calculating productivity: r[   N)r
   r}   r~   r   r   r   r   Z/calculate_productivity_score_from_activitywatchr?   r   r   rF   )	rh   ri   re   r3   rx   ry   
calculatorZanalysisr   r4   r4   r5   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 codingrj   rk      )daysr   rl   rw   )Zdaily_hours_reportr{   r|   zError calculating daily hours: r[   N)r
   r}   r~   r   r   r   r   r   Zcalculate_daily_reportr   r?   r   r   rF   )	rh   ri   re   r3   rx   ry   r   Zreportr   r4   r4   r5   get_daily_hoursa  s    r   z/top-window-titles2   )rh   ri   limitre   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 ActivityWatchrj   rk   r   rl   Ztotal_durationr'   z.2fhZduration_formatted	last_seen%Y-%m-%d %H:%M:%SZlast_seen_formattedrw   )Ztop_window_titlesZtotal_titlesr{   r|   z"Error fetching top window titles: r[   N)r   r
   r}   r~   r   r   r   get_top_window_titlesstrftimelenr?   r   r   rF   )
rh   ri   r   re   r   rx   ry   Z
top_titlesr"   r   r4   r4   r5   r     s$    r   c                      s   t dS )z$Serve the developer selection portalzdeveloper_selection_portal.htmlr   r4   r4   r4   r5    serve_developer_selection_portal  s    r   z/developer-setupc                      s   t dS )Nzdeveloper-setup.htmlr   r4   r4   r4   r5   serve_developer_setup  s    r   z/developers-listc                      s   t dS )Nzdevelopers_list.htmlr   r4   r4   r4   r5   serve_developers_list  s    r   z/api/admin/developers-overview)re   r3   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   rl   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tzinfo  _      r'   <    min agoh agoNevername	is_activehours_todayr+   activities_countr   c                 s   s   | ]}|d  V  qdS )r+   Nr4   )rs   devr4   r4   r5   ru     s     z0get_admin_developers_overview.<locals>.<genexpr>Ztotal_developersZactive_developerstotal_hoursavg_productivityZoverview
developersr|   z#Error getting developers overview: r[   N)r
   r   
sqlalchemyr   r   r   r~   executefetchalltotal_secondsminmaxrH   appendr   r   r   r   rF   )re   r3   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   r4   r4   r5   get_admin_developers_overview  s^    


$
r   z/api/public/developers-listr2   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   r|   zError listing developers: r[   )r
   r   r   r   r   r   r   r   r~   r   rH   r   r   r?   r   r   rF   )r3   r
   r   r   Zdevelopers_queryr   r   r   r   r   r   r   r   r   r   r4   r4   r5   get_public_developers_list  s<    


r   z/api/developers/real-summary)rh   ri   r3   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 r0   )r   rs   dr4   r4   r5   ru   K  s     z)get_developers_summary.<locals>.<genexpr>c                 s   s   | ]}|j V  qd S r0   )r+   r   r4   r4   r5   ru   L  s     r   c                 s   s   | ]}|j rd V  qdS )r   N)r   r   r4   r4   r5   ru   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   r4   r4   r5   
<listcomp>V  s   	z*get_developers_summary.<locals>.<listcomp>r   )querymodelsZ	Developerallr   r   )rh   ri   r3   r   r   r   Zactive_devsr4   r4   r5   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   rZ   z-Invalid name: could not generate developer IDr[   )rerO   striplowerr   )r   Z
clean_namer   r4   r4   r5   generate_developer_id  s    r   z/api/test-developer-connectionr:   )r   r9   r;   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   rZ   z ActivityWatch not responding at r[   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: r|   zTest failed: )requestsrT   rL   r   jsonr   listkeys
exceptionsZRequestExceptionrF   r   )	r   r9   r;   Zaw_urlZinfo_responseZ	info_dataZbuckets_responseZbuckets_datar   r4   r4   r5   test_developer_connection  sB    


	r   z/register-developerc                      s   t dS )%Serve the developer registration formz"simple_developer_registration.htmlr   r4   r4   r4   r5   serve_registration_form  s    r   z/api/register-developer)registrationr3   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_rZ   z=Invalid access token format. Token must start with 'AWToken_'r[   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   emailactiver8   Z
created_atu   ✅ Developer registered: z ()z!Developer registered successfullyz&Data collection will begin immediatelyz$Developer will appear in portal as '')r   r   r   r7   Zmonitoring_startsZportal_accessu   ❌ Registration failed for z: r|   zRegistration failed: N)r   r7   r8   
startswithr   r   r   fetchoner
   r   r   r   commitprintr   rollbackrF   )r   r3   r   Zexisting_devZexisting_nameZexisting_tokenZinsert_queryr   r4   r4   r5   register_developer*  sz    


		r   c                      s   t dS )r   zregister-developer.htmlr   r4   r4   r4   r5   r     s    z$/api/developer-status/{developer_id})r   r3   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 foundr[   r   r   r   r   N   r      r      Z
monitoring
registered)
r   r   r   Zregistered_atZ	last_syncr9   r;   Zactivitywatch_statusZrecent_activitiesr   r|   z Error getting developer status: )r   r   r   r   r?   r   rF   )r   r3   Zstatus_queryr   r   r4   r4   r5   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   rF   )r3   Zupdatesupdater   r4   r4   r5   setup_database_schema  s    r   __main__z0.0.0.0i@  )hostport)r:   )vZfastapir   r   r   r   r   r   Zfastapi.securityr   r   Zsqlalchemy.ormr	   r
   r   r   typingr   r   Zstateless_webhookr   Zstateless_routeruvicornr   ZschemasrU   rS   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BasemetadataZ
create_allappZfastapi.middleware.corsr$   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_schemer6   rF   rY   ZpostZUserZ
UserCreater\   Tokenrc   rT   rf   rg   r   r   r   r   rH   r   r   r   r   r   r   r   r   r   r   r   r   r   rC   runr4   r4   r4   r5   <module>   sn  (
	

	5
((


W7  2
h
4
