"""
Permission Analyzer for MCP Security Auditor.

Analyzes MCP server permission scopes and tool annotations
for excessive or dangerous permissions.
"""

import re
from pathlib import Path
from typing import List

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


class PermissionAnalyzer(BaseAnalyzer):
    """
    Analyzes MCP server permissions and capabilities.
    
    Checks for:
    - Excessive permission scopes
    - Missing tool annotations (readOnlyHint, destructiveHint)
    - Dangerous permission combinations
    - Filesystem/network access patterns
    - System-level permissions
    """
    
    name = "permissions"
    
    # High-risk permission patterns
    HIGH_RISK_PERMISSIONS = [
        ("filesystem.root", "Root filesystem access"),
        ("filesystem.write", "Filesystem write access"),
        ("network.unrestricted", "Unrestricted network access"),
        ("system.shell", "Shell command execution"),
        ("system.env", "Environment variable access"),
        ("system.process", "Process management"),
        ("database.admin", "Database admin access"),
        ("credentials.read", "Credential reading"),
    ]
    
    # Patterns that indicate permission requirements in code
    PERMISSION_INDICATORS = {
        "python": [
            (r"os\.system|subprocess\.|Popen", "system.shell", "Shell command execution"),
            (r"open\s*\([^)]*['\"]w|write\s*\(|writeFile", "filesystem.write", "File writing"),
            (r"os\.remove|os\.unlink|shutil\.rmtree", "filesystem.delete", "File deletion"),
            (r"os\.environ|getenv", "system.env", "Environment access"),
            (r"socket\.|httpx\.|requests\.|aiohttp\.", "network.external", "External network"),
            (r"sqlite3|psycopg|pymysql|sqlalchemy", "database.access", "Database access"),
        ],
        "typescript": [
            (r"child_process|exec\(|spawn\(", "system.shell", "Shell command execution"),
            (r"fs\.write|writeFile|createWriteStream", "filesystem.write", "File writing"),
            (r"fs\.unlink|fs\.rmdir|rimraf", "filesystem.delete", "File deletion"),
            (r"process\.env", "system.env", "Environment access"),
            (r"fetch\(|axios\.|http\.|https\.", "network.external", "External network"),
            (r"mysql|postgres|mongodb|redis", "database.access", "Database access"),
        ],
        "javascript": [
            (r"child_process|exec\(|spawn\(", "system.shell", "Shell command execution"),
            (r"fs\.write|writeFile|createWriteStream", "filesystem.write", "File writing"),
            (r"fs\.unlink|fs\.rmdir|rimraf", "filesystem.delete", "File deletion"),
            (r"process\.env", "system.env", "Environment access"),
            (r"fetch\(|axios\.|http\.|https\.", "network.external", "External network"),
            (r"mysql|postgres|mongodb|redis", "database.access", "Database access"),
        ]
    }
    
    async def analyze(self, path: str, server_info: MCPServerInfo) -> List[Finding]:
        """Analyze permissions and tool annotations."""
        findings = []
        path = Path(path)
        
        # Analyze tool annotations
        findings.extend(self._analyze_tool_annotations(server_info.tools, path))
        
        # Analyze implicit permissions from code
        findings.extend(await self._analyze_implicit_permissions(path, server_info))
        
        # Check for forbidden permissions
        findings.extend(self._check_forbidden_permissions(server_info, path))
        
        # Check for permission escalation patterns
        findings.extend(await self._check_permission_escalation(path, server_info))
        
        return findings
    
    def _analyze_tool_annotations(self, tools: List[ToolDefinition], base_path: Path) -> List[Finding]:
        """Analyze tool annotations for missing or incorrect values."""
        findings = []
        
        for tool in tools:
            # Check for missing annotations
            if not tool.annotations:
                findings.append(Finding(
                    title=f"Tool '{tool.name}' missing annotations",
                    description=f"Tool '{tool.name}' does not have any MCP annotations defined. "
                               f"Annotations help AI models understand tool capabilities and risks.",
                    severity="medium",
                    category="permissions",
                    analyzer=self.name,
                    location=CodeLocation(
                        file=tool.file_path or "unknown",
                        line_start=tool.line_number or 0,
                        line_end=tool.line_number or 0
                    ) if tool.file_path else None,
                    remediation=Remediation(
                        description="Add annotations to your tool decorator specifying readOnlyHint, destructiveHint, idempotentHint, and openWorldHint.",
                        code_example='@mcp.tool(\n    name="my_tool",\n    annotations={\n        "readOnlyHint": True,\n        "destructiveHint": False,\n        "idempotentHint": True,\n        "openWorldHint": False\n    }\n)'
                    ),
                    confidence="high"
                ))
            
            # Check for destructive tools without explicit annotation
            destructive_keywords = ['delete', 'remove', 'drop', 'destroy', 'clear', 'reset', 'wipe']
            if any(kw in tool.name.lower() for kw in destructive_keywords):
                if not tool.annotations.get('destructiveHint', False):
                    findings.append(Finding(
                        title=f"Destructive tool '{tool.name}' not marked as destructive",
                        description=f"Tool '{tool.name}' appears to perform destructive operations but is not marked with destructiveHint=True.",
                        severity="high",
                        category="permissions",
                        analyzer=self.name,
                        location=CodeLocation(
                            file=tool.file_path or "unknown",
                            line_start=tool.line_number or 0,
                            line_end=tool.line_number or 0
                        ) if tool.file_path else None,
                        remediation=Remediation(
                            description="Set destructiveHint=True in the tool annotations to warn AI models about destructive capabilities.",
                            code_example='annotations={"destructiveHint": True}'
                        ),
                        confidence="high"
                    ))
            
            # Check for write operations not marked
            write_keywords = ['write', 'create', 'update', 'modify', 'set', 'save', 'put', 'post']
            if any(kw in tool.name.lower() for kw in write_keywords):
                if tool.annotations.get('readOnlyHint', False):
                    findings.append(Finding(
                        title=f"Write tool '{tool.name}' incorrectly marked as read-only",
                        description=f"Tool '{tool.name}' appears to perform write operations but is marked with readOnlyHint=True.",
                        severity="high",
                        category="permissions",
                        analyzer=self.name,
                        location=CodeLocation(
                            file=tool.file_path or "unknown",
                            line_start=tool.line_number or 0,
                            line_end=tool.line_number or 0
                        ) if tool.file_path else None,
                        remediation=Remediation(
                            description="Set readOnlyHint=False for tools that modify data."
                        ),
                        confidence="high"
                    ))
        
        return findings
    
    async def _analyze_implicit_permissions(self, path: Path, server_info: MCPServerInfo) -> List[Finding]:
        """Analyze code for implicit permission requirements."""
        findings = []
        
        # Get appropriate patterns for the language
        patterns = self.PERMISSION_INDICATORS.get(
            server_info.language, 
            self.PERMISSION_INDICATORS["python"]
        )
        
        # Determine file extensions
        if server_info.language == "python":
            extensions = ["*.py"]
        else:
            extensions = ["*.ts", "*.js", "*.mjs"]
        
        detected_permissions = set()
        
        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
                
                relative_path = str(file_path.relative_to(path))
                lines = content.split('\n')
                
                for pattern, permission, description in patterns:
                    try:
                        regex = re.compile(pattern, re.IGNORECASE)
                        for i, line in enumerate(lines, 1):
                            if regex.search(line):
                                # Avoid duplicate findings for same permission
                                key = (permission, relative_path)
                                if key not in detected_permissions:
                                    detected_permissions.add(key)
                                    
                                    # Determine severity based on permission type
                                    severity = "high" if permission in ["system.shell", "filesystem.delete"] else "medium"
                                    
                                    findings.append(Finding(
                                        title=f"Implicit {permission} permission detected",
                                        description=f"Code requires {permission} permission for {description}. "
                                                   f"Ensure this capability is documented and necessary.",
                                        severity=severity,
                                        category="permissions",
                                        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=f"Document the {permission} requirement in your MCP server manifest "
                                                       f"and ensure it's necessary for your use case."
                                        ),
                                        confidence="medium"
                                    ))
                                break  # Only report first occurrence per file
                    except re.error:
                        continue
        
        return findings
    
    def _check_forbidden_permissions(self, server_info: MCPServerInfo, path: Path) -> List[Finding]:
        """Check for forbidden permissions in declared permissions."""
        findings = []
        
        for permission in server_info.permissions:
            if permission.name in self.config.forbidden_permissions:
                findings.append(Finding(
                    title=f"Forbidden permission: {permission.name}",
                    description=f"The MCP server declares forbidden permission '{permission.name}'. "
                               f"This permission is not allowed in restricted environments.",
                    severity="critical",
                    category="permissions",
                    analyzer=self.name,
                    location=CodeLocation(
                        file=permission.file_path or "manifest",
                        line_start=0,
                        line_end=0
                    ) if permission.file_path else None,
                    remediation=Remediation(
                        description=f"Remove the '{permission.name}' permission or find an alternative approach "
                                   f"that doesn't require elevated privileges."
                    ),
                    confidence="high"
                ))
        
        return findings
    
    async def _check_permission_escalation(self, path: Path, server_info: MCPServerInfo) -> List[Finding]:
        """Check for permission escalation patterns."""
        findings = []
        
        # Patterns that might indicate permission escalation attempts
        escalation_patterns = [
            (r"sudo|doas|pkexec", "Privilege escalation via sudo"),
            (r"chmod\s+[0-7]*7|chmod\s+\+[rwx]", "Permission modification"),
            (r"chown|chgrp", "Ownership modification"),
            (r"setuid|setgid|setcap", "Capability modification"),
            (r"mount\s|umount\s", "Mount operations"),
            (r"/etc/passwd|/etc/shadow", "System file access"),
            (r"iptables|firewalld|ufw", "Firewall modification"),
        ]
        
        # Scan source files
        if server_info.language == "python":
            extensions = ["*.py"]
        else:
            extensions = ["*.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
                
                relative_path = str(file_path.relative_to(path))
                lines = content.split('\n')
                
                for pattern, description in escalation_patterns:
                    try:
                        regex = re.compile(pattern, re.IGNORECASE)
                        for i, line in enumerate(lines, 1):
                            if regex.search(line):
                                findings.append(Finding(
                                    title=f"Potential privilege escalation: {description}",
                                    description=f"Detected pattern that may indicate privilege escalation: {description}. "
                                               f"This could allow unauthorized access to system resources.",
                                    severity="critical",
                                    category="permissions",
                                    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="Remove privilege escalation commands. MCP servers should not "
                                                   "require elevated system privileges."
                                    ),
                                    cwe_id="CWE-269",
                                    confidence="high"
                                ))
                                break
                    except re.error:
                        continue
        
        return findings
