U
    h|                     @   s\  d dl mZmZmZmZ d dlmZmZ d dl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Z8e8j9e	dgddgdgd e8j:edd gd! d dl;mZ< e8:e< d dl=mZ> e8j:e>d"gd# ed$d%Z?d&d' Z4e@eAd(B  G d)d* d*e.ZCee?ee4feDed+d,d-ZEe8jFd.ejGd/ee4fejHed0d1d2ZIe8jFd3ejJd/e ee4feed4d5d6ZKe8jLd7ejGd/eeEfejGd8d9d:ZMe8Ld;d<d= ZNe8Ld>ddeeEee4feeD eeD ejGed?d@dAZOe8LdBddeeEee4feeD eeD ejGed?dCdDZPe8LdEddeeEee4feeD eeD ejGed?dFdGZQe8LdHddeeEee4feeD eeD ejGed?dIdJZRe8LdKdddLeeEfeeD eeD eSejGdMdNdOZTe8Ld;dPdQ ZUe8LdRdSdT ZVe8LdUeeEee4fejGedVdWdXZWe8LdYee4fedZd[d\ZXeLd]ee4feDeDed^d_d`ZYeDeDdadbdcZZe8Lddd|eDeDeSdfdgdhZ[e8Ldidjdk Z\e8Fdlee4feCedmdndoZ]e8Ldidpdk Z\e8Ldqee4feDedrdsdtZ^e8Lduee4fedZdvdwZ_e`dxkrXejae8dydzd{ dS )}    )FastAPIDependsHTTPExceptionstatus)OAuth2PasswordBearerOAuth2PasswordRequestForm)CORSMiddleware)Session)datetime	timedeltatimezone)ListOptional)routerN)SessionLocalengine)ActivityWatchClient)ProductivityCalculator)RealisticHoursCalculator)DeveloperDiscovery)DiscoveredDeveloperActivityRecord)ThreadPoolExecutorFileResponse)StaticFiles)	BaseModel)r   text)	APIRouterr   )get_db)bindzTimesheet APIz1.0.0)titleversionzhttp://localhost:3000T*)Zallow_originsZallow_credentialsZallow_methodsZallow_headersz/api/v1zstateless-webhook)prefixtagszdynamic-developers)r&   token)ZtokenUrlc                  c   s    t  } z
| V  W 5 |   X d S N)r   closedb r,   '/var/www/html/timesheet/backend/main.pyr    >   s    
r    zreal_data_endpoints.pyc                   @   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)vr,   r,   r-   <lambda>R       z%DeveloperRegistration.Config.<lambda>N)__name__
__module____qualname__r
   Zjson_encodersr,   r,   r,   r-   ConfigP   s    r>   )r;   r<   r=   str__annotations__r1   r   r3   intr4   r5   r6   r>   r,   r,   r,   r-   r.   G   s   
r.   )r'   r+   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'   r+   Zcredentials_exceptionpayloadrJ   userr,   r,   r-   get_current_userX   s     


