"""
Base analyzer class and Static Code Analyzer.

The static code analyzer detects dangerous patterns, anti-patterns,
and potential security issues in source code.
"""

import re
from abc import ABC, abstractmethod
from pathlib import Path
from typing import List, Optional

from ..core.config import AuditConfig
from ..core.models import (
    Finding, CodeLocation, Remediation, MCPServerInfo
)


class BaseAnalyzer(ABC):
    """Base class for all security analyzers."""
    
    name: str = "base"
    
    def __init__(self, config: AuditConfig):
        self.config = config
    
    @abstractmethod
    async def analyze(self, path: str, server_info: MCPServerInfo) -> List[Finding]:
        """
        Analyze the MCP server for security issues.
        
        Args:
            path: Path to the MCP server directory
            server_info: Parsed information about the MCP server
            
        Returns:
            List of security findings
        """
        pass
    
    def _read_file_content(self, file_path: Path) -> Optional[str]:
        """Safely read file content."""
        try:
            return file_path.read_text(encoding='utf-8')
        except Exception:
            try:
                return file_path.read_text(encoding='latin-1')
            except Exception:
                return None
    
    def _get_code_snippet(self, file_path: Path, line_num: int, context: int = 3) -> Optional[str]:
        """Extract code snippet around a line number."""
        try:
            lines = file_path.read_text().split('\n')
            start = max(0, line_num - context - 1)
            end = min(len(lines), line_num + context)
            
            snippet_lines = []
            for i in range(start, end):
                prefix = ">>> " if i == line_num - 1 else "    "
                snippet_lines.append(f"{i+1:4d} {prefix}{lines[i]}")
            
            return '\n'.join(snippet_lines)
        except Exception:
            return None
    
    def _should_skip_file(self, file_path: Path) -> bool:
        """Check if file should be skipped based on patterns."""
        path_str = str(file_path)
        
        # Skip common non-source directories
        skip_dirs = ['node_modules', '__pycache__', '.git', 'dist', 'build', 'venv', '.venv']
        for skip_dir in skip_dirs:
            if f'/{skip_dir}/' in path_str or path_str.startswith(skip_dir):
                return True
        
        # Skip minified files
        if '.min.' in file_path.name:
            return True
        
        return False


