"""MCP Config Hardener — Generates hardened MCP configuration files.

Takes a claude_desktop_config.json and produces a security-hardened version
by applying least-privilege permissions, removing unused servers, adding
timeouts, pinning versions, and more.

Zero external dependencies — pure Python stdlib.
"""

from __future__ import annotations

import copy
import json
import re
import time
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any


# ---------- Data Models ----------

@dataclass
class HardeningRule:
    """A single hardening rule with condition, action, and description."""

    rule_id: str
    name: str
    description: str
    category: str  # permissions, timeout, version, cleanup, network, env, auth
    severity: str = "medium"  # critical, high, medium, low, info

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


@dataclass
class HardeningChange:
    """A single change applied during hardening."""

    rule_id: str
    path: str  # JSON path to changed field
    description: str
    old_value: Any = None
    new_value: Any = None

    def to_dict(self) -> dict[str, Any]:
        result: dict[str, Any] = {
            "rule_id": self.rule_id,
            "path": self.path,
            "description": self.description,
        }
        if self.old_value is not None:
            result["old_value"] = self.old_value
        if self.new_value is not None:
            result["new_value"] = self.new_value
        return result


@dataclass
class HardeningResult:
    """Result of hardening a configuration file."""

    original_path: str = ""
    original_config: dict[str, Any] = field(default_factory=dict)
    hardened_config: dict[str, Any] = field(default_factory=dict)
    changes: list[HardeningChange] = field(default_factory=list)
    rules_applied: list[str] = field(default_factory=list)
    rules_skipped: list[str] = field(default_factory=list)
    timestamp: str = ""

    @property
    def total_changes(self) -> int:
        return len(self.changes)

    @property
    def exit_code(self) -> int:
        if not self.changes:
            return 0
        for c in self.changes:
            if any(r.severity == "critical" for r in _get_all_hardening_rules()
                   if r.rule_id == c.rule_id):
                return 2
        return 1

    def to_dict(self) -> dict[str, Any]:
        return {
            "original_path": self.original_path,
            "total_changes": self.total_changes,
            "rules_applied": self.rules_applied,
            "rules_skipped": self.rules_skipped,
            "changes": [c.to_dict() for c in self.changes],
            "timestamp": self.timestamp,
        }


# ---------- Shell / Security Patterns ----------

_SHELL_COMMANDS = {"bash", "sh", "zsh", "cmd", "cmd.exe", "powershell", "pwsh"}

_SENSITIVE_ENV_PATTERNS: list[re.Pattern[str]] = [
    re.compile(r"(?i)(api[_-]?key|secret|token|password|passwd|credential|auth)"),
    re.compile(r"(?i)(aws[_-]?access|aws[_-]?secret|private[_-]?key)"),
    re.compile(r"(?i)(database[_-]?url|db[_-]?pass|connection[_-]?string)"),
    re.compile(r"(?i)(github[_-]?token|npm[_-]?token|slack[_-]?token)"),
    re.compile(r"(?i)(openai[_-]?api|anthropic[_-]?api|claude[_-]?api)"),
]

_DANGEROUS_ARGS_PATTERNS: list[re.Pattern[str]] = [
    re.compile(r"--allow-all"),
    re.compile(r"--no-sandbox"),
    re.compile(r"--disable-security"),
    re.compile(r"--insecure"),
    re.compile(r"--skip-verify"),
    re.compile(r"--allow-write"),
    re.compile(r"--privileged"),
]

_TRUSTED_BINARIES = {
    "node", "python", "python3", "npx", "uvx", "uv", "pip", "pip3",
    "docker", "podman", "deno", "bun",
}

_DEFAULT_TIMEOUT = 30
_DEFAULT_MAX_RETRIES = 3


# ---------- Builtin Hardening Rules ----------

