
    i>                         d Z ddlZddlZddlZddlmZmZmZ ddlmZm	Z	 ddl
mZmZ  ej                  e      Z G d d      Zy)z
Bitbucket API client for repository management and change detection.
Uses Bitbucket Cloud REST API 2.0 with read-only access.
    N)ListDictOptional)datetime	timedelta)urlparseparse_qsc            	          e Zd ZdZddedededefdZdded	ee   d
edefdZde	e   fdZ
dedee   fdZdedee   fdZddededee   fdZdedee   defdZddedededefdZddededee   fdZd dededee   fdZy)!BitbucketClientz>Read-only client for interacting with Bitbucket Cloud API 2.0.N	workspaceusernameapp_password	api_tokenc                 V   || _         d| _        |r%|st        d      || _        || _        ||f| _        n2|r%|st        d      || _        || _        ||f| _        nt        d      t        j                         | _	        | j
                  | j                  _        d| _
        d| _        y)a7  
        Initialize Bitbucket client with read-only access.
        
        Supports two authentication methods:
        1. App Password: username + app_password
        2. API Token: api_token (username will be your email if using token)
        
        Args:
            workspace: Bitbucket workspace name
            username: Bitbucket username/email (required for app password, optional for API token)
            app_password: Bitbucket app password (alternative to api_token)
            api_token: Atlassian API token (alternative to app_password)
        zhttps://api.bitbucket.org/2.0z1Username (email) is required when using API tokenz,Username is required when using app passwordz1Either app_password or api_token must be providedr   g?N)r   base_url
ValueErrorr   r   authr   requestsSessionsessionlast_request_timemin_request_interval)selfr   r   r   r   s        8/home/batmin/projects/sonar_scan/src/bitbucket_client.py__init__zBitbucketClient.__init__   s     #7  !TUU$DM&DN!9-DI !OPP$DM ,D!<0DIPQQ'') II!"$'!    endpointparamsmethodreturnc           	         |j                         dk7  rt        d      t        j                         | j                  z
  }|| j                  k  r"t        j
                  | j                  |z
         | j                   d|j                  d       }	 | j                  j                  ||d      }t        j                         | _        |j                  dk(  rqt        |j                  j                  dd            }t        j                  d	| d
       t        j
                  |       | j                  j                  ||d      }|j                          |j!                         S # t"        j$                  $ r}|j&                  j                  dk(  r0t        j)                  d| d|j&                  j*                          nFt        j-                  d| d|j&                  j                   d|j&                  j*                          t/        |j&                  d      r,t        j)                  d|j&                  j0                           d}~wt"        j2                  $ ro}t        j-                  d| d|        t/        |d      rBt/        |j&                  d      r,t        j)                  d|j&                  j0                           d}~ww xY w)a  
        Make authenticated read-only request to Bitbucket API 2.0.
        
        Args:
            endpoint: API endpoint (relative to base_url)
            params: Query parameters
            method: HTTP method (only GET allowed for read-only)
            
        Returns:
            JSON response data
            
        Raises:
            requests.RequestException: If request fails
        GETz+Read-only client only supports GET requests/   )r   timeouti  zRetry-After<   zRate limited. Waiting z seconds...  zBitbucket API 404 for : z!Bitbucket API request failed for  textz
Response: Nresponse)upperr   timer   r   sleepr   lstripr   getstatus_codeintheadersloggerwarningraise_for_statusjsonr   	HTTPErrorr+   debugreasonerrorhasattrr*   RequestException)	r   r   r   r   time_since_lasturlr+   retry_afteres	            r   _make_requestzBitbucketClient._make_request<   s<     <<>U"JKK ))+(>(>>T666JJt00?BCq!5 67	||''FB'GH%)YY[D" ##s*!("2"2"6"6}b"IJ!7}KPQ

;'<<++C+K%%'==?"!! 	zz%%,5hZr!**BSBSATUV@
"QZZMcMcLddefgfpfpfwfwexyzqzz6*z!**//):;<(( 	LL<XJbLMq*%'!**f*Ez!**//):;<		s'   CE) )K<CIK$A*KKc           	         g }d| j                    }ddi}	 d}	 |rt        |      }|j                  j                  dd      j	                  d      }t        t        |j                              }|j                         D ci c]+  \  }}|t        |t              rt        |      dk(  r|d	   n|- }}}| j                  ||      }	n| j                  ||      }	|j                  |	j                  d
g              |	j                  d      }|snt        j!                  dt        |       d| j                           |S c c}}w # t"        $ r$}
t        j%                  d|
        g cY d}
