"""MCP Registry Scanner for MCP Security Audit.

Scans MCPs from a registry/index (JSON of known MCPs), assesses risk based on
permissions, tools exposed, and data access patterns. Includes a builtin
catalog of 20 popular MCPs with metadata.

Usage:
    from mcpaudit.registry_scanner import scan_registry, get_known_mcps
    report = scan_registry("registry.json")
    mcps = get_known_mcps()

CLI: mcpaudit registry scan, registry list, registry assess <name>

Zero external dependencies. Part of mcpaudit v2.3.0.
"""

from __future__ import annotations

import json
from dataclasses import dataclass, field
from datetime import datetime, timezone
from enum import Enum
from pathlib import Path
from typing import Any


# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------

class MCPCategory(Enum):
    """Categories for MCP servers."""
    FILESYSTEM = "filesystem"
    DATABASE = "database"
    NETWORK = "network"
    CODE_EXECUTION = "code_execution"
    AI_ML = "ai_ml"
    CLOUD = "cloud"
    COMMUNICATION = "communication"
    MONITORING = "monitoring"
    SECURITY = "security"
    UTILITY = "utility"
    BROWSER = "browser"
    DATA_PROCESSING = "data_processing"


class RiskLevel(Enum):
    """Risk levels for MCP servers."""
    CRITICAL = "critical"
    HIGH = "high"
    MEDIUM = "medium"
    LOW = "low"
    MINIMAL = "minimal"

    @property
    def score(self) -> int:
        """Numeric score for risk level."""
        return {
            RiskLevel.CRITICAL: 100,
            RiskLevel.HIGH: 75,
            RiskLevel.MEDIUM: 50,
            RiskLevel.LOW: 25,
            RiskLevel.MINIMAL: 10,
        }[self]

    @classmethod
    def from_score(cls, score: int) -> RiskLevel:
        """Determine risk level from a numeric score."""
        if score >= 80:
            return cls.CRITICAL
        if score >= 60:
            return cls.HIGH
        if score >= 40:
            return cls.MEDIUM
        if score >= 20:
            return cls.LOW
        return cls.MINIMAL

    @classmethod
    def from_string(cls, value: str) -> RiskLevel:
        """Parse a risk level string."""
        try:
            return cls(value.lower())
        except ValueError:
            valid = ", ".join(r.value for r in cls)
            raise ValueError(f"Invalid risk level '{value}'. Valid: {valid}")


# Permission risk weights
_PERMISSION_WEIGHTS: dict[str, int] = {
    "file_read": 15,
    "file_write": 25,
    "file_delete": 30,
    "network_access": 20,
    "network_listen": 25,
    "shell_execute": 35,
    "process_spawn": 30,
    "env_read": 10,
    "env_write": 20,
    "db_read": 15,
    "db_write": 25,
    "db_admin": 35,
    "cloud_api": 20,
    "secret_access": 30,
    "system_info": 10,
    "browser_control": 20,
    "email_send": 15,
    "code_execute": 35,
}

# Tool exposure risk weights
_TOOL_RISK_KEYWORDS: dict[str, int] = {
    "execute": 30,
    "shell": 35,
    "eval": 35,
    "run": 25,
    "delete": 25,
    "write": 20,
    "modify": 20,
    "create": 15,
    "upload": 20,
    "download": 15,
    "send": 15,
    "install": 25,
    "deploy": 20,
    "admin": 25,
    "root": 30,
    "sudo": 35,
    "credential": 30,
    "secret": 25,
    "token": 20,
    "password": 25,
}

# Data access risk weights
_DATA_ACCESS_WEIGHTS: dict[str, int] = {
    "credentials": 35,
    "personal_data": 30,
    "financial_data": 30,
    "health_data": 35,
    "system_config": 20,
    "source_code": 15,
    "logs": 10,
    "user_data": 25,
    "api_keys": 35,
    "tokens": 30,
    "environment": 15,
    "database": 20,
}


