"""
Configuration Analyzer for MCP Security Auditor.

Analyzes MCP server configuration files for security misconfigurations.
"""

import re
import json
from pathlib import Path
from typing import List, Dict, Any, Optional

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


class ConfigurationAnalyzer(BaseAnalyzer):
    """
    Analyzes configuration files for security issues.
    
    Checks for:
    - Insecure default settings
    - Missing security configurations
    - Overly permissive settings
    - Debug mode enabled
    - Exposed sensitive paths
    """
    
    name = "config"
    
    # Configuration files to analyze
    CONFIG_FILES = [
        "package.json",
        "pyproject.toml",
        "setup.py",
        "setup.cfg",
        ".env",
        ".env.example",
        "config.json",
        "config.yaml",
        "config.yml",
        "settings.json",
        "settings.yaml",
        "settings.py",
        "mcp.json",
        "mcp.yaml",
        "manifest.json",
    ]
    
    # Dangerous configuration patterns
    DANGEROUS_CONFIGS = {
        # Debug/development settings
        "debug": [
            (r'"debug"\s*:\s*true', "Debug mode enabled"),
            (r'DEBUG\s*=\s*True', "Debug mode enabled"),
            (r'"development"\s*:\s*true', "Development mode enabled"),
            (r'NODE_ENV.*development', "Node environment set to development"),
        ],
        
        # Security misconfigurations
        "security": [
            (r'"allowAll"\s*:\s*true', "Allow all permissions enabled"),
            (r'"skipValidation"\s*:\s*true', "Validation skipping enabled"),
            (r'"disableSecurity"\s*:\s*true', "Security disabled"),
            (r'"trustProxy"\s*:\s*true', "Trust proxy enabled without restrictions"),
            (r'"cors"\s*:\s*\{\s*"origin"\s*:\s*"\*"', "CORS allows all origins"),
            (r'CORS.*\*', "CORS allows all origins"),
            (r'"allowedOrigins"\s*:\s*\[?\s*"\*"', "All origins allowed"),
        ],
        
        # Exposed paths
        "paths": [
            (r'/root/', "Root path access"),
            (r'/etc/', "System config path"),
            (r'/var/log/', "System log path"),
            (r'C:\\\\Windows', "Windows system path"),
            (r'\.\./', "Path traversal pattern"),
        ],
        
        # Insecure protocols
        "protocols": [
            (r'"http://', "Insecure HTTP URL in config"),
            (r'"ftp://', "Insecure FTP URL in config"),
            (r'"telnet://', "Insecure Telnet URL"),
        ],
    }
    
    # Required security configurations
    REQUIRED_CONFIGS = {
        "package.json": [
            ("engines", "low", "Missing Node.js version specification"),
        ],
        "pyproject.toml": [
            ("python", "low", "Missing Python version specification"),
        ],
    }
    
    async def analyze(self, path: str, server_info: MCPServerInfo) -> List[Finding]:
        """Analyze configuration files for security issues."""
        findings = []
        path = Path(path)
        
        # Analyze known config files
        for config_file in self.CONFIG_FILES:
            config_path = path / config_file
            if config_path.exists():
                findings.extend(await self._analyze_config_file(config_path, path))
        
        # Find and analyze all YAML/JSON configs
        for pattern in ["*.json", "*.yaml", "*.yml", "*.toml"]:
            for file_path in path.glob(pattern):
                if file_path.name not in self.CONFIG_FILES:
                    if not self._should_skip_file(file_path):
                        findings.extend(await self._analyze_config_file(file_path, path))
        
        # Check for missing security configurations
        findings.extend(self._check_missing_configs(path, server_info))
        
        # Analyze environment file patterns
        findings.extend(self._analyze_env_files(path))
        
        return findings
    
    async def _analyze_config_file(self, file_path: Path, base_path: Path) -> List[Finding]:
        """Analyze a single configuration file."""
        findings = []
        
        content = self._read_file_content(file_path)
        if not content:
            return findings
        
        relative_path = str(file_path.relative_to(base_path))
        lines = content.split('\n')
        
        # Check dangerous patterns
        for category, patterns in self.DANGEROUS_CONFIGS.items():
            for pattern, description in patterns:
                try:
                    regex = re.compile(pattern, re.IGNORECASE)
                    for i, line in enumerate(lines, 1):
                        if regex.search(line):
                            severity = self._get_severity_for_category(category)
                            
                            findings.append(Finding(
                                title=f"Configuration issue: {description}",
                                description=f"Potentially insecure configuration detected in {relative_path}: {description}",
                                severity=severity,
                                category="configuration",
                                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=self._get_remediation_for_issue(description)
                                ),
                                confidence="high"
                            ))
                except re.error:
                    continue
        
        # Special handling for specific config files
        if file_path.name == "package.json":
            findings.extend(self._analyze_package_json(file_path, content, relative_path))
        elif file_path.name == "pyproject.toml":
            findings.extend(self._analyze_pyproject(file_path, content, relative_path))
        
        return findings
    
    def _analyze_package_json(self, file_path: Path, content: str, relative_path: str) -> List[Finding]:
        """Analyze package.json for specific issues."""
        findings = []
        
        try:
            data = json.loads(content)
            
            # Check for postinstall scripts (potential supply chain risk)
            scripts = data.get("scripts", {})
            dangerous_scripts = ["postinstall", "preinstall", "install"]
            
            for script_name in dangerous_scripts:
                if script_name in scripts:
                    script_content = scripts[script_name]
                    # Check for suspicious commands
                    suspicious_patterns = [
                        r'curl\s+', r'wget\s+', r'bash\s+-c',
                        r'eval\s+', r'node\s+-e', r'python\s+-c'
                    ]
                    
                    for pattern in suspicious_patterns:
                        if re.search(pattern, script_content):
                            findings.append(Finding(
                                title=f"Suspicious {script_name} script",
                                description=f"The {script_name} script contains potentially dangerous commands "
                                           f"that could be used for supply chain attacks.",
                                severity="high",
                                category="configuration",
                                analyzer=self.name,
                                location=CodeLocation(
                                    file=relative_path,
                                    line_start=0,
                                    line_end=0
                                ),
                                remediation=Remediation(
                                    description=f"Review the {script_name} script and ensure it's necessary "
                                               f"and doesn't execute untrusted code."
                                ),
                                confidence="medium"
                            ))
                            break
            
            # Check for overly broad file patterns in "files"
            files_field = data.get("files", [])
            if "**/*" in files_field or "*" in files_field:
                findings.append(Finding(
                    title="Overly broad file inclusion",
                    description="The 'files' field includes all files, which may expose sensitive data.",
                    severity="low",
                    category="configuration",
                    analyzer=self.name,
                    location=CodeLocation(
                        file=relative_path,
                        line_start=0,
                        line_end=0
                    ),
                    remediation=Remediation(
                        description="Specify explicit file patterns to include only necessary files."
                    ),
                    confidence="medium"
                ))
            
            # Check for missing private field
            if not data.get("private") and "dependencies" in data:
                findings.append(Finding(
                    title="Package not marked as private",
                    description="Package is not marked as private, which could lead to accidental publication.",
                    severity="low",
                    category="configuration",
                    analyzer=self.name,
                    location=CodeLocation(
                        file=relative_path,
                        line_start=0,
                        line_end=0
                    ),
                    remediation=Remediation(
                        description='Add "private": true to package.json to prevent accidental publication.'
                    ),
                    confidence="high"
                ))
        
        except json.JSONDecodeError:
            pass
        
        return findings
    
    def _analyze_pyproject(self, file_path: Path, content: str, relative_path: str) -> List[Finding]:
        """Analyze pyproject.toml for specific issues."""
        findings = []
        
        # Check for common security-related settings
        if "[tool.bandit]" not in content and "[tool.safety]" not in content:
            findings.append(Finding(
                title="No security linting configured",
                description="No security linting tools (bandit, safety) configured in pyproject.toml.",
                severity="info",
                category="configuration",
                analyzer=self.name,
                location=CodeLocation(
                    file=relative_path,
                    line_start=0,
                    line_end=0
                ),
                remediation=Remediation(
                    description="Consider adding security linting tools to your project configuration.",
                    code_example="[tool.bandit]\ntargets = ['src']\n\n[tool.safety]\nignore = []"
                ),
                confidence="high"
            ))
        
        return findings
    
    def _analyze_env_files(self, path: Path) -> List[Finding]:
        """Analyze .env files for security issues."""
        findings = []
        
        for env_file in path.glob(".env*"):
            if env_file.name == ".env.example" or env_file.name == ".env.template":
                continue
            
            if env_file.is_file():
                relative_path = str(env_file.relative_to(path))
                
                # Check if .env is in .gitignore
                gitignore_path = path / ".gitignore"
                env_ignored = False
                
                if gitignore_path.exists():
                    gitignore_content = gitignore_path.read_text()
                    env_ignored = ".env" in gitignore_content or env_file.name in gitignore_content
                
                if not env_ignored:
                    findings.append(Finding(
                        title=f"Environment file may be committed to version control",
                        description=f"File '{relative_path}' may contain secrets and is not ignored by git.",
                        severity="high",
                        category="configuration",
                        analyzer=self.name,
                        location=CodeLocation(
                            file=relative_path,
                            line_start=0,
                            line_end=0
                        ),
                        remediation=Remediation(
                            description="Add .env files to .gitignore to prevent committing secrets.",
                            code_example="# In .gitignore:\n.env\n.env.local\n.env.*.local"
                        ),
                        cwe_id="CWE-312",
                        confidence="high"
                    ))
                
                # Check for sensitive values
                content = self._read_file_content(env_file)
                if content:
                    lines = content.split('\n')
                    for i, line in enumerate(lines, 1):
                        if '=' in line and not line.strip().startswith('#'):
                            key, _, value = line.partition('=')
                            value = value.strip().strip('"\'')
                            
                            # Check for placeholder/example values
                            if value and not any(placeholder in value.lower() for placeholder in 
                                ['your_', 'replace_', 'xxx', 'changeme', 'example', 'placeholder']):
                                
                                # Check if it looks like a real secret
                                if len(value) > 10 and any(key.upper().endswith(suffix) for suffix in 
                                    ['_KEY', '_SECRET', '_TOKEN', '_PASSWORD', '_CREDENTIAL']):
                                    findings.append(Finding(
                                        title=f"Potential secret in environment file",
                                        description=f"Variable '{key.strip()}' appears to contain a real secret value.",
                                        severity="high",
                                        category="secrets",
                                        analyzer=self.name,
                                        location=CodeLocation(
                                            file=relative_path,
                                            line_start=i,
                                            line_end=i
                                        ),
                                        remediation=Remediation(
                                            description="Use a secrets manager or ensure this file is not committed to version control."
                                        ),
                                        cwe_id="CWE-312",
                                        confidence="medium"
                                    ))
        
        return findings
    
    def _check_missing_configs(self, path: Path, server_info: MCPServerInfo) -> List[Finding]:
        """Check for missing security configurations."""
        findings = []
        
        # Check for .gitignore
        if not (path / ".gitignore").exists():
            findings.append(Finding(
                title="Missing .gitignore file",
                description="No .gitignore file found. Sensitive files may be accidentally committed.",
                severity="medium",
                category="configuration",
                analyzer=self.name,
                remediation=Remediation(
                    description="Create a .gitignore file to exclude sensitive files and directories.",
                    code_example="# .gitignore\n.env\nnode_modules/\n__pycache__/\n*.pyc\n.DS_Store"
                ),
                confidence="high"
            ))
        
        # Check for security policy
        security_files = ["SECURITY.md", "security.md", ".github/SECURITY.md"]
        has_security_policy = any((path / f).exists() for f in security_files)
        
        if not has_security_policy:
            findings.append(Finding(
                title="Missing security policy",
                description="No SECURITY.md file found. Consider adding a security policy for vulnerability reporting.",
                severity="info",
                category="configuration",
                analyzer=self.name,
                remediation=Remediation(
                    description="Create a SECURITY.md file with instructions for reporting security vulnerabilities."
                ),
                confidence="high"
            ))
        
        return findings
    
    def _get_severity_for_category(self, category: str) -> str:
        """Get severity level for a configuration category."""
        severity_map = {
            "debug": "medium",
            "security": "high",
            "paths": "high",
            "protocols": "medium",
        }
        return severity_map.get(category, "medium")
    
    def _get_remediation_for_issue(self, description: str) -> str:
        """Get remediation advice for a specific issue."""
        description_lower = description.lower()
        
        if "debug" in description_lower:
            return "Disable debug mode in production environments. Use environment variables to control debug settings."
        elif "cors" in description_lower or "origin" in description_lower:
            return "Configure CORS to only allow specific trusted origins instead of using wildcards."
        elif "http://" in description_lower:
            return "Use HTTPS instead of HTTP for all external connections."
        elif "path" in description_lower:
            return "Avoid accessing system paths. Use relative paths within the application directory."
        elif "validation" in description_lower:
            return "Never skip validation in production. Use proper input validation for all user inputs."
        else:
            return "Review this configuration setting and ensure it follows security best practices."