def _get_all_hardening_rules() -> list[HardeningRule]:
    """Return all 18 built-in hardening rules."""
    return [
        HardeningRule(
            rule_id="HARDEN-001",
            name="Remove shell command wrappers",
            description="Replace 'bash -c' / 'sh -c' with direct binary invocation",
            category="permissions",
            severity="critical",
        ),
        HardeningRule(
            rule_id="HARDEN-002",
            name="Pin server versions",
            description="Add version pinning to server packages without explicit versions",
            category="version",
            severity="high",
        ),
        HardeningRule(
            rule_id="HARDEN-003",
            name="Add command timeouts",
            description="Add timeout configuration to servers lacking timeout settings",
            category="timeout",
            severity="medium",
        ),
        HardeningRule(
            rule_id="HARDEN-004",
            name="Remove sensitive env vars from inline config",
            description="Replace inline secrets with environment variable references",
            category="env",
            severity="critical",
        ),
        HardeningRule(
            rule_id="HARDEN-005",
            name="Remove dangerous command-line flags",
            description="Strip --allow-all, --no-sandbox, --insecure and similar flags",
            category="permissions",
            severity="high",
        ),
        HardeningRule(
            rule_id="HARDEN-006",
            name="Add resource limits",
            description="Add maxTokens or resource limit settings where missing",
            category="permissions",
            severity="medium",
        ),
        HardeningRule(
            rule_id="HARDEN-007",
            name="Restrict filesystem paths",
            description="Replace wildcard paths (*) with explicit directory lists",
            category="permissions",
            severity="high",
        ),
        HardeningRule(
            rule_id="HARDEN-008",
            name="Enable TLS verification",
            description="Set TLS/SSL verification to true where it is disabled",
            category="network",
            severity="critical",
        ),
        HardeningRule(
            rule_id="HARDEN-009",
            name="Remove duplicate servers",
            description="Remove duplicate MCP server entries with same command",
            category="cleanup",
            severity="low",
        ),
        HardeningRule(
            rule_id="HARDEN-010",
            name="Add authentication requirements",
            description="Flag servers without auth configuration",
            category="auth",
            severity="high",
        ),
        HardeningRule(
            rule_id="HARDEN-011",
            name="Normalize server names",
            description="Ensure server names follow lowercase-kebab convention",
            category="cleanup",
            severity="info",
        ),
        HardeningRule(
            rule_id="HARDEN-012",
            name="Remove empty or null configurations",
            description="Clean up null/empty entries in server configurations",
            category="cleanup",
            severity="low",
        ),
        HardeningRule(
            rule_id="HARDEN-013",
            name="Add retry configuration",
            description="Add max retries and backoff settings where missing",
            category="timeout",
            severity="low",
        ),
        HardeningRule(
            rule_id="HARDEN-014",
            name="Restrict network access",
            description="Add allowedHosts or network restrictions where absent",
            category="network",
            severity="medium",
        ),
        HardeningRule(
            rule_id="HARDEN-015",
            name="Remove commented-out servers",
            description="Remove server entries that appear disabled or commented",
            category="cleanup",
            severity="low",
        ),
        HardeningRule(
            rule_id="HARDEN-016",
            name="Add logging configuration",
            description="Enable audit logging for servers without log settings",
            category="auth",
            severity="medium",
        ),
        HardeningRule(
            rule_id="HARDEN-017",
            name="Enforce read-only where possible",
            description="Set read-only flags for servers that only need read access",
            category="permissions",
            severity="medium",
        ),
        HardeningRule(
            rule_id="HARDEN-018",
            name="Remove unused environment variables",
            description="Strip env vars not referenced by the server command",
            category="env",
            severity="low",
        ),
    ]


def get_hardening_rules() -> list[HardeningRule]:
    """Return all built-in hardening rules (public API)."""
    return _get_all_hardening_rules()


# ---------- Hardening Logic ----------

def _is_sensitive_env(name: str) -> bool:
    """Check if an environment variable name looks sensitive."""
    for pat in _SENSITIVE_ENV_PATTERNS:
        if pat.search(name):
            return True
    return False


def _is_dangerous_arg(arg: str) -> bool:
    """Check if a command-line argument is dangerous."""
    for pat in _DANGEROUS_ARGS_PATTERNS:
        if pat.search(arg):
            return True
    return False