# ---------------------------------------------------------------------------
# Data models
# ---------------------------------------------------------------------------

@dataclass
class MCPEntry:
    """Metadata for a known MCP server."""

    name: str
    description: str = ""
    repo: str = ""
    category: str = "utility"
    permissions: list[str] = field(default_factory=list)
    tools: list[str] = field(default_factory=list)
    data_access: list[str] = field(default_factory=list)
    risk_level: str = "medium"
    version: str = ""
    maintainer: str = ""
    last_updated: str = ""
    stars: int = 0
    verified: bool = False

    def to_dict(self) -> dict[str, Any]:
        return {
            "name": self.name,
            "description": self.description,
            "repo": self.repo,
            "category": self.category,
            "permissions": self.permissions,
            "tools": self.tools,
            "data_access": self.data_access,
            "risk_level": self.risk_level,
            "version": self.version,
            "maintainer": self.maintainer,
            "last_updated": self.last_updated,
            "stars": self.stars,
            "verified": self.verified,
        }

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> MCPEntry:
        return cls(
            name=data.get("name", ""),
            description=data.get("description", ""),
            repo=data.get("repo", ""),
            category=data.get("category", "utility"),
            permissions=data.get("permissions", []),
            tools=data.get("tools", []),
            data_access=data.get("data_access", []),
            risk_level=data.get("risk_level", "medium"),
            version=data.get("version", ""),
            maintainer=data.get("maintainer", ""),
            last_updated=data.get("last_updated", ""),
            stars=data.get("stars", 0),
            verified=data.get("verified", False),
        )


@dataclass
class RiskFactor:
    """A single risk factor in an assessment."""

    category: str
    name: str
    weight: int
    description: str = ""

    def to_dict(self) -> dict[str, Any]:
        return {
            "category": self.category,
            "name": self.name,
            "weight": self.weight,
            "description": self.description,
        }


@dataclass
class RiskAssessment:
    """Risk assessment for a single MCP."""

    mcp_name: str
    risk_level: str = "medium"
    risk_score: int = 0
    factors: list[RiskFactor] = field(default_factory=list)
    mitigations: list[str] = field(default_factory=list)
    summary: str = ""

    def to_dict(self) -> dict[str, Any]:
        return {
            "mcp_name": self.mcp_name,
            "risk_level": self.risk_level,
            "risk_score": self.risk_score,
            "factors": [f.to_dict() for f in self.factors],
            "mitigations": self.mitigations,
            "summary": self.summary,
        }


@dataclass
class RegistryReport:
    """Report from scanning a registry of MCPs."""

    total_mcps: int = 0
    scanned_at: str = ""
    risk_distribution: dict[str, int] = field(default_factory=dict)
    assessments: list[RiskAssessment] = field(default_factory=list)
    high_risk_mcps: list[str] = field(default_factory=list)
    recommendations: list[str] = field(default_factory=list)

    def __post_init__(self) -> None:
        if not self.scanned_at:
            self.scanned_at = _now_iso()

    def to_dict(self) -> dict[str, Any]:
        return {
            "total_mcps": self.total_mcps,
            "scanned_at": self.scanned_at,
            "risk_distribution": self.risk_distribution,
            "assessments": [a.to_dict() for a in self.assessments],
            "high_risk_mcps": self.high_risk_mcps,
            "recommendations": self.recommendations,
        }


# ---------------------------------------------------------------------------
# Builtin catalog of 20 known MCPs
# ---------------------------------------------------------------------------

