"""API client for Prismor security scanning service."""

import os
import re
import requests
from typing import Optional, Dict, Any
from urllib.parse import urlparse


class PrismorAPIError(Exception):
    """Custom exception for Prismor API errors."""
    pass


def parse_github_repo(repo_input: str) -> str:
    """Extract user/repo_name from various GitHub URL formats or return as-is if already in correct format.
    
    This function handles multiple GitHub URL formats:
    - user/repo_name (already in correct format)
    - https://github.com/user/repo_name
    - https://www.github.com/user/repo_name
    - http://github.com/user/repo_name
    - http://www.github.com/user/repo_name
    - github.com/user/repo_name
    - www.github.com/user/repo_name
    - git@github.com:user/repo_name.git
    - https://github.com/user/repo_name.git
    - https://github.com/user/repo_name/
    - https://github.com/user/repo_name#branch
    - https://github.com/user/repo_name/tree/branch
    - https://github.com/user/repo_name/blob/branch/file
    
    Args:
        repo_input: Repository input in any of the supported formats
        
    Returns:
        Repository in "user/repo_name" format
        
    Raises:
        PrismorAPIError: If the input format is not recognized or invalid
    """
    if not repo_input or not isinstance(repo_input, str):
        raise PrismorAPIError("Repository input cannot be empty")
    
    repo_input = repo_input.strip()
    
    # If it's already in user/repo format (no protocol, no domain), return as-is
    if "/" in repo_input and not any(repo_input.startswith(prefix) for prefix in 
                                    ["http://", "https://", "git@", "github.com", "www.github.com"]):
        # Validate it has exactly one slash and both parts are non-empty
        parts = repo_input.split("/")
        if len(parts) == 2 and parts[0] and parts[1]:
            return repo_input
        else:
            raise PrismorAPIError(f"Invalid repository format: {repo_input}. Expected 'user/repo_name'")
    
    # Handle SSH format: git@github.com:user/repo.git
    if repo_input.startswith("git@github.com:"):
        repo_part = repo_input[15:]  # Remove "git@github.com:"
        # Remove .git suffix if present
        if repo_part.endswith(".git"):
            repo_part = repo_part[:-4]
        if "/" in repo_part:
            return repo_part
        else:
            raise PrismorAPIError(f"Invalid SSH repository format: {repo_input}")
    
    # Handle HTTP/HTTPS URLs
    if repo_input.startswith(("http://", "https://")):
        try:
            parsed = urlparse(repo_input)
            hostname = parsed.hostname.lower()
            
            # Check if it's a GitHub URL
            if hostname in ["github.com", "www.github.com"]:
                path = parsed.path.strip("/")
                
                # Remove .git suffix if present
                if path.endswith(".git"):
                    path = path[:-4]
                
                # Split path and extract user/repo
                path_parts = path.split("/")
                if len(path_parts) >= 2:
                    user = path_parts[0]
                    repo = path_parts[1]
                    
                    # Handle special GitHub paths like /tree/branch, /blob/branch/file
                    if len(path_parts) > 2 and path_parts[2] in ["tree", "blob"]:
                        # This is a branch/file reference, just take user/repo
                        pass
                    
                    if user and repo:
                        return f"{user}/{repo}"
                    else:
                        raise PrismorAPIError(f"Invalid GitHub URL format: {repo_input}")
                else:
                    raise PrismorAPIError(f"Invalid GitHub URL format: {repo_input}")
            else:
                raise PrismorAPIError(f"Not a GitHub URL: {repo_input}")
                
        except Exception as e:
            raise PrismorAPIError(f"Failed to parse URL: {repo_input}. Error: {str(e)}")
    
    # Handle bare domain formats: github.com/user/repo or www.github.com/user/repo
    if repo_input.startswith(("github.com/", "www.github.com/")):
        # Remove domain prefix
        if repo_input.startswith("github.com/"):
            repo_part = repo_input[11:]  # Remove "github.com/"
        else:  # www.github.com/
            repo_part = repo_input[15:]  # Remove "www.github.com/"
        
        # Remove .git suffix if present
        if repo_part.endswith(".git"):
            repo_part = repo_part[:-4]
        
        # Remove trailing slash
        repo_part = repo_part.rstrip("/")
        
        # Split and validate
        parts = repo_part.split("/")
        if len(parts) >= 2:
            user = parts[0]
            repo = parts[1]
            
            # Handle special GitHub paths like /tree/branch, /blob/branch/file
            if len(parts) > 2 and parts[2] in ["tree", "blob"]:
                # This is a branch/file reference, just take user/repo
                pass
            
            if user and repo:
                return f"{user}/{repo}"
            else:
                raise PrismorAPIError(f"Invalid repository format: {repo_input}")
        else:
            raise PrismorAPIError(f"Invalid repository format: {repo_input}")
    
    # If we get here, the format is not recognized
    raise PrismorAPIError(
        f"Unrecognized repository format: {repo_input}. "
        "Supported formats: 'user/repo', 'https://github.com/user/repo', "
        "'git@github.com:user/repo.git', or 'github.com/user/repo'"
    )