def _has_version_pin(package: str) -> bool:
    """Check if a package string includes a version pin."""
    return bool(re.search(r"[@=><~^][\d]", package))


def _normalize_server_name(name: str) -> str:
    """Convert a server name to lowercase-kebab convention."""
    normalized = re.sub(r"[_\s]+", "-", name)
    normalized = re.sub(r"[^a-zA-Z0-9-]", "", normalized)
    return normalized.lower()


def _apply_shell_removal(
    server_name: str, server_config: dict[str, Any], changes: list[HardeningChange]
) -> dict[str, Any]:
    """HARDEN-001: Remove shell command wrappers."""
    config = copy.deepcopy(server_config)
    command = config.get("command", "")
    args = config.get("args", [])

    if command in _SHELL_COMMANDS and args:
        # bash -c "actual command" -> extract actual command
        if len(args) >= 2 and args[0] == "-c":
            inner = args[1]
            parts = inner.split()
            if parts:
                old_cmd = f"{command} -c \"{inner}\""
                config["command"] = parts[0]
                config["args"] = parts[1:]
                changes.append(HardeningChange(
                    rule_id="HARDEN-001",
                    path=f"mcpServers.{server_name}.command",
                    description=f"Replaced shell wrapper with direct binary",
                    old_value=old_cmd,
                    new_value=f"{parts[0]} {' '.join(parts[1:])}".strip(),
                ))
    return config


def _apply_version_pinning(
    server_name: str, server_config: dict[str, Any], changes: list[HardeningChange]
) -> dict[str, Any]:
    """HARDEN-002: Pin server versions."""
    config = copy.deepcopy(server_config)
    args = config.get("args", [])
    new_args = []

    for arg in args:
        if (arg.startswith("@") or "/" in arg) and not _has_version_pin(arg):
            pinned = arg + "@latest"
            changes.append(HardeningChange(
                rule_id="HARDEN-002",
                path=f"mcpServers.{server_name}.args",
                description=f"Pinned package version",
                old_value=arg,
                new_value=pinned,
            ))
            new_args.append(pinned)
        else:
            new_args.append(arg)

    config["args"] = new_args
    return config


def _apply_timeouts(
    server_name: str, server_config: dict[str, Any], changes: list[HardeningChange]
) -> dict[str, Any]:
    """HARDEN-003: Add command timeouts."""
    config = copy.deepcopy(server_config)
    if "timeout" not in config:
        config["timeout"] = _DEFAULT_TIMEOUT
        changes.append(HardeningChange(
            rule_id="HARDEN-003",
            path=f"mcpServers.{server_name}.timeout",
            description=f"Added default timeout of {_DEFAULT_TIMEOUT}s",
            old_value=None,
            new_value=_DEFAULT_TIMEOUT,
        ))
    return config


def _apply_env_sanitization(
    server_name: str, server_config: dict[str, Any], changes: list[HardeningChange]
) -> dict[str, Any]:
    """HARDEN-004: Remove sensitive env vars from inline config."""
    config = copy.deepcopy(server_config)
    env = config.get("env", {})
    sanitized_env = {}

    for key, value in env.items():
        if _is_sensitive_env(key) and not str(value).startswith("${"):
            ref = f"${{{key}}}"
            changes.append(HardeningChange(
                rule_id="HARDEN-004",
                path=f"mcpServers.{server_name}.env.{key}",
                description=f"Replaced inline secret with env reference",
                old_value="<redacted>",
                new_value=ref,
            ))
            sanitized_env[key] = ref
        else:
            sanitized_env[key] = value

    if env:
        config["env"] = sanitized_env
    return config