_KNOWN_MCPS: list[MCPEntry] = [
    MCPEntry(
        name="filesystem",
        description="Read, write, and manage local filesystem",
        repo="modelcontextprotocol/servers",
        category="filesystem",
        permissions=["file_read", "file_write", "file_delete"],
        tools=["read_file", "write_file", "list_directory", "create_directory",
               "move_file", "delete_file", "search_files"],
        data_access=["source_code", "system_config", "user_data"],
        risk_level="high",
        verified=True,
    ),
    MCPEntry(
        name="github",
        description="GitHub API integration for repos, issues, PRs",
        repo="modelcontextprotocol/servers",
        category="cloud",
        permissions=["network_access", "env_read"],
        tools=["create_issue", "create_pull_request", "push_files",
               "search_repositories", "get_file_contents"],
        data_access=["source_code", "api_keys"],
        risk_level="medium",
        verified=True,
    ),
    MCPEntry(
        name="postgres",
        description="PostgreSQL database access",
        repo="modelcontextprotocol/servers",
        category="database",
        permissions=["db_read", "db_write", "network_access"],
        tools=["query", "list_tables", "describe_table"],
        data_access=["database", "personal_data", "user_data"],
        risk_level="high",
        verified=True,
    ),
    MCPEntry(
        name="sqlite",
        description="SQLite database access",
        repo="modelcontextprotocol/servers",
        category="database",
        permissions=["db_read", "db_write", "file_read", "file_write"],
        tools=["query", "list_tables", "describe_table", "create_table"],
        data_access=["database", "user_data"],
        risk_level="medium",
        verified=True,
    ),
    MCPEntry(
        name="puppeteer",
        description="Browser automation with Puppeteer",
        repo="modelcontextprotocol/servers",
        category="browser",
        permissions=["network_access", "browser_control"],
        tools=["navigate", "screenshot", "click", "fill", "evaluate"],
        data_access=["user_data", "credentials"],
        risk_level="high",
        verified=True,
    ),
    MCPEntry(
        name="playwright",
        description="Browser automation with Playwright",
        repo="microsoft/playwright-mcp",
        category="browser",
        permissions=["network_access", "browser_control"],
        tools=["navigate", "click", "fill", "screenshot", "evaluate"],
        data_access=["user_data", "credentials"],
        risk_level="high",
        verified=True,
    ),
    MCPEntry(
        name="brave-search",
        description="Brave Search API integration",
        repo="modelcontextprotocol/servers",
        category="network",
        permissions=["network_access", "env_read"],
        tools=["brave_web_search", "brave_local_search"],
        data_access=["logs"],
        risk_level="low",
        verified=True,
    ),
    MCPEntry(
        name="fetch",
        description="HTTP fetch for web content retrieval",
        repo="modelcontextprotocol/servers",
        category="network",
        permissions=["network_access"],
        tools=["fetch"],
        data_access=["logs"],
        risk_level="medium",
        verified=True,
    ),
    MCPEntry(
        name="slack",
        description="Slack workspace integration",
        repo="modelcontextprotocol/servers",
        category="communication",
        permissions=["network_access", "env_read"],
        tools=["send_message", "list_channels", "get_messages",
               "add_reaction", "search_messages"],
        data_access=["personal_data", "user_data"],
        risk_level="medium",
        verified=True,
    ),
    MCPEntry(
        name="google-drive",
        description="Google Drive file management",
        repo="modelcontextprotocol/servers",
        category="cloud",
        permissions=["network_access", "file_read", "file_write", "env_read"],
        tools=["search_files", "read_file", "create_file", "list_files"],
        data_access=["user_data", "personal_data"],
        risk_level="medium",
        verified=True,
    ),
    MCPEntry(
        name="memory",
        description="Knowledge graph-based persistent memory",
        repo="modelcontextprotocol/servers",
        category="data_processing",
        permissions=["file_read", "file_write"],
        tools=["create_entities", "create_relations", "search_nodes",
               "read_graph", "delete_entities"],
        data_access=["user_data"],
        risk_level="low",
        verified=True,
    ),
    MCPEntry(
        name="everything",
        description="Reference/test MCP with all capability types",
        repo="modelcontextprotocol/servers",
        category="utility",
        permissions=["file_read", "network_access"],
        tools=["echo", "add", "longRunningOperation", "sampleLLM"],
        data_access=["logs"],
        risk_level="low",
        verified=True,
    ),
    MCPEntry(
        name="docker",
        description="Docker container management",
        repo="community/docker-mcp",
        category="code_execution",
        permissions=["shell_execute", "process_spawn", "network_access"],
        tools=["run_container", "list_containers", "stop_container",
               "exec_in_container", "build_image"],
        data_access=["system_config", "source_code"],
        risk_level="critical",
        verified=False,
    ),
    MCPEntry(
        name="kubernetes",
        description="Kubernetes cluster management",
        repo="community/k8s-mcp",
        category="cloud",
        permissions=["network_access", "shell_execute", "env_read"],
        tools=["get_pods", "apply_manifest", "delete_resource",
               "exec_in_pod", "get_logs"],
        data_access=["system_config", "credentials", "api_keys"],
        risk_level="critical",
        verified=False,
    ),
    MCPEntry(
        name="shell",
        description="Shell command execution",
        repo="community/shell-mcp",
        category="code_execution",
        permissions=["shell_execute", "process_spawn", "file_read",
                     "file_write", "env_read"],
        tools=["execute", "run_script"],
        data_access=["source_code", "system_config", "credentials",
                     "environment"],
        risk_level="critical",
        verified=False,
    ),
    MCPEntry(
        name="redis",
        description="Redis database access",
        repo="community/redis-mcp",
        category="database",
        permissions=["db_read", "db_write", "network_access"],
        tools=["get", "set", "delete", "keys", "hget", "hset"],
        data_access=["database", "tokens"],
        risk_level="medium",
        verified=False,
    ),
    MCPEntry(
        name="aws",
        description="AWS cloud services integration",
        repo="community/aws-mcp",
        category="cloud",
        permissions=["cloud_api", "network_access", "env_read",
                     "secret_access"],
        tools=["s3_list", "s3_get", "s3_put", "lambda_invoke",
               "ec2_describe"],
        data_access=["api_keys", "credentials", "system_config"],
        risk_level="high",
        verified=False,
    ),
    MCPEntry(
        name="email",
        description="Email sending and management",
        repo="community/email-mcp",
        category="communication",
        permissions=["network_access", "email_send", "env_read"],
        tools=["send_email", "list_emails", "read_email", "delete_email"],
        data_access=["personal_data", "credentials"],
        risk_level="medium",
        verified=False,
    ),
    MCPEntry(
        name="code-interpreter",
        description="Python code execution sandbox",
        repo="community/code-interpreter-mcp",
        category="code_execution",
        permissions=["code_execute", "file_read", "file_write"],
        tools=["execute_python", "install_package"],
        data_access=["source_code", "user_data"],
        risk_level="high",
        verified=False,
    ),
    MCPEntry(
        name="notion",
        description="Notion workspace integration",
        repo="community/notion-mcp",
        category="data_processing",
        permissions=["network_access", "env_read"],
        tools=["search_pages", "create_page", "update_page",
               "get_database", "query_database"],
        data_access=["user_data", "personal_data"],
        risk_level="low",
        verified=False,
    ),
]