~
S d}
~
ww xY w)z
        Get all repositories in the workspace using API 2.0 pagination.
        
        Returns:
            List of repository dictionaries with name, slug, and clone URLs
        repositories/pagelend   Nz/2.0/ r#      r   valuesnextzFound z repositories in workspace zFailed to fetch repositories: )r   r   pathreplacer/   dictr	   queryitems
isinstancelistlenrB   extendr0   r4   info	Exceptionr;   )r   reposr   r   next_urlparsedquery_paramskvdatarA   s              r   get_all_repositoriesz$BitbucketClient.get_all_repositoriesr   sq    "4>>"23S!	H &h/F%{{227B?FFsKH#'(>#?L *6););)=?!%A  At)<Q11RSS ?F ?--h?D  --h?DTXXh34  88F+) , KK&U,GGWXYL?   	LL9!=>I	s1   A,E 0E3BE E 	E4E/)E4/E4	repo_slugc                     	 d| j                    d| }| j                  |      S # t        $ r%}t        j	                  d| d|        Y d}~yd}~ww xY w)z
        Get detailed information about a specific repository.
        
        Args:
            repo_slug: Repository slug/name
            
        Returns:
            Repository information dictionary
        rD   r#   z$Failed to fetch repository info for r(   N)r   rB   rU   r4   r;   )r   r^   r   rA   s       r   get_repository_infoz#BitbucketClient.get_repository_info   s\    	&t~~&6a	{CH%%h// 	LL?	{"QCPQ	s   "% 	AAAc                 &   	 | j                  |      }|rL|j                  d      }|r0t        |t              r|j                  d      n
t	        |      }|r|S d|v r|d   S y# t
        $ r%}t        j                  d| d|        Y d}~yd}~ww xY w)z
        Get the default branch for a repository.
        
        Args:
            repo_slug: Repository slug/name
            
        Returns:
            Default branch name (e.g., "main", "master") or None
        
mainbranchnamedefault_branchz!Failed to get default branch for r(   N)r`   r0   rP   rM   strrU   r4   r9   )r   r^   	repo_inforb   branch_namerA   s         r   get_default_branchz"BitbucketClient.get_default_branch   s    	O00;I&]]<8
<FzSW<X*.."8^abl^mK"** $y0$%566   	OLL<YKr!MNN	Os   AA" A" "	B+BBbranchc           
         g }|r|j                  |       | j                  |      }|r||vr|j                  |       dD ]  }||vs|j                  |        |D ]  }	 d| j                   d| d| }ddi}| j                  ||      }	|	rd|	v rt	        |	d         dkD  r{|	d   d   }
t
        j                  d	| d
|        |
j                  d      |
j                  d      |
j                  dd      |
j                  di       j                  dd      |dc S  t
        j                  d| ddj                  |       d       y# t        j                  $ rx}|j                  j                  dk(  r"t
        j                  d| d| d       Y d}~=t
        j                  d| d| d|j                  j                          Y d}~ud}~wt        $ r)}t
        j                  d| d| d|        Y d}~d}~ww xY w)a?  
        Get the latest commit for a repository branch using API 2.0.
        
        Args:
            repo_slug: Repository slug/name
            branch: Branch name (if None, will try to get default branch, then try main/master)
            
        Returns:
            Latest commit information with date
        )mainmasterdeveloprD   r#   z	/commits/rE   rH   rI   r   zFound latest commit on branch 'z' for hashdatemessagerG   authorraw)rn   ro   rp   rq   ri   r'   zBranch 'z' not found for z, trying next branchNz'HTTP error getting commits for branch 'z' in r(   z"Error getting commits for branch 'z$Could not find any valid branch for z	 (tried: z, ))appendrh   r   rB   rR   r4   r9   r0   r   r8   r+   r1   r5   rU   join)r   r^   ri   branches_to_tryrd   common_branchrg   r   r   r\   latest_commitrA   s               r   get_latest_commitz!BitbucketClient.get_latest_commit   s=     ""6* 00;nOC"">2 ; 	6MO3&&}5	6 + 	K*4>>*:!I;iP[}]#Q))(F;H,T(^1Dq1H$(N1$5MLL#B;-vV_U`!ab - 1 1& 9 - 1 1& 9#0#4#4Y#C"/"3"3Hb"A"E"EeR"P"- 	< 	=i[	RVR[R[\kRlQmmnop %% ::))S0LL8K=8HSg!hi!HUZ[dZeeghihrhrh~h~g   A  B A+eT]S^^`ab`cdes+   !B:EH5G2GHHHlast_scan_datec                 F   |y| j                  |      }|sy|j                  d      }|sy	 t        j                  |j	                  dd            }|j
                  r|j	                  d      }||kD  S # t        $ r"}t        j                  d|        Y d}~yd}~ww xY w)	a.  
        Check if repository has changes since last scan date.
        
        Args:
            repo_slug: Repository slug/name
            last_scan_date: Last scan datetime (None if never scanned)
            
        Returns:
            True if repository has new commits since last scan
        NTFro   Z+00:00tzinfozFailed to parse commit date: )	ry   r0   r   fromisoformatrL   r   rU   r4   r5   )r   r^   rz   rx   commit_date_strcommit_daterA   s          r   has_changes_sincez!BitbucketClient.has_changes_since  s     !..y9'++F3
	"001H1Hh1WXK!!)111>// 	NN:1#>?	s   AA5 5	B >BB daysc                    | j                  ||      }|sy|j                  d      }|sy	 t        j                  |j	                  dd            }|j
                  r|j	                  d      }t        j                         t        |      z
  }||k\  S # t        $ r%}t        j                  d| d	|        Y d}~yd}~ww xY w)