def _apply_dangerous_flags_removal(
    server_name: str, server_config: dict[str, Any], changes: list[HardeningChange]
) -> dict[str, Any]:
    """HARDEN-005: Remove dangerous command-line flags."""
    config = copy.deepcopy(server_config)
    args = config.get("args", [])
    safe_args = []

    for arg in args:
        if _is_dangerous_arg(arg):
            changes.append(HardeningChange(
                rule_id="HARDEN-005",
                path=f"mcpServers.{server_name}.args",
                description=f"Removed dangerous flag",
                old_value=arg,
                new_value=None,
            ))
        else:
            safe_args.append(arg)

    config["args"] = safe_args
    return config


def _apply_resource_limits(
    server_name: str, server_config: dict[str, Any], changes: list[HardeningChange]
) -> dict[str, Any]:
    """HARDEN-006: Add resource limits."""
    config = copy.deepcopy(server_config)
    if "maxTokens" not in config and "resourceLimits" not in config:
        config["resourceLimits"] = {"maxTokens": 100000, "maxRequests": 1000}
        changes.append(HardeningChange(
            rule_id="HARDEN-006",
            path=f"mcpServers.{server_name}.resourceLimits",
            description="Added resource limits",
            old_value=None,
            new_value=config["resourceLimits"],
        ))
    return config


def _apply_path_restriction(
    server_name: str, server_config: dict[str, Any], changes: list[HardeningChange]
) -> dict[str, Any]:
    """HARDEN-007: Restrict filesystem paths."""
    config = copy.deepcopy(server_config)
    args = config.get("args", [])
    new_args = []
    changed = False

    for arg in args:
        if arg == "*" or arg == "/**" or arg == "./*":
            new_args.append("./")
            changed = True
        else:
            new_args.append(arg)

    if changed:
        changes.append(HardeningChange(
            rule_id="HARDEN-007",
            path=f"mcpServers.{server_name}.args",
            description="Restricted wildcard path to current directory",
            old_value=[a for a in args if a in ("*", "/**", "./*")],
            new_value="./",
        ))
        config["args"] = new_args
    return config


def _apply_tls_enforcement(
    server_name: str, server_config: dict[str, Any], changes: list[HardeningChange]
) -> dict[str, Any]:
    """HARDEN-008: Enable TLS verification."""
    config = copy.deepcopy(server_config)

    tls_fields = ["tlsVerify", "sslVerify", "verifySSL", "verify_ssl"]
    for tls_field in tls_fields:
        if tls_field in config and config[tls_field] is False:
            config[tls_field] = True
            changes.append(HardeningChange(
                rule_id="HARDEN-008",
                path=f"mcpServers.{server_name}.{tls_field}",
                description="Enabled TLS/SSL verification",
                old_value=False,
                new_value=True,
            ))

    env = config.get("env", {})
    for key, val in list(env.items()):
        if key.upper() in ("NODE_TLS_REJECT_UNAUTHORIZED",) and str(val) == "0":
            env[key] = "1"
            changes.append(HardeningChange(
                rule_id="HARDEN-008",
                path=f"mcpServers.{server_name}.env.{key}",
                description="Enabled TLS rejection of unauthorized certificates",
                old_value="0",
                new_value="1",
            ))

    return config


def _apply_duplicate_removal(
    servers: dict[str, Any], changes: list[HardeningChange]
) -> dict[str, Any]:
    """HARDEN-009: Remove duplicate servers."""
    seen: dict[str, str] = {}
    result = {}

    for name, config in servers.items():
        cmd = config.get("command", "") + " " + " ".join(config.get("args", []))
        cmd_key = cmd.strip()

        if cmd_key in seen:
            changes.append(HardeningChange(
                rule_id="HARDEN-009",
                path=f"mcpServers.{name}",
                description=f"Removed duplicate of '{seen[cmd_key]}'",
                old_value=name,
                new_value=None,
            ))
        else:
            seen[cmd_key] = name
            result[name] = config

    return result


def _apply_name_normalization(
    servers: dict[str, Any], changes: list[HardeningChange]
) -> dict[str, Any]:
    """HARDEN-011: Normalize server names."""
    result = {}

    for name, config in servers.items():
        normalized = _normalize_server_name(name)
        if normalized != name:
            changes.append(HardeningChange(
                rule_id="HARDEN-011",
                path=f"mcpServers.{name}",
                description=f"Normalized server name",
                old_value=name,
                new_value=normalized,
            ))
            result[normalized] = config
        else:
            result[name] = config

    return result