# ---------------------------------------------------------------------------
# Helper functions
# ---------------------------------------------------------------------------

def _now_iso() -> str:
    """Return current UTC time as ISO string."""
    return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")


def _compute_permission_risk(permissions: list[str]) -> tuple[int, list[RiskFactor]]:
    """Compute risk score from permissions.

    Returns:
        Tuple of (score, factors).
    """
    score = 0
    factors: list[RiskFactor] = []
    for perm in permissions:
        weight = _PERMISSION_WEIGHTS.get(perm, 5)
        score += weight
        factors.append(RiskFactor(
            category="permission",
            name=perm,
            weight=weight,
            description=f"Permission: {perm} (weight: {weight})",
        ))
    return score, factors


def _compute_tool_risk(tools: list[str]) -> tuple[int, list[RiskFactor]]:
    """Compute risk score from exposed tools.

    Returns:
        Tuple of (score, factors).
    """
    score = 0
    factors: list[RiskFactor] = []
    for tool in tools:
        tool_lower = tool.lower()
        max_weight = 0
        matched_keyword = ""
        for keyword, weight in _TOOL_RISK_KEYWORDS.items():
            if keyword in tool_lower and weight > max_weight:
                max_weight = weight
                matched_keyword = keyword
        if max_weight > 0:
            score += max_weight
            factors.append(RiskFactor(
                category="tool",
                name=tool,
                weight=max_weight,
                description=f"Tool '{tool}' matches risky keyword '{matched_keyword}'",
            ))
    return score, factors


