"""
Check for secret leaks (API keys, passwords, etc.).
"""

import os
import re
from pathlib import Path
from typing import Dict, List, Optional, Any, Tuple, Set

from vulnerability_checker.checks.base import BaseCheck
from vulnerability_checker.models import CheckResult, CheckStatus, VulnerabilityIssue


class SecretLeakCheck(BaseCheck):
    """
    Check for secret leaks (API keys, passwords, etc.).
    """
    
    name = "secret-leak"
    description = "Check for secret leaks (API keys, passwords, etc.)"
    
    # Patterns to search for potential secrets
    SECRET_PATTERNS = [
        # Private keys
        (r"-----BEGIN( RSA)? PRIVATE KEY-----", "Private Key", "critical"),
        
        # Access tokens
        (r"(access_token|access-token|api[_-]?key|api[_-]?secret|secret[_-]?key)\s*=\s*['\"]([A-Za-z0-9_\-\.]{20,})['\"]", "API Key/Token", "high"),
        
        # AWS keys
        (r"AKIA[0-9A-Z]{16}", "AWS Access Key", "critical"),
        
        # GitHub tokens
        (r"github_token\s*=\s*['\"]([A-Za-z0-9_\-\.]{36,})['\"]", "GitHub Token", "high"),
        
        # Firebase URL
        (r"https://[a-z0-9-]+\.firebaseio\.com", "Firebase URL", "medium"),
        
        # URL with login/password
        (r"[a-zA-Z]{3,10}://[^/\s:@]+:[^/\s:@]+@[^/\s:@]+", "URL with credentials", "high"),
        
        # Passwords
        (r"password\s*=\s*['\"]([^'\"]{4,})['\"]", "Password", "high"),
        (r"passwd\s*=\s*['\"]([^'\"]{4,})['\"]", "Password", "high"),
        
        # Variables with secrets
        (r"(secret|key|token|password|credential)\s*=\s*['\"]([^'\"]{4,})['\"]", "Secret Variable", "medium"),
    ]
    
    # Exclusions (files that should not be checked)
    EXCLUDE_FILES = {
        "test_", "tests", "mock", ".git", "venv", ".venv", "env", "example", "sample",
    }
    
    def __init__(self, config: Optional[Dict[str, Any]] = None):
        super().__init__(config)
    
    def run(self, target_path: str, **kwargs) -> Tuple[CheckResult, List[VulnerabilityIssue]]:
        """
        Run check for secret leaks.
        
        Args:
            target_path: Path to directory or file for checking
            **kwargs: Additional parameters
            
        Returns:
            Tuple[CheckResult, List[VulnerabilityIssue]]: Check result and list of found vulnerabilities
        """
        self.issues = []
        
        try:
            # Check files for secrets
            path = Path(target_path)
            for file_path in self._find_files_to_check(path):
                self._check_file_for_secrets(file_path)
            
            # Create result
            status = CheckStatus.PASSED if not self.issues else CheckStatus.FAILED
            result = self.create_result(
                status=status,
                message=f"Found {len(self.issues)} potential secret leaks" if self.issues else "No secret leaks detected",
            )
            
            return result, self.issues
            
        except Exception as e:
            # In case of error, return ERROR status
            result = self.create_result(
                status=CheckStatus.ERROR,
                message=f"Error checking for secret leaks: {str(e)}",
            )
            return result, self.issues
    
    def _find_files_to_check(self, path: Path) -> Set[Path]:
        """
        Find files to check for secrets.
        
        Args:
            path: Path to directory or file
            
        Returns:
            Set[Path]: Set of paths to files
        """
        result = set()
        
        # Files that might contain hardcoded secrets
        extensions = {".py", ".json", ".yaml", ".yml", ".ini", ".cfg", ".conf", ".env", ".properties", ".xml"}
        
        if path.is_file():
            if path.suffix in extensions and not self.should_exclude_path(path):
                result.add(path)
            return result
        
        # Traverse directory
        for root, dirs, files in os.walk(path):
            # Filter directories to exclude
            dirs[:] = [d for d in dirs if not self.should_exclude_path(os.path.join(root, d))]
            
            for file in files:
                file_path = Path(root) / file
                if file_path.suffix in extensions and not self.should_exclude_path(file_path):
                    result.add(file_path)
        
        return result
    
    def _check_file_for_secrets(self, file_path: Path) -> None:
        """
        Check file for secrets.
        
        Args:
            file_path: Path to file
        """
        try:
            # Ignore .env.example and similar files
            if "example" in file_path.name or "sample" in file_path.name:
                return
                
            with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
                content = f.read()
            
            lines = content.splitlines()
            
            # Check each line for secrets
            for i, line in enumerate(lines):
                for pattern, secret_type, severity in self.SECRET_PATTERNS:
                    matches = re.finditer(pattern, line, re.IGNORECASE)
                    for match in matches:
                        # Check if it's a variable template (e.g., ${SECRET_KEY})
                        if "${" in match.group(0) or "{{" in match.group(0):
                            continue
                        
                        # Check if it's clearly a test/dummy value
                        if any(x in match.group(0).lower() for x in ["example", "sample", "test", "dummy"]):
                            continue
                        
                        # Add found secret to the list of vulnerabilities
                        self._add_secret_issue(file_path, i + 1, line, secret_type, severity)
                    
        except Exception as e:
            # Skip files with errors
            print(f"Error checking file {file_path}: {str(e)}")
    
    def _add_secret_issue(self, file_path: Path, line_number: int, code_snippet: str, secret_type: str, severity: str) -> None:
        """
        Add found secret to the list of vulnerabilities.
        
        Args:
            file_path: Path to file
            line_number: Line number
            code_snippet: Code fragment
            secret_type: Type of secret
            severity: Severity level
        """
        # Replace secret for display (don't show it fully)
        redacted_snippet = code_snippet
        
        # Simple masking of strings in quotes for the report (don't show the full secret)
        redacted_snippet = re.sub(
            r'(["\'])(?:(?=(\\?))\2.)*?\1',
            lambda m: m.group(0)[:4] + "*****" + m.group(0)[-1],
            redacted_snippet
        )
        
        self.add_issue(
            issue_type=f"Secret Leak: {secret_type}",
            severity=severity,
            description=f"Found potential secret of type {secret_type}",
            file_path=str(file_path),
            line_number=line_number,
            code_snippet=redacted_snippet.strip(),
            confidence="MEDIUM",
            cwe_id="CWE-798",
            owasp_category="A3:2017-Sensitive Data Exposure",
            remediation="Don't store secrets in code. Use environment variables or secure secret storage.",
        )