def _apply_null_cleanup(
    server_name: str, server_config: dict[str, Any], changes: list[HardeningChange]
) -> dict[str, Any]:
    """HARDEN-012: Remove empty or null configurations."""
    config = copy.deepcopy(server_config)
    keys_to_remove = []

    for key, value in config.items():
        if value is None or (isinstance(value, (str, list, dict)) and not value):
            keys_to_remove.append(key)

    for key in keys_to_remove:
        changes.append(HardeningChange(
            rule_id="HARDEN-012",
            path=f"mcpServers.{server_name}.{key}",
            description=f"Removed empty/null field '{key}'",
            old_value=config[key],
            new_value=None,
        ))
        del config[key]

    return config


def _apply_retry_config(
    server_name: str, server_config: dict[str, Any], changes: list[HardeningChange]
) -> dict[str, Any]:
    """HARDEN-013: Add retry configuration."""
    config = copy.deepcopy(server_config)
    if "maxRetries" not in config and "retryConfig" not in config:
        config["maxRetries"] = _DEFAULT_MAX_RETRIES
        changes.append(HardeningChange(
            rule_id="HARDEN-013",
            path=f"mcpServers.{server_name}.maxRetries",
            description=f"Added max retries of {_DEFAULT_MAX_RETRIES}",
            old_value=None,
            new_value=_DEFAULT_MAX_RETRIES,
        ))
    return config


def _apply_network_restriction(
    server_name: str, server_config: dict[str, Any], changes: list[HardeningChange]
) -> dict[str, Any]:
    """HARDEN-014: Restrict network access."""
    config = copy.deepcopy(server_config)
    if "allowedHosts" not in config and "networkPolicy" not in config:
        config["networkPolicy"] = {"allowedHosts": ["localhost", "127.0.0.1"]}
        changes.append(HardeningChange(
            rule_id="HARDEN-014",
            path=f"mcpServers.{server_name}.networkPolicy",
            description="Added network restriction to localhost only",
            old_value=None,
            new_value=config["networkPolicy"],
        ))
    return config


def _apply_disabled_removal(
    servers: dict[str, Any], changes: list[HardeningChange]
) -> dict[str, Any]:
    """HARDEN-015: Remove commented-out/disabled servers."""
    result = {}

    for name, config in servers.items():
        disabled = config.get("disabled", False)
        enabled = config.get("enabled", True)

        if disabled or not enabled:
            changes.append(HardeningChange(
                rule_id="HARDEN-015",
                path=f"mcpServers.{name}",
                description="Removed disabled server entry",
                old_value=name,
                new_value=None,
            ))
        else:
            result[name] = config

    return result


def _apply_logging_config(
    server_name: str, server_config: dict[str, Any], changes: list[HardeningChange]
) -> dict[str, Any]:
    """HARDEN-016: Add logging configuration."""
    config = copy.deepcopy(server_config)
    if "logging" not in config and "logLevel" not in config:
        config["logging"] = {"enabled": True, "level": "info"}
        changes.append(HardeningChange(
            rule_id="HARDEN-016",
            path=f"mcpServers.{server_name}.logging",
            description="Enabled audit logging",
            old_value=None,
            new_value=config["logging"],
        ))
    return config


def _apply_readonly_enforcement(
    server_name: str, server_config: dict[str, Any], changes: list[HardeningChange]
) -> dict[str, Any]:
    """HARDEN-017: Enforce read-only where possible."""
    config = copy.deepcopy(server_config)
    args = config.get("args", [])
    write_indicators = {"--write", "--rw", "--allow-write", "-w", "--readwrite"}
    has_write = any(a in write_indicators for a in args)

    if not has_write and "readOnly" not in config:
        config["readOnly"] = True
        changes.append(HardeningChange(
            rule_id="HARDEN-017",
            path=f"mcpServers.{server_name}.readOnly",
            description="Set read-only mode (no write indicators found)",
            old_value=None,
            new_value=True,
        ))
    return config