class PrismorClient:
    """Client for interacting with Prismor API."""
    
    def __init__(self, api_key: Optional[str] = None):
        """Initialize the Prismor API client.
        
        Args:
            api_key: Prismor API key. If not provided, will look for PRISMOR_API_KEY env var.
        """
        self.api_key = api_key or os.environ.get("PRISMOR_API_KEY")
        if not self.api_key:
            raise PrismorAPIError(
                "PRISMOR_API_KEY environment variable is not set. "
                "Please specify your API key. You can generate one for free at https://www.prismor.dev/cli\n"
                "Set it with: export PRISMOR_API_KEY=your_api_key"
            )
        
        # self.base_url = "http://localhost:3000"
        self.base_url = "https://prismor.dev"
        self.headers = {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json"
        }
    
    def authenticate(self) -> Dict[str, Any]:
        """Authenticate with the Prismor API using the API key.
        
        Returns:
            Dictionary containing user information and repositories
            
        Raises:
            PrismorAPIError: If authentication fails
        """
        try:
            response = requests.post(
                f"{self.base_url}/api/cli/auth",
                json={"apiKey": self.api_key},
                headers={"Content-Type": "application/json"},
                timeout=30
            )
            
            if response.status_code == 401:
                raise PrismorAPIError("Invalid API key. Please check your PRISMOR_API_KEY.")
            
            if response.status_code == 400:
                raise PrismorAPIError("API key is required.")
            
            if response.status_code >= 400:
                error_msg = response.json().get("error", "Authentication failed")
                raise PrismorAPIError(f"Authentication error: {error_msg}")
            
            response.raise_for_status()
            return response.json()
            
        except requests.exceptions.Timeout:
            raise PrismorAPIError("Authentication request timed out.")
        except requests.exceptions.ConnectionError:
            raise PrismorAPIError(
                "Failed to connect to Prismor API. Please check your internet connection."
            )
        except requests.exceptions.RequestException as e:
            raise PrismorAPIError(f"Authentication request failed: {str(e)}")
    
    def normalize_repo_url(self, repo: str) -> str:
        """Normalize repository input to a full GitHub URL.
        
        Args:
            repo: Repository in various formats (username/repo, GitHub URL, etc.)
            
        Returns:
            Full GitHub repository URL
        """
        # Use the comprehensive parser to extract user/repo_name
        repo_name = parse_github_repo(repo)
        
        # Convert to full GitHub URL
        return f"https://github.com/{repo_name}"
    
    def scan(
        self,
        repo: str,
        scan: bool = False,
        sbom: bool = False,
        detect_secret: bool = False,
        fullscan: bool = False,
        branch: Optional[str] = None
    ) -> Dict[str, Any]:
        """Perform security scan on a GitHub repository.
        
        Args:
            repo: Repository URL or username/repo format
            scan: Enable vulnerability scanning
            sbom: Enable SBOM generation
            detect_secret: Enable secret detection
            fullscan: Enable all scan types
            branch: Specific branch to scan (defaults to main/master)
            
        Returns:
            Dictionary containing scan results
        """
        repo_url = self.normalize_repo_url(repo)
        
        # First authenticate to get user info
        auth_response = self.authenticate()
        user_info = auth_response.get("user", {})
        
        # Prepare request payload for CLI scan
        payload = {
            "repo_url": repo_url,
            "api_key": self.api_key,
            "scan": scan or fullscan,
            "sbom": sbom or fullscan,
            "detect_secret": detect_secret or fullscan,
            "fullscan": fullscan,
            "branch": branch
        }
        
        try:
            # Note: Vulnerability scans now run asynchronously and can take up to 10 minutes
            # The web API handles polling internally, so we just need a longer timeout
            response = requests.post(
                f"{self.base_url}/api/cli/scan",
                json=payload,
                headers={"Content-Type": "application/json"},
                timeout=600  # 10 minute timeout to accommodate async vulnerability scans
            )
            
            if response.status_code == 401:
                error_data = response.json()
                if error_data.get("action") == "integrate_github":
                    raise PrismorAPIError(
                        f"{error_data.get('message', 'GitHub integration required')}\n"
                        f"Please visit: {error_data.get('integration_url', 'https://prismor.dev/dashboard')}"
                    )
                raise PrismorAPIError("Invalid API key. Please check your PRISMOR_API_KEY.")
            
            if response.status_code == 404:
                raise PrismorAPIError("CLI scan endpoint not found. Please check if CLI endpoints are available.")
            
            if response.status_code >= 400:
                error_msg = response.json().get("error", "Unknown error")
                raise PrismorAPIError(f"API error: {error_msg}")
            
            response.raise_for_status()
            result = response.json()
            
            # Handle the new response format from CLI endpoint
            if result.get("ok") and "results" in result:
                return result["results"]
            return result
            
        except requests.exceptions.Timeout:
            raise PrismorAPIError(
                "Request timed out. The repository scan is taking longer than expected. "
                "Large repositories may require more time. Please try again or check the dashboard for results."
            )
        except requests.exceptions.ConnectionError:
            raise PrismorAPIError(
                "Failed to connect to Prismor API. Please check your internet connection."
            )
        except requests.exceptions.RequestException as e:
            raise PrismorAPIError(f"Request failed: {str(e)}")
    
    def start_vulnerability_scan(
        self,
        repo: str,
        branch: Optional[str] = None,
        github_token: Optional[str] = None
    ) -> Dict[str, Any]:
        """Start a vulnerability scan and return immediately with a job_id.
        
        This method directly calls the backend API to start an async scan.
        Use check_scan_status() to poll for completion.
        
        Args:
            repo: Repository URL or username/repo format
            branch: Specific branch to scan (defaults to main)
            github_token: Optional GitHub token. If not provided, will try to get from env var GITHUB_TOKEN
            
        Returns:
            Dictionary containing job_id and status information
            
        Raises:
            PrismorAPIError: If request fails
        """
        # Directly call the backend API
        backend_url = os.environ.get(
            "PRISMOR_BACKEND_URL", 
            "https://2dlxuia6i5.execute-api.us-east-1.amazonaws.com/prod"
        )
        
        repo_url = self.normalize_repo_url(repo)
        
        # Get GitHub token from parameter, env var, or raise error
        gh_token = github_token or os.environ.get("GITHUB_TOKEN")
        
        if not gh_token:
            raise PrismorAPIError(
                "GitHub token required. Provide it as a parameter, set GITHUB_TOKEN environment variable, "
                "or use 'prismor --scan <repo> --scan' which handles authentication automatically."
            )
        
        try:
            response = requests.post(
                f"{backend_url}/scan",
                json={
                    "repo_url": repo_url,
                    "token": gh_token,
                    "branch": branch or "main"
                },
                headers={"Content-Type": "application/json"},
                timeout=30
            )
            
            if response.status_code >= 400:
                error_msg = response.json().get("error", "Unknown error")
                raise PrismorAPIError(f"API error: {error_msg}")
            
            response.raise_for_status()
            return response.json()
            
        except requests.exceptions.Timeout:
            raise PrismorAPIError("Request timed out.")
        except requests.exceptions.ConnectionError:
            raise PrismorAPIError(
                "Failed to connect to Prismor API. Please check your internet connection."
            )
        except requests.exceptions.RequestException as e:
            raise PrismorAPIError(f"Request failed: {str(e)}")
    
    def check_scan_status(self, job_id: str) -> Dict[str, Any]:
        """Check the status of a vulnerability scan job.
        
        This method directly calls the backend API to check scan status.
        Use this when you have a job_id from starting an async scan.
        
        Args:
            job_id: The job ID returned from starting a scan
            
        Returns:
            Dictionary containing scan status and results if completed
            
        Raises:
            PrismorAPIError: If request fails
        """
        # Directly call the backend API for status check
        backend_url = os.environ.get(
            "PRISMOR_BACKEND_URL", 
            "https://2dlxuia6i5.execute-api.us-east-1.amazonaws.com/prod"
        )
        
        try:
            response = requests.get(
                f"{backend_url}/scan/status/{job_id}",
                headers={"Content-Type": "application/json"},
                timeout=30
            )
            
            if response.status_code == 404:
                raise PrismorAPIError(f"Scan job '{job_id}' not found.")
            
            if response.status_code >= 400:
                error_msg = response.json().get("error", "Unknown error")
                raise PrismorAPIError(f"API error: {error_msg}")
            
            response.raise_for_status()
            return response.json()
            
        except requests.exceptions.Timeout:
            raise PrismorAPIError("Request timed out.")
        except requests.exceptions.ConnectionError:
            raise PrismorAPIError(
                "Failed to connect to Prismor API. Please check your internet connection."
            )
        except requests.exceptions.RequestException as e:
            raise PrismorAPIError(f"Request failed: {str(e)}")
    
    def get_repositories(self) -> Dict[str, Any]:
        """Get user's repositories.
        
        Returns:
            Dictionary containing user repositories
            
        Raises:
            PrismorAPIError: If request fails
        """
        auth_response = self.authenticate()
        user_info = auth_response.get("user", {})
        return {
            "repositories": user_info.get("repositories", []),
            "user": {
                "id": user_info.get("id"),
                "email": user_info.get("email"),
                "name": user_info.get("name")
            }
        }
    
    def get_repository_by_name(self, repo_name: str) -> Dict[str, Any]:
        """Get repository ID by repository name.
        
        Args:
            repo_name: Repository name (e.g., "username/repo")
            
        Returns:
            Dictionary containing repository information including ID
            
        Raises:
            PrismorAPIError: If request fails
        """
        try:
            response = requests.post(
                f"{self.base_url}/api/repositories/by-name",
                json={
                    "apiKey": self.api_key,
                    "repoName": repo_name
                },
                headers={"Content-Type": "application/json"},
                timeout=30
            )
            
            if response.status_code == 401:
                raise PrismorAPIError("Invalid API key. Please check your PRISMOR_API_KEY.")
            
            if response.status_code == 404:
                raise PrismorAPIError(f"Repository '{repo_name}' not found.")
            
            if response.status_code >= 400:
                error_msg = response.json().get("error", "Unknown error")
                raise PrismorAPIError(f"API error: {error_msg}")
            
            response.raise_for_status()
            return response.json()
            
        except requests.exceptions.Timeout:
            raise PrismorAPIError("Request timed out.")
        except requests.exceptions.ConnectionError:
            raise PrismorAPIError(
                "Failed to connect to Prismor API. Please check your internet connection."
            )
        except requests.exceptions.RequestException as e:
            raise PrismorAPIError(f"Request failed: {str(e)}")

