"""
Supreme 2 MAX MCP Security Module
Critical security measures for MCP server path validation.

WARNING from spec: "MCP servers run with same permissions as the IDE!"
This module enforces strict filesystem access control.

Security Requirements (Priority: CRITICAL):
1. Input Validation: validate all paths, reject path traversal attacks
2. Path Sandboxing: restrict access to workspace project only
3. No Code Execution: never use eval(), exec(), or shell=True with user input
"""

from __future__ import annotations

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


class SecurityError(Exception):
    """Raised when security validation fails"""
    pass


# Default workspace root; can be overridden via set_workspace_root()
_workspace_root: Optional[Path] = None


def set_workspace_root(path: Path) -> None:
    """Set the workspace root directory for path sandboxing."""
    global _workspace_root
    _workspace_root = path.resolve()


def get_workspace_root() -> Optional[Path]:
    """Return the currently configured workspace root, if any."""
    return _workspace_root


def validate_project_path(path: str, workspace_root: Path) -> Path:
    """
    Validate that requested path is within workspace boundaries.
    Prevents path traversal attacks.
    """
    # Resolve to absolute path
    requested_path = Path(path).resolve()
    workspace_root = workspace_root.resolve()

    # Check if path is within workspace
    try:
        requested_path.relative_to(workspace_root)
    except ValueError:
        raise SecurityError(
            f"Access denied: Path {path} is outside workspace"
        )

    # Prevent symlink escapes
    if requested_path.is_symlink():
        target = requested_path.readlink().resolve()
        try:
            target.relative_to(workspace_root)
        except ValueError:
            raise SecurityError(
                f"Access denied: Symlink {path} points outside workspace"
            )

    return requested_path


def validate_path_components(path: str) -> None:
    """
    Reject paths with dangerous components before resolution.

    Blocks:
    - Relative segments containing ".."
    - Null bytes
    - Excessively long paths
    """
    if "\x00" in path:
        raise SecurityError("Access denied: Path contains null bytes")

    if len(path) > 4096:
        raise SecurityError("Access denied: Path exceeds maximum length")

    # Normalise separators for consistent checking
    normalised = path.replace("\\", "/")

    # Reject explicit traversal patterns
    parts = normalised.split("/")
    for part in parts:
        if part == "..":
            raise SecurityError(
                f"Access denied: Path traversal detected in '{path}'"
            )


def safe_validate_project_path(
    path: str,
    workspace_root: Optional[Path] = None,
) -> Path:
    """
    Full validation pipeline for project_path parameters.

    Combines component-level checks with workspace boundary enforcement.
    If *workspace_root* is ``None``, the module-level default is used.
    When no workspace root is configured at all, only component-level
    validation is applied and the resolved path is returned.

    Returns the resolved, validated ``Path``.
    """
    # 1. Component-level checks (traversal, null bytes, length)
    validate_path_components(path)

    # 2. Determine workspace root
    effective_root = workspace_root or _workspace_root

    if effective_root is not None:
        # 3. Workspace boundary + symlink escape check
        return validate_project_path(path, effective_root)

    # No workspace root configured — resolve and return after basic checks
    return Path(path).resolve()


# ======================================================================
# Secrets Filtering  (Priority: CRITICAL)
# "Never log or return API keys, tokens, or credentials"
# ======================================================================

_SECRET_PATTERNS = [
    # --- Patterns from spec ---
    re.compile(r'AKIA[0-9A-Z]{16}'),                    # AWS keys
    re.compile(r'sk-[a-zA-Z0-9]{48,}'),                 # OpenAI keys
    re.compile(r'ghp_[0-9a-zA-Z]{36}'),                 # GitHub tokens
    # --- Extended patterns ---
    re.compile(r'gho_[0-9a-zA-Z]{36}'),                 # GitHub OAuth tokens
    re.compile(r'ghu_[0-9a-zA-Z]{36}'),                 # GitHub user tokens
    re.compile(r'ghs_[0-9a-zA-Z]{36}'),                 # GitHub server tokens
    re.compile(r'github_pat_[0-9a-zA-Z_]{22,}'),        # GitHub fine-grained PATs
    re.compile(r'sk-ant-[a-zA-Z0-9-]{40,}'),            # Anthropic keys
    re.compile(r'xoxb-[0-9A-Za-z-]+'),                  # Slack bot tokens
    re.compile(r'xoxp-[0-9A-Za-z-]+'),                  # Slack user tokens
    re.compile(r'SG\.[0-9A-Za-z-_]{22}\.[0-9A-Za-z-_]{43}'),  # SendGrid
    re.compile(r'-----BEGIN[\s\S]*?PRIVATE KEY-----'),   # Private keys
    re.compile(
        r'eyJ[A-Za-z0-9-_=]+\.eyJ[A-Za-z0-9-_=]+\.'    # JWT tokens
        r'[A-Za-z0-9-_=]*'
    ),
]

# Rule IDs that indicate a detected secret
_SECRET_RULE_IDS = frozenset({
    'hardcoded-secret',
    'env-secret',
    'hardcoded-password',
    'hardcoded-token',
    'hardcoded-api-key',
    'secret-detected',
    'generic-api-key',
    'private-key',
})

_REDACTED = '[REDACTED]'


def _redact_secrets_in_text(text: str) -> str:
    """Replace any secret pattern occurrences in *text* with [REDACTED]."""
    for pattern in _SECRET_PATTERNS:
        text = pattern.sub(_REDACTED, text)
    return text


def sanitize_scan_results(issues: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
    """
    Remove detected secrets from scan results before returning to LLM.
    Prevents accidental secret exposure in LLM context.
    """
    for issue in issues:
        # --- Rule-id based redaction (from spec) ---
        if issue.get('rule_id') in _SECRET_RULE_IDS:
            # Redact the actual secret value
            issue['code_snippet'] = _REDACTED
            issue['message'] = issue['message'].replace(
                issue.get('secret_value', ''),
                _REDACTED,
            )

        # --- Pattern-based redaction across all text fields ---
        for field in ('code_snippet', 'message', 'remediation'):
            if field in issue and isinstance(issue[field], str):
                issue[field] = _redact_secrets_in_text(issue[field])

        # Remove raw secret_value if present — never expose it
        issue.pop('secret_value', None)

    return issues


def sanitize_analyzed_issues(
    analyzed_issues: List[Dict[str, Any]],
) -> List[Dict[str, Any]]:
    """Sanitize the nested issue dicts inside analyze_findings output."""
    for entry in analyzed_issues:
        inner = entry.get('issue')
        if isinstance(inner, dict):
            sanitize_scan_results([inner])
    return analyzed_issues