def _apply_unused_env_cleanup(
    server_name: str, server_config: dict[str, Any], changes: list[HardeningChange]
) -> dict[str, Any]:
    """HARDEN-018: Remove unused environment variables."""
    config = copy.deepcopy(server_config)
    env = config.get("env", {})
    command = config.get("command", "")
    args = config.get("args", [])
    full_cmd = command + " " + " ".join(str(a) for a in args)

    used_env = {}
    unused_keys = []

    for key, value in env.items():
        # Keep if the key appears referenced in command/args or is sensitive
        if key in full_cmd or f"${key}" in full_cmd or _is_sensitive_env(key):
            used_env[key] = value
        else:
            unused_keys.append(key)

    if unused_keys:
        changes.append(HardeningChange(
            rule_id="HARDEN-018",
            path=f"mcpServers.{server_name}.env",
            description=f"Removed {len(unused_keys)} unused env var(s)",
            old_value=unused_keys,
            new_value=None,
        ))
        config["env"] = used_env

    return config


# ---------- Main Hardening Engine ----------

def harden_config(
    config: dict[str, Any],
    *,
    rules: list[str] | None = None,
    exclude_rules: list[str] | None = None,
) -> HardeningResult:
    """Harden an MCP configuration dictionary.

    Args:
        config: Parsed MCP config (claude_desktop_config.json format).
        rules: Optional list of rule IDs to apply (default: all).
        exclude_rules: Optional list of rule IDs to skip.

    Returns:
        HardeningResult with changes and hardened config.
    """
    all_rules = _get_all_hardening_rules()
    rule_ids = {r.rule_id for r in all_rules}

    if rules:
        active_rules = {r for r in rules if r in rule_ids}
    else:
        active_rules = rule_ids

    if exclude_rules:
        active_rules -= set(exclude_rules)

    changes: list[HardeningChange] = []
    hardened = copy.deepcopy(config)
    servers = hardened.get("mcpServers", {})

    # Fleet-level rules
    if "HARDEN-009" in active_rules:
        servers = _apply_duplicate_removal(servers, changes)

    if "HARDEN-015" in active_rules:
        servers = _apply_disabled_removal(servers, changes)

    if "HARDEN-011" in active_rules:
        servers = _apply_name_normalization(servers, changes)

    # Per-server rules
    hardened_servers: dict[str, Any] = {}
    for name, server_config in servers.items():
        sc = copy.deepcopy(server_config)

        if "HARDEN-001" in active_rules:
            sc = _apply_shell_removal(name, sc, changes)
        if "HARDEN-002" in active_rules:
            sc = _apply_version_pinning(name, sc, changes)
        if "HARDEN-003" in active_rules:
            sc = _apply_timeouts(name, sc, changes)
        if "HARDEN-004" in active_rules:
            sc = _apply_env_sanitization(name, sc, changes)
        if "HARDEN-005" in active_rules:
            sc = _apply_dangerous_flags_removal(name, sc, changes)
        if "HARDEN-006" in active_rules:
            sc = _apply_resource_limits(name, sc, changes)
        if "HARDEN-007" in active_rules:
            sc = _apply_path_restriction(name, sc, changes)
        if "HARDEN-008" in active_rules:
            sc = _apply_tls_enforcement(name, sc, changes)
        if "HARDEN-012" in active_rules:
            sc = _apply_null_cleanup(name, sc, changes)
        if "HARDEN-013" in active_rules:
            sc = _apply_retry_config(name, sc, changes)
        if "HARDEN-014" in active_rules:
            sc = _apply_network_restriction(name, sc, changes)
        if "HARDEN-016" in active_rules:
            sc = _apply_logging_config(name, sc, changes)
        if "HARDEN-017" in active_rules:
            sc = _apply_readonly_enforcement(name, sc, changes)
        if "HARDEN-018" in active_rules:
            sc = _apply_unused_env_cleanup(name, sc, changes)

        hardened_servers[name] = sc

    hardened["mcpServers"] = hardened_servers

    applied = sorted({c.rule_id for c in changes})
    skipped = sorted(active_rules - set(applied))

    return HardeningResult(
        original_config=config,
        hardened_config=hardened,
        changes=changes,
        rules_applied=applied,
        rules_skipped=skipped,
        timestamp=time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
    )