def _compute_data_risk(data_access: list[str]) -> tuple[int, list[RiskFactor]]:
    """Compute risk score from data access patterns.

    Returns:
        Tuple of (score, factors).
    """
    score = 0
    factors: list[RiskFactor] = []
    for data_type in data_access:
        weight = _DATA_ACCESS_WEIGHTS.get(data_type, 5)
        score += weight
        factors.append(RiskFactor(
            category="data_access",
            name=data_type,
            weight=weight,
            description=f"Accesses {data_type} (weight: {weight})",
        ))
    return score, factors


def _generate_mitigations(
    risk_level: RiskLevel,
    factors: list[RiskFactor],
) -> list[str]:
    """Generate mitigation recommendations based on risk factors."""
    mitigations: list[str] = []

    categories = {f.category for f in factors}
    names = {f.name for f in factors}

    if "shell_execute" in names or "code_execute" in names:
        mitigations.append(
            "Restrict command execution to an allowlist of safe commands."
        )
        mitigations.append(
            "Run in a sandboxed environment (container, VM, or chroot)."
        )

    if "secret_access" in names or "credentials" in names:
        mitigations.append(
            "Use a secrets manager instead of direct access. "
            "Implement least-privilege access."
        )

    if "file_write" in names or "file_delete" in names:
        mitigations.append(
            "Restrict filesystem access to specific directories. "
            "Implement path validation."
        )

    if "network_access" in names:
        mitigations.append(
            "Implement network egress controls and URL allowlisting."
        )

    if "db_write" in names or "db_admin" in names:
        mitigations.append(
            "Use read-only database connections where possible. "
            "Implement query parameterization."
        )

    if "browser_control" in names:
        mitigations.append(
            "Restrict browser navigation to trusted domains. "
            "Disable JavaScript execution where possible."
        )

    if risk_level in (RiskLevel.CRITICAL, RiskLevel.HIGH):
        mitigations.append(
            "Conduct a thorough security review before deployment."
        )
        mitigations.append(
            "Implement comprehensive logging and monitoring."
        )

    if not mitigations:
        mitigations.append(
            "No specific mitigations needed. Follow standard security practices."
        )

    return mitigations


# ---------------------------------------------------------------------------
# Core functions
# ---------------------------------------------------------------------------

def get_known_mcps() -> list[MCPEntry]:
    """Get the builtin catalog of known MCPs.

    Returns:
        List of MCPEntry objects for 20 popular MCPs.
    """
    return list(_KNOWN_MCPS)


def get_known_mcp(name: str) -> MCPEntry | None:
    """Get a specific MCP by name from the builtin catalog.

    Args:
        name: MCP name (case-insensitive).

    Returns:
        MCPEntry or None if not found.
    """
    name_lower = name.lower()
    for mcp in _KNOWN_MCPS:
        if mcp.name.lower() == name_lower:
            return mcp
    return None