class StaticCodeAnalyzer(BaseAnalyzer):
    """
    Static code analyzer for detecting dangerous patterns.
    
    Analyzes source code for:
    - Dangerous function calls (eval, exec, etc.)
    - Unsafe deserialization
    - SQL injection patterns
    - Command injection patterns
    - Insecure cryptography
    - SSL/TLS issues
    - Prompt injection vulnerabilities
    """
    
    name = "static"
    
    # Remediation suggestions for common issues
    REMEDIATIONS = {
        "eval": Remediation(
            description="Replace eval() with safer alternatives like ast.literal_eval() for data parsing, or use a proper parser for expressions.",
            code_example="# Instead of eval(user_input)\nimport ast\nresult = ast.literal_eval(user_input)  # Only for literals",
            references=["https://cwe.mitre.org/data/definitions/95.html"]
        ),
        "exec": Remediation(
            description="Avoid dynamic code execution. If necessary, use a restricted execution environment or sandbox.",
            references=["https://cwe.mitre.org/data/definitions/78.html"]
        ),
        "subprocess": Remediation(
            description="Use subprocess with shell=False and pass arguments as a list. Validate and sanitize all inputs.",
            code_example="# Instead of subprocess.call(cmd, shell=True)\nsubprocess.run(['command', 'arg1', 'arg2'], shell=False)",
            references=["https://cwe.mitre.org/data/definitions/78.html"]
        ),
        "pickle": Remediation(
            description="Never unpickle data from untrusted sources. Use safer alternatives like JSON for data serialization.",
            code_example="# Instead of pickle.load()\nimport json\ndata = json.load(file)",
            references=["https://cwe.mitre.org/data/definitions/502.html"]
        ),
        "yaml": Remediation(
            description="Always use yaml.safe_load() instead of yaml.load() to prevent code execution.",
            code_example="# Instead of yaml.load(file)\nimport yaml\ndata = yaml.safe_load(file)",
            references=["https://cwe.mitre.org/data/definitions/502.html"]
        ),
        "sql_injection": Remediation(
            description="Use parameterized queries or prepared statements instead of string formatting for SQL queries.",
            code_example="# Instead of f\"SELECT * FROM users WHERE id = {user_id}\"\ncursor.execute(\"SELECT * FROM users WHERE id = ?\", (user_id,))",
            references=["https://cwe.mitre.org/data/definitions/89.html"]
        ),
        "ssl_verify": Remediation(
            description="Never disable SSL certificate verification. If needed for testing, use a proper test certificate.",
            code_example="# Remove verify=False from requests\nrequests.get(url)  # verify=True is default",
            references=["https://cwe.mitre.org/data/definitions/295.html"]
        ),
        "weak_hash": Remediation(
            description="Use stronger hash algorithms like SHA-256 or SHA-3 instead of MD5 or SHA-1.",
            code_example="# Instead of hashlib.md5()\nimport hashlib\nhash_obj = hashlib.sha256(data)",
            references=["https://cwe.mitre.org/data/definitions/328.html"]
        ),
        "prompt_injection": Remediation(
            description="Never concatenate user input directly into prompts. Use input validation, sanitization, and structured prompting.",
            code_example="# Instead of prompt = f\"User said: {user_input}\"\n# Use structured input handling and validation",
            references=["https://owasp.org/www-project-top-10-for-large-language-model-applications/"]
        )
    }
    
    async def analyze(self, path: str, server_info: MCPServerInfo) -> List[Finding]:
        """Analyze source code for dangerous patterns."""
        findings = []
        path = Path(path)
        
        # Determine file extensions to scan
        if server_info.language == "python":
            extensions = ["*.py"]
        elif server_info.language in ["typescript", "javascript"]:
            extensions = ["*.ts", "*.js", "*.mjs", "*.cjs"]
        else:
            extensions = ["*.py", "*.ts", "*.js", "*.mjs"]
        
        for ext in extensions:
            for file_path in path.rglob(ext):
                if self._should_skip_file(file_path):
                    continue
                
                content = self._read_file_content(file_path)
                if not content:
                    continue
                
                file_findings = self._analyze_file(file_path, content, path)
                findings.extend(file_findings)
        
        return findings
    
    def _analyze_file(self, file_path: Path, content: str, base_path: Path) -> List[Finding]:
        """Analyze a single file for dangerous patterns."""
        findings = []
        lines = content.split('\n')
        relative_path = str(file_path.relative_to(base_path))
        
        for pattern in self.config.dangerous_patterns:
            try:
                regex = re.compile(pattern.pattern, re.IGNORECASE | re.MULTILINE)
                
                for i, line in enumerate(lines, 1):
                    if regex.search(line):
                        # Determine remediation based on pattern
                        remediation = self._get_remediation(pattern.pattern)
                        
                        findings.append(Finding(
                            title=pattern.description,
                            description=f"Detected dangerous pattern in {relative_path} at line {i}: {pattern.description}",
                            severity=pattern.severity,
                            category="code_quality",
                            analyzer=self.name,
                            location=CodeLocation(
                                file=relative_path,
                                line_start=i,
                                line_end=i,
                                snippet=self._get_code_snippet(file_path, i)
                            ),
                            remediation=remediation,
                            cwe_id=pattern.cwe_id,
                            confidence="high" if pattern.severity in ["critical", "high"] else "medium"
                        ))
            except re.error:
                # Invalid regex pattern, skip
                continue
        
        # Additional context-aware analysis
        findings.extend(self._analyze_context(file_path, content, lines, relative_path))
        
        return findings
    
    def _analyze_context(self, file_path: Path, content: str, lines: List[str], relative_path: str) -> List[Finding]:
        """Perform context-aware analysis for more accurate findings."""
        findings = []
        
        # Check for insecure random number generation for security purposes
        if re.search(r'import\s+random\b', content):
            # Check if random is used for security-sensitive operations
            if re.search(r'random\.(choice|randint|random).*(?:token|password|key|secret)', content, re.IGNORECASE):
                for i, line in enumerate(lines, 1):
                    if 'random.' in line and any(word in line.lower() for word in ['token', 'password', 'key', 'secret']):
                        findings.append(Finding(
                            title="Insecure random number generation for security",
                            description="Using random module for security-sensitive operations. Use secrets module instead.",
                            severity="high",
                            category="cryptography",
                            analyzer=self.name,
                            location=CodeLocation(
                                file=relative_path,
                                line_start=i,
                                line_end=i,
                                snippet=self._get_code_snippet(file_path, i)
                            ),
                            remediation=Remediation(
                                description="Use the secrets module for cryptographically secure random numbers.",
                                code_example="import secrets\ntoken = secrets.token_hex(32)",
                                references=["https://cwe.mitre.org/data/definitions/330.html"]
                            ),
                            cwe_id="CWE-330",
                            confidence="high"
                        ))
                        break
        
        # Check for hardcoded credentials in config-like structures
        credential_patterns = [
            (r'(?:password|passwd|pwd)\s*[=:]\s*["\'][^"\']{4,}["\']', "Hardcoded password"),
            (r'(?:api_key|apikey|api-key)\s*[=:]\s*["\'][^"\']{8,}["\']', "Hardcoded API key"),
            (r'(?:secret|secret_key)\s*[=:]\s*["\'][^"\']{8,}["\']', "Hardcoded secret"),
        ]
        
        for pattern, title in credential_patterns:
            for i, line in enumerate(lines, 1):
                # Skip comments
                stripped = line.strip()
                if stripped.startswith('#') or stripped.startswith('//'):
                    continue
                
                if re.search(pattern, line, re.IGNORECASE):
                    findings.append(Finding(
                        title=title,
                        description=f"Potential {title.lower()} detected. Use environment variables or secure secret management.",
                        severity="high",
                        category="secrets",
                        analyzer=self.name,
                        location=CodeLocation(
                            file=relative_path,
                            line_start=i,
                            line_end=i,
                            snippet=self._get_code_snippet(file_path, i)
                        ),
                        remediation=Remediation(
                            description="Store sensitive credentials in environment variables or use a secrets manager.",
                            code_example="import os\napi_key = os.environ.get('API_KEY')",
                            references=["https://cwe.mitre.org/data/definitions/798.html"]
                        ),
                        cwe_id="CWE-798",
                        confidence="medium"
                    ))
        
        return findings
    
    def _get_remediation(self, pattern: str) -> Optional[Remediation]:
        """Get remediation suggestion based on pattern."""
        pattern_lower = pattern.lower()
        
        if 'eval' in pattern_lower:
            return self.REMEDIATIONS["eval"]
        elif 'exec' in pattern_lower and 'subprocess' not in pattern_lower:
            return self.REMEDIATIONS["exec"]
        elif 'subprocess' in pattern_lower or 'child_process' in pattern_lower:
            return self.REMEDIATIONS["subprocess"]
        elif 'pickle' in pattern_lower:
            return self.REMEDIATIONS["pickle"]
        elif 'yaml' in pattern_lower and 'safe' not in pattern_lower:
            return self.REMEDIATIONS["yaml"]
        elif 'select' in pattern_lower or 'sql' in pattern_lower:
            return self.REMEDIATIONS["sql_injection"]
        elif 'verify' in pattern_lower and 'false' in pattern_lower:
            return self.REMEDIATIONS["ssl_verify"]
        elif 'md5' in pattern_lower or 'sha1' in pattern_lower:
            return self.REMEDIATIONS["weak_hash"]
        elif 'prompt' in pattern_lower or 'user_input' in pattern_lower:
            return self.REMEDIATIONS["prompt_injection"]
        
        return None