def harden_file(
    path: Path | str,
    *,
    rules: list[str] | None = None,
    exclude_rules: list[str] | None = None,
) -> HardeningResult:
    """Harden an MCP config file.

    Args:
        path: Path to claude_desktop_config.json or similar.
        rules: Optional list of rule IDs to apply.
        exclude_rules: Optional list of rule IDs to skip.

    Returns:
        HardeningResult with hardened configuration.
    """
    p = Path(path)
    content = p.read_text(encoding="utf-8")
    config = json.loads(content)
    result = harden_config(config, rules=rules, exclude_rules=exclude_rules)
    result.original_path = str(p)
    return result


# ---------- Diff Output ----------

def format_diff(result: HardeningResult) -> str:
    """Format the hardening changes as a readable diff.

    Args:
        result: HardeningResult to format.

    Returns:
        Human-readable diff text.
    """
    lines: list[str] = []
    lines.append("=" * 60)
    lines.append("MCP Config Hardener — Diff Report")
    lines.append("=" * 60)

    if result.original_path:
        lines.append(f"File: {result.original_path}")

    lines.append(f"Changes: {result.total_changes}")
    lines.append(f"Rules applied: {len(result.rules_applied)}")
    lines.append("")

    if not result.changes:
        lines.append("No changes needed — configuration is already hardened.")
        return "\n".join(lines)

    for change in result.changes:
        lines.append(f"  [{change.rule_id}] {change.description}")
        lines.append(f"    Path: {change.path}")
        if change.old_value is not None:
            lines.append(f"    - {change.old_value}")
        if change.new_value is not None:
            lines.append(f"    + {change.new_value}")
        lines.append("")

    return "\n".join(lines)


def format_hardened_json(result: HardeningResult) -> str:
    """Format the hardened config as pretty JSON.

    Args:
        result: HardeningResult containing hardened config.

    Returns:
        JSON string of the hardened configuration.
    """
    return json.dumps(result.hardened_config, indent=2, ensure_ascii=False)


def format_hardening_report_json(result: HardeningResult) -> str:
    """Format the full hardening report as JSON.

    Args:
        result: HardeningResult.

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


def format_hardening_text(result: HardeningResult) -> str:
    """Format the hardening result as human-readable text.

    Args:
        result: HardeningResult.

    Returns:
        Formatted text report.
    """
    lines: list[str] = []
    lines.append("")
    lines.append("MCP Config Hardener Report")
    lines.append("=" * 40)

    if result.original_path:
        lines.append(f"Source: {result.original_path}")
    lines.append(f"Timestamp: {result.timestamp}")
    lines.append(f"Total changes: {result.total_changes}")
    lines.append(f"Rules applied: {', '.join(result.rules_applied) or 'none'}")
    lines.append("")

    if not result.changes:
        lines.append("Configuration is already hardened. No changes applied.")
        return "\n".join(lines)

    # Group changes by rule
    by_rule: dict[str, list[HardeningChange]] = {}
    for c in result.changes:
        by_rule.setdefault(c.rule_id, []).append(c)

    for rule_id, rule_changes in sorted(by_rule.items()):
        rule_obj = next(
            (r for r in _get_all_hardening_rules() if r.rule_id == rule_id), None
        )
        rule_name = rule_obj.name if rule_obj else rule_id
        lines.append(f"[{rule_id}] {rule_name}")
        for c in rule_changes:
            lines.append(f"  - {c.description} ({c.path})")
        lines.append("")

    return "\n".join(lines)