def assess_mcp_risk(mcp: MCPEntry | dict[str, Any]) -> RiskAssessment:
    """Assess risk for a single MCP based on its metadata.

    Args:
        mcp: MCPEntry or dict with MCP metadata.

    Returns:
        RiskAssessment with score, factors, and mitigations.
    """
    if isinstance(mcp, dict):
        mcp = MCPEntry.from_dict(mcp)

    # Compute risk from three dimensions
    perm_score, perm_factors = _compute_permission_risk(mcp.permissions)
    tool_score, tool_factors = _compute_tool_risk(mcp.tools)
    data_score, data_factors = _compute_data_risk(mcp.data_access)

    # Weighted combination (permissions matter most)
    raw_score = (perm_score * 0.4) + (tool_score * 0.3) + (data_score * 0.3)
    # Normalize to 0-100
    normalized = min(100, int(raw_score))

    all_factors = perm_factors + tool_factors + data_factors
    risk_level = RiskLevel.from_score(normalized)

    mitigations = _generate_mitigations(risk_level, all_factors)

    total_factors = len(all_factors)
    summary = (
        f"{mcp.name}: {risk_level.value} risk "
        f"(score {normalized}/100, {total_factors} risk factors)"
    )

    return RiskAssessment(
        mcp_name=mcp.name,
        risk_level=risk_level.value,
        risk_score=normalized,
        factors=all_factors,
        mitigations=mitigations,
        summary=summary,
    )


def load_registry(path: str | Path) -> list[MCPEntry]:
    """Load MCP registry from a JSON file.

    Args:
        path: Path to registry JSON file.

    Returns:
        List of MCPEntry objects.

    Raises:
        FileNotFoundError: If file doesn't exist.
        ValueError: If JSON is invalid.
    """
    filepath = Path(path)
    if not filepath.exists():
        raise FileNotFoundError(f"Registry file not found: {filepath}")

    try:
        text = filepath.read_text(encoding="utf-8")
    except (OSError, UnicodeDecodeError) as e:
        raise ValueError(f"Failed to read registry file: {e}")

    try:
        data = json.loads(text)
    except json.JSONDecodeError as e:
        raise ValueError(f"Invalid JSON in registry file: {e}")

    if isinstance(data, list):
        entries = data
    elif isinstance(data, dict):
        entries = data.get("mcps", data.get("servers", data.get("entries", [])))
    else:
        raise ValueError("Registry must be a JSON array or object with mcps/servers/entries key")

    return [MCPEntry.from_dict(e) for e in entries if isinstance(e, dict)]


def save_registry(
    entries: list[MCPEntry],
    path: str | Path,
) -> None:
    """Save MCP registry to a JSON file.

    Args:
        entries: List of MCPEntry objects.
        path: Output file path.
    """
    filepath = Path(path)
    data = [e.to_dict() for e in entries]
    filepath.write_text(
        json.dumps(data, indent=2, ensure_ascii=False) + "\n",
        encoding="utf-8",
    )


def scan_registry(
    path: str | Path | None = None,
    entries: list[MCPEntry] | None = None,
) -> RegistryReport:
    """Scan a registry of MCPs and generate a risk report.

    Args:
        path: Path to registry JSON file.
        entries: List of MCPEntry objects (alternative to path).

    Returns:
        RegistryReport with assessments for all MCPs.
    """
    if entries is None:
        if path is not None:
            entries = load_registry(path)
        else:
            entries = get_known_mcps()

    assessments: list[RiskAssessment] = []
    risk_dist: dict[str, int] = {
        "critical": 0, "high": 0, "medium": 0, "low": 0, "minimal": 0,
    }
    high_risk: list[str] = []

    for mcp in entries:
        assessment = assess_mcp_risk(mcp)
        assessments.append(assessment)

        level = assessment.risk_level
        if level in risk_dist:
            risk_dist[level] += 1

        if level in ("critical", "high"):
            high_risk.append(mcp.name)

    # Generate overall recommendations
    recommendations: list[str] = []
    critical_count = risk_dist.get("critical", 0)
    high_count = risk_dist.get("high", 0)

    if critical_count > 0:
        recommendations.append(
            f"{critical_count} MCP(s) have critical risk. "
            "Review and restrict before production use."
        )

    if high_count > 0:
        recommendations.append(
            f"{high_count} MCP(s) have high risk. "
            "Implement additional security controls."
        )

    if len(entries) > 5:
        recommendations.append(
            "Consider reducing the number of installed MCPs "
            "to minimize attack surface."
        )

    unverified = [e.name for e in entries if not e.verified]
    if unverified:
        recommendations.append(
            f"{len(unverified)} unverified MCP(s): "
            f"{', '.join(unverified[:5])}{'...' if len(unverified) > 5 else ''}. "
            "Prefer verified MCPs from official sources."
        )

    if not recommendations:
        recommendations.append(
            "Registry looks clean. Continue monitoring for updates."
        )

    return RegistryReport(
        total_mcps=len(entries),
        assessments=assessments,
        risk_distribution=risk_dist,
        high_risk_mcps=high_risk,
        recommendations=recommendations,
    )