rR   z	/register)Zresponse_model)rQ   r+   c                 C   s.   t j|| jd}|r tdddt j|| dS )NrI     zUsername already registeredrE   rF   )r+   rQ   )rN   rO   rJ   r   create_user)rQ   r+   Zdb_userr,   r,   r-   rU   k   s    rU   z/token)	form_datar+   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 passwordrB   rC   rD   )minutesrH   )dataZexpires_deltaZbearer)access_token
token_type)
rL   Zauthenticate_userrJ   passwordr   r   rK   r   ZACCESS_TOKEN_EXPIRE_MINUTESZcreate_access_token)rV   r+   rQ   Zaccess_token_expiresrY   r,   r,   r-   login_for_access_tokenr   s     r\   z	/users/mecurrent_userc                 C   s   | S r(   r,   r]   r,   r,   r-   read_users_me   s    r_   z/activity-trackerc                      s   t dS )Nzactivity_tracker.htmlr   r,   r,   r,   r-   get_activity_tracker   s    r`   z/activity-data)
start_dateend_dater^   r+   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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durationNr,   .0itemr,   r,   r-   	<genexpr>   s     z$get_activity_data.<locals>.<genexpr>startendrX   Z
total_time
date_range  zError fetching activity data: rT   N)r   r
   fromisoformatreplacenowr   utcget_activity_datarN   Zcreate_activity_recordidget_activity_summarysumr7   	Exceptionr   r?   )ra   rb   r^   r+   	aw_clientrp   rq   Zactivity_dataZactivityZprocessed_dataer,   r,   r-   ry      s$    ry   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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 databaserc   rd   r   re   c                 s   s   | ]}|d  V  qdS rj   r,   rk   r,   r,   r-   rn      s     z'get_activity_summary.<locals>.<genexpr>ro   rr   rt   z Error getting activity summary: rT   N)r
   ru   rv   rw   r   rx   rN   r{   rz   r|   r7   r}   r   r?   )ra   rb   r^   r+   rp   rq   Zsummaryr   r,   r,   r-   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 rangerc   rd   r   re   ro   )Zproductivity_analysisrs   rt   z Error calculating productivity: rT   N)r
   ru   rv   rw   r   rx   r   Z/calculate_productivity_score_from_activitywatchr7   r}   r   r?   )	ra   rb   r^   r+   rp   rq   
calculatorZanalysisr   r,   r,   r-   get_productivity_analysis   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 codingrc   rd      )daysr   re   ro   )Zdaily_hours_reportrs   rt   zError calculating daily hours: rT   N)r
   ru   rv   rw   r   rx   r   r   Zcalculate_daily_reportrz   r7   r}   r   r?   )	ra   rb   r^   r+   rp   rq   r   Zreportr   r,   r,   r-   get_daily_hours   s    r   z/top-window-titles2   )ra   rb   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 ActivityWatchrc   rd   r   re   Ztotal_duration  z.2fhZduration_formatted	last_seen%Y-%m-%d %H:%M:%SZlast_seen_formattedro   )Ztop_window_titlesZtotal_titlesrs   rt   z"Error fetching top window titles: rT   N)r   r
   ru   rv   rw   r   rx   get_top_window_titlesstrftimelenr7   r}   r   r?   )
ra   rb   r   r^   r~   rp   rq   Z
top_titlesr"   r   r,   r,   r-   r     s$    r   c                      s   t dS )z$Serve the developer selection portalzdeveloper_selection_portal.htmlr   r,   r,   r,   r-    serve_developer_selection_portal8  s    r   z/developers-listc                      s   t dS )Nzdevelopers_list.htmlr   r,   r,   r,   r-   serve_developers_list=  s    r   z/api/admin/developers-overview)r^   r+   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   re   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_todayproductivityactivities_countr   c                 s   s   | ]}|d  V  qdS )r   Nr,   )rl   devr,   r,   r-   rn     s     z0get_admin_developers_overview.<locals>.<genexpr>Ztotal_developersZactive_developerstotal_hoursavg_productivityZoverview
developersrt   z#Error getting developers overview: rT   N)r
   r   
sqlalchemyr   rw   rx   rv   executefetchalltotal_secondsminmaxrA   appendr|   r   r}   r   r?   )r^   r+   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   r,   r,   r-   get_admin_developers_overviewA  s^    


$
r   z/api/public/developers-listr*   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   rt   zError listing developers: rT   )r
   r   r   r   r   r   rw   rx   rv   r   rA   r   r   r7   r}   r   r?   )r+   r
   r   r   Zdevelopers_queryr   r   r   r   r   r   r   r   r   r   r,   r,   r-   get_public_developers_list  s<    


r   z/api/developers/real-summary)ra   rb   r+   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   rl   dr,   r,   r-   rn     s     z)get_developers_summary.<locals>.<genexpr>c                 s   s   | ]}|j V  qd S r(   )r   r   r,   r,   r-   rn     s     r   c                 s   s   | ]}|j rd V  qdS )r   N)r   r   r,   r,   r-   rn     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   r,   r,   r-   
<listcomp>  s   	z*get_developers_summary.<locals>.<listcomp>r   )querymodelsZ	Developerallr|   r   )ra   rb   r+   r   r   r   Zactive_devsr,   r,   r-   get_developers_summary  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   rS   z-Invalid name: could not generate developer IDrT   )rerH   striplowerr   )r   Z
clean_namer   r,   r,   r-   generate_developer_idd  s    r   z/api/test-developer-connectionr2   )r   r1   r3   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   rS   z ActivityWatch not responding at rT   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: rt   zTest failed: )requestsrM   rE   r   jsonr   listkeys
exceptionsZRequestExceptionr?   r}   )	r   r1   r3   Zaw_urlZinfo_responseZ	info_dataZbuckets_responseZbuckets_datar   r,   r,   r-   test_developer_connectionu  sB    


	r   z/register-developerc                      s   t dS )%Serve the developer registration formz"simple_developer_registration.htmlr   r,   r,   r,   r-   serve_registration_form  s    r   z/api/register-developer)registrationr+   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_rS   z=Invalid access token format. Token must start with 'AWToken_'rT   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activer0   Z
created_atu   ✅ Developer registered: z ()z!Developer registered successfullyz&Data collection will begin immediatelyz$Developer will appear in portal as '')r   r   r   r/   Zmonitoring_startsZportal_accessu   ❌ Registration failed for z: rt   zRegistration failed: N)r   r/   r0   
startswithr   r   r   fetchoner
   rw   r   rx   commitprintr}   rollbackr?   )r   r+   r   Zexisting_devZexisting_nameZexisting_tokenZinsert_queryr   r,   r,   r-   register_developer  sz    


		r   c                      s   t dS )r   zregister-developer.htmlr   r,   r,   r,   r-   r     s    z$/api/developer-status/{developer_id})r   r+   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 foundrT   r   r   r   r   N   r      r      Z
monitoring
registered)
r   r   r   Zregistered_atZ	last_syncr1   r3   Zactivitywatch_statusZrecent_activitiesr   rt   z Error getting developer status: )r   r   r   r   r7   r}   r?   )r   r+   Zstatus_queryr   r   r,   r,   r-   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   r?   )r+   Zupdatesupdater   r,   r,   r-   setup_database_schemaX  s    r   __main__z0.0.0.0i@  )hostport)r2   )bZfastapir   r   r   r   Zfastapi.securityr   r   Zfastapi.middleware.corsr   Zsqlalchemy.ormr	   r
   r   r   typingr   r   Zstateless_webhookr   Zstateless_routeruvicornr   ZschemasrN   rL   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add_middlewareZinclude_routerZsimple_multi_dev_apiZmulti_dev_routerZfixed_dynamic_developer_apiZdynamic_developer_routerZoauth2_schemeexecopenreadr.   r?   rR   ZpostZUserZ
UserCreaterU   Tokenr\   rM   r_   r`   ry   r{   r   r   rA   r   r   r   r   r   r   r   r   r   r   r   r   r;   runr,   r,   r,   r-   <module>   s2  (


((

W7  2
h
4