at  
        Check if repository has at least one commit in the last N days (by latest commit).
        
        Args:
            repo_slug: Repository slug/name
            days: Number of days to look back (default: 7)
            branch: Branch name (None = default branch)
            
        Returns:
            True if latest commit is within the last N days
        Fro   r|   r}   Nr~   r   z Failed to parse commit date for r(   )ry   r0   r   r   rL   r   nowr   rU   r4   r5   )	r   r^   r   ri   rx   r   r   cutoffrA   s	            r   has_commits_in_last_n_daysz*BitbucketClient.has_commits_in_last_n_days(  s     ..y&A'++F3	"001H1Hh1WXK!!)111>\\^iT&::F&(( 	NN=i[1#NO	s   A(B 	CB==Cc                 j   	 d| j                    d| d}ddd}| j                  ||      }|r|j                  dg       ng }|sy|d	   }|j                  d
      }|j                  di       xs i }	|	j                  d      }
|	j                  d      }|r|
syt        j                  |
j                  dd            }|j                  r|j                  d      }t        j                         t        |      z
  }||k  ry||
|dS # t        j                  $ rk}|j                  j                  dk(  rt        j                  d|        n/t        j                  d| d|j                  j                          Y d}~yd}~wt         $ r%}t        j                  d| d|        Y d}~yd}~ww xY w)u  
        Get the branch with the most recent commit activity within the last N days.

        Uses Bitbucket API 2.0 branches endpoint with sort by latest commit date.
        Endpoint: /repositories/{workspace}/{repo_slug}/refs/branches?sort=-target.date

        Args:
            repo_slug: Repository slug/name
            days: Look-back window in days (default: 15)

        Returns:
            Dict with keys: name, date (ISO string), hash — or None if no activity in window
        rD   r#   z/refs/branchesz-target.daterH   )sortrE   rI   Nr   rc   targetro   rn   r|   r}   r~   r   )rc   ro   rn   r'   zNo branches found for z HTTP error getting branches for r(   z%Error getting most active branch for )r   rB   r0   r   r   rL   r   r   r   r   r8   r+   r1   r4   r9   r5   rU   )r   r^   r   r   r   r\   rI   ri   rc   r   date_strcommit_hashr   r   rA   s                  r   get_most_active_branchz&BitbucketClient.get_most_active_branchF  s   !	&t~~&6a	{.QH,;F%%h7D/3TXXh+FAYF::f%DZZ"-3Fzz&)H **V,Kx"001A1A#x1PQK!!)111>\\^iT&::FV# (KHH!! 	zz%%,5i[AB!A)BqzzOeOeNfgh 	LL@2aSQR	s9   AD AD A)D D F2A!FF2F--F2rf   protocolc                     |j                  di       }|j                  dg       }|D ])  }|j                  d      |k(  s|j                  d      c S  y)z
        Get clone URL for repository.
        
        Args:
            repo_info: Repository information dictionary
            protocol: Clone protocol (https or ssh)
            
        Returns:
            Clone URL string
        linksclonerc   hrefN)r0   )r   rf   r   r   clone_linkslinks         r   get_clone_urlzBitbucketClient.get_clone_urlw  sX     gr*ii, 	(Dxx8+xx''	( r   )NNN)Nr"   )N)   N)   )https)__name__
__module____qualname____doc__re   r   r   r   rB   r   r]   r`   rh   ry   r   boolr   r2   r   r   r    r   r   r   r      s2   H((# (( ((3 ((be ((T4c 48D> 4RU 4bf 4l'd4j 'RS Xd^ "C HSM :;3 ; ;xPT~ ;z 3  @R  W[  DC s PS _c </ /3 /QU /bt s RU r   r   )r   r   loggingr-   typingr   r   r   r   r   urllib.parser   r	   	getLoggerr   r4   r   r   r   r   <module>r      s?       ' ' ( +			8	$z zr   