# ---------------------------------------------------------------------------
# Formatting
# ---------------------------------------------------------------------------

def format_registry_text(report: RegistryReport) -> str:
    """Format registry report as plain text.

    Args:
        report: RegistryReport to format.

    Returns:
        Formatted text string.
    """
    lines: list[str] = [
        "MCP Registry Scan Report",
        "=" * 60,
        f"Total MCPs: {report.total_mcps}",
        f"Scanned at: {report.scanned_at}",
        "",
        "Risk Distribution:",
    ]

    for level in ("critical", "high", "medium", "low", "minimal"):
        count = report.risk_distribution.get(level, 0)
        bar = "#" * min(count * 2, 40)
        lines.append(f"  {level:<10} {count:>3}  {bar}")

    lines.append("")

    if report.high_risk_mcps:
        lines.append(f"High-Risk MCPs: {', '.join(report.high_risk_mcps)}")
        lines.append("")

    lines.append("Assessments:")
    lines.append("-" * 60)
    for a in report.assessments:
        lines.append(f"  {a.mcp_name:<20} [{a.risk_level:<8}] score={a.risk_score}")
        if a.mitigations:
            for m in a.mitigations[:2]:
                lines.append(f"    -> {m}")

    lines.append("")
    lines.append("Recommendations:")
    for i, rec in enumerate(report.recommendations, 1):
        lines.append(f"  {i}. {rec}")

    return "\n".join(lines) + "\n"


def format_registry_json(report: RegistryReport) -> str:
    """Format registry report as JSON.

    Args:
        report: RegistryReport to format.

    Returns:
        JSON string.
    """
    return json.dumps(report.to_dict(), indent=2, ensure_ascii=False)


def format_assessment_text(assessment: RiskAssessment) -> str:
    """Format a single risk assessment as text.

    Args:
        assessment: RiskAssessment to format.

    Returns:
        Formatted text string.
    """
    lines: list[str] = [
        f"Risk Assessment: {assessment.mcp_name}",
        "=" * 50,
        f"Risk Level: {assessment.risk_level}",
        f"Risk Score: {assessment.risk_score}/100",
        "",
        "Risk Factors:",
    ]

    for f in assessment.factors:
        lines.append(f"  [{f.category}] {f.name}: weight={f.weight}")

    lines.append("")
    lines.append("Mitigations:")
    for m in assessment.mitigations:
        lines.append(f"  - {m}")

    lines.append("")
    lines.append(f"Summary: {assessment.summary}")

    return "\n".join(lines) + "\n"


def format_mcps_list_text(mcps: list[MCPEntry]) -> str:
    """Format a list of MCPs as text.

    Args:
        mcps: List of MCPEntry objects.

    Returns:
        Formatted text string.
    """
    lines: list[str] = [
        "Known MCP Catalog",
        "=" * 60,
        f"{'Name':<20} {'Category':<15} {'Risk':<10} {'Verified':<8}",
        "-" * 60,
    ]

    for mcp in sorted(mcps, key=lambda m: m.name):
        verified = "yes" if mcp.verified else "no"
        lines.append(
            f"  {mcp.name:<18} {mcp.category:<15} {mcp.risk_level:<10} {verified:<8}"
        )

    lines.append("")
    lines.append(f"Total: {len(mcps)} MCPs")

    return "\n".join(lines) + "\n"
