"""MCP Remediation Engine — Automated remediation plans for findings.

Generates prioritized remediation plans with concrete steps for each
security finding. Supports code_change, config_change, and manual fix
types with dry-run mode for safe preview.

CLI: mcpaudit remediate <path> [--dry-run] [--format text|json|html]
     mcpaudit remediate <path> --priority critical,high
     mcpaudit remediate <path> --fix-type code_change

Zero external dependencies — pure Python stdlib.
"""

from __future__ import annotations

import html as html_mod
import json
from collections import Counter
from dataclasses import dataclass, field
from datetime import datetime, timezone
from enum import Enum
from pathlib import Path
from typing import Any

from .models import Finding, ScanResult, ScanSummary, Severity


# ---------------------------------------------------------------------------
# Enums
# ---------------------------------------------------------------------------

class FixType(Enum):
    """Type of remediation fix."""

    CODE_CHANGE = "code_change"
    CONFIG_CHANGE = "config_change"
    MANUAL = "manual"

    @classmethod
    def from_string(cls, value: str) -> FixType:
        """Parse a fix type string (case-insensitive)."""
        normalized = value.lower().strip().replace("-", "_")
        for member in cls:
            if member.value == normalized:
                return member
        valid = ", ".join(m.value for m in cls)
        raise ValueError(f"Invalid fix type '{value}'. Valid: {valid}")


class RemediationPriority(Enum):
    """Priority level for remediation steps."""

    CRITICAL = "critical"
    HIGH = "high"
    MEDIUM = "medium"
    LOW = "low"
    INFO = "info"

    @classmethod
    def from_severity(cls, severity: Severity) -> RemediationPriority:
        """Map a Severity to a RemediationPriority."""
        mapping = {
            Severity.CRITICAL: cls.CRITICAL,
            Severity.HIGH: cls.HIGH,
            Severity.MEDIUM: cls.MEDIUM,
            Severity.LOW: cls.LOW,
            Severity.INFO: cls.INFO,
        }
        return mapping[severity]

    @property
    def weight(self) -> int:
        """Numeric weight for sorting (lower = higher priority)."""
        return {
            RemediationPriority.CRITICAL: 0,
            RemediationPriority.HIGH: 1,
            RemediationPriority.MEDIUM: 2,
            RemediationPriority.LOW: 3,
            RemediationPriority.INFO: 4,
        }[self]

    def __lt__(self, other: RemediationPriority) -> bool:
        return self.weight < other.weight

    def __le__(self, other: RemediationPriority) -> bool:
        return self.weight <= other.weight


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

@dataclass
class RemediationStep:
    """A single remediation step."""

    step_number: int
    title: str
    description: str
    fix_type: FixType
    code_before: str = ""
    code_after: str = ""
    effort_minutes: int = 0
    reference: str = ""

    def to_dict(self) -> dict[str, Any]:
        result: dict[str, Any] = {
            "step_number": self.step_number,
            "title": self.title,
            "description": self.description,
            "fix_type": self.fix_type.value,
            "effort_minutes": self.effort_minutes,
        }
        if self.code_before:
            result["code_before"] = self.code_before
        if self.code_after:
            result["code_after"] = self.code_after
        if self.reference:
            result["reference"] = self.reference
        return result


@dataclass
class RemediationItem:
    """A remediation item for a single finding or group of findings."""

    rule_id: str
    title: str
    priority: RemediationPriority
    fix_type: FixType
    description: str
    steps: list[RemediationStep] = field(default_factory=list)
    affected_files: list[str] = field(default_factory=list)
    affected_lines: list[int] = field(default_factory=list)
    finding_count: int = 1
    estimated_effort_minutes: int = 0
    cwe: str = ""

    def to_dict(self) -> dict[str, Any]:
        return {
            "rule_id": self.rule_id,
            "title": self.title,
            "priority": self.priority.value,
            "fix_type": self.fix_type.value,
            "description": self.description,
            "steps": [s.to_dict() for s in self.steps],
            "affected_files": list(self.affected_files),
            "affected_lines": list(self.affected_lines),
            "finding_count": self.finding_count,
            "estimated_effort_minutes": self.estimated_effort_minutes,
            "cwe": self.cwe,
        }


@dataclass
class RemediationPlan:
    """Complete remediation plan for a scan."""

    items: list[RemediationItem] = field(default_factory=list)
    total_findings: int = 0
    total_effort_minutes: int = 0
    priority_counts: dict[str, int] = field(default_factory=dict)
    fix_type_counts: dict[str, int] = field(default_factory=dict)
    timestamp: str = ""
    dry_run: bool = False

    @property
    def total_items(self) -> int:
        return len(self.items)

    @property
    def critical_count(self) -> int:
        return self.priority_counts.get("critical", 0)

    @property
    def high_count(self) -> int:
        return self.priority_counts.get("high", 0)

    def to_dict(self) -> dict[str, Any]:
        return {
            "items": [i.to_dict() for i in self.items],
            "total_findings": self.total_findings,
            "total_items": self.total_items,
            "total_effort_minutes": self.total_effort_minutes,
            "priority_counts": dict(self.priority_counts),
            "fix_type_counts": dict(self.fix_type_counts),
            "timestamp": self.timestamp,
            "dry_run": self.dry_run,
        }


# ---------------------------------------------------------------------------
# Rule-specific remediation knowledge base
# ---------------------------------------------------------------------------

_REMEDIATION_DB: dict[str, dict[str, Any]] = {
    "CMD-001": {
        "title": "Replace shell command injection with safe subprocess",
        "fix_type": FixType.CODE_CHANGE,
        "description": (
            "Use subprocess.run() with a list of arguments instead of "
            "os.system() or shell=True to prevent command injection."
        ),
        "effort": 15,
        "steps": [
            {
                "title": "Replace os.system with subprocess.run",
                "description": "Use subprocess.run([cmd, arg1, arg2]) instead of os.system(cmd).",
                "code_before": "os.system(f'ls {user_input}')",
                "code_after": "subprocess.run(['ls', user_input], check=True)",
            },
            {
                "title": "Remove shell=True from subprocess calls",
                "description": "Pass command as a list, never as a string with shell=True.",
                "code_before": "subprocess.run(cmd, shell=True)",
                "code_after": "subprocess.run(shlex.split(cmd))",
            },
        ],
    },
    "CMD-002": {
        "title": "Sanitize command arguments",
        "fix_type": FixType.CODE_CHANGE,
        "description": "Use shlex.quote() to sanitize user-supplied arguments.",
        "effort": 10,
        "steps": [
            {
                "title": "Apply shlex.quote to user inputs",
                "description": "Wrap user-supplied values with shlex.quote().",
                "code_before": "cmd = f'echo {user_input}'",
                "code_after": "cmd = f'echo {shlex.quote(user_input)}'",
            },
        ],
    },
    "SQL-001": {
        "title": "Use parameterized SQL queries",
        "fix_type": FixType.CODE_CHANGE,
        "description": "Replace string formatting in SQL with parameterized queries.",
        "effort": 15,
        "steps": [
            {
                "title": "Use parameterized queries",
                "description": "Use ? or %s placeholders instead of f-strings.",
                "code_before": "cursor.execute(f'SELECT * FROM users WHERE id={uid}')",
                "code_after": "cursor.execute('SELECT * FROM users WHERE id=?', (uid,))",
            },
        ],
    },
    "PATH-001": {
        "title": "Validate and restrict file paths",
        "fix_type": FixType.CODE_CHANGE,
        "description": "Resolve and validate paths to prevent directory traversal.",
        "effort": 15,
        "steps": [
            {
                "title": "Resolve and check path boundaries",
                "description": "Use Path.resolve() and verify the path stays within allowed directories.",
                "code_before": "open(user_path)",
                "code_after": (
                    "resolved = Path(user_path).resolve()\n"
                    "assert str(resolved).startswith(str(ALLOWED_DIR))\n"
                    "open(resolved)"
                ),
            },
        ],
    },
    "AUTH-001": {
        "title": "Add authentication to MCP tool handlers",
        "fix_type": FixType.CODE_CHANGE,
        "description": "Implement authentication checks before processing tool requests.",
        "effort": 30,
        "steps": [
            {
                "title": "Add auth decorator or check",
                "description": "Verify caller identity before executing tool logic.",
                "code_before": "@tool\ndef my_tool(args):\n    return process(args)",
                "code_after": "@tool\n@require_auth\ndef my_tool(args):\n    return process(args)",
            },
        ],
    },
    "AUTHZ-001": {
        "title": "Implement authorization checks",
        "fix_type": FixType.CODE_CHANGE,
        "description": "Add role-based or permission-based authorization to sensitive operations.",
        "effort": 30,
        "steps": [
            {
                "title": "Add authorization verification",
                "description": "Check user permissions before allowing access to resources.",
            },
        ],
    },
    "SSRF-001": {
        "title": "Restrict outbound URL requests",
        "fix_type": FixType.CODE_CHANGE,
        "description": "Validate and restrict URLs before making requests to prevent SSRF.",
        "effort": 20,
        "steps": [
            {
                "title": "Validate URL against allowlist",
                "description": "Check URLs against a list of allowed domains before making requests.",
                "code_before": "requests.get(user_url)",
                "code_after": (
                    "parsed = urlparse(user_url)\n"
                    "if parsed.hostname not in ALLOWED_HOSTS:\n"
                    "    raise ValueError('Host not allowed')\n"
                    "requests.get(user_url)"
                ),
            },
        ],
    },
    "DESER-001": {
        "title": "Replace unsafe deserialization",
        "fix_type": FixType.CODE_CHANGE,
        "description": "Replace pickle/eval with json.loads or safe alternatives.",
        "effort": 20,
        "steps": [
            {
                "title": "Use JSON instead of pickle",
                "description": "Replace pickle.loads() with json.loads() for data deserialization.",
                "code_before": "data = pickle.loads(raw)",
                "code_after": "data = json.loads(raw)",
            },
        ],
    },
    "INFO-001": {
        "title": "Remove sensitive information disclosure",
        "fix_type": FixType.CODE_CHANGE,
        "description": "Remove or redact sensitive data from error messages and logs.",
        "effort": 10,
        "steps": [
            {
                "title": "Sanitize error messages",
                "description": "Do not include stack traces or internal details in user-facing errors.",
            },
        ],
    },
    "ERR-001": {
        "title": "Implement proper error handling",
        "fix_type": FixType.CODE_CHANGE,
        "description": "Add structured error handling with safe error messages.",
        "effort": 15,
        "steps": [
            {
                "title": "Add try/except with safe messages",
                "description": "Catch specific exceptions and return generic error messages.",
            },
        ],
    },
    "RATE-001": {
        "title": "Add rate limiting to tool handlers",
        "fix_type": FixType.CODE_CHANGE,
        "description": "Implement rate limiting to prevent denial of service.",
        "effort": 20,
        "steps": [
            {
                "title": "Add rate limiter",
                "description": "Use a token bucket or sliding window rate limiter.",
            },
        ],
    },
    "SEC-001": {
        "title": "Implement secrets management",
        "fix_type": FixType.CONFIG_CHANGE,
        "description": "Move hardcoded secrets to environment variables or a secrets manager.",
        "effort": 15,
        "steps": [
            {
                "title": "Use environment variables for secrets",
                "description": "Replace hardcoded API keys with os.environ.get().",
                "code_before": "API_KEY = 'sk-abc123'",
                "code_after": "API_KEY = os.environ['API_KEY']",
            },
        ],
    },
    "PERM-001": {
        "title": "Restrict excessive permissions",
        "fix_type": FixType.CONFIG_CHANGE,
        "description": "Apply principle of least privilege to file and process permissions.",
        "effort": 15,
        "steps": [
            {
                "title": "Restrict file permissions",
                "description": "Use 0o600 or 0o644 instead of 0o777.",
                "code_before": "os.chmod(path, 0o777)",
                "code_after": "os.chmod(path, 0o644)",
            },
        ],
    },
    "PRIV-001": {
        "title": "Reduce privilege scope",
        "fix_type": FixType.CONFIG_CHANGE,
        "description": "Limit the scope of elevated privileges to the minimum required.",
        "effort": 20,
        "steps": [
            {
                "title": "Apply least-privilege principle",
                "description": "Drop privileges after initialization.",
            },
        ],
    },
    "VAL-001": {
        "title": "Add input validation",
        "fix_type": FixType.CODE_CHANGE,
        "description": "Validate all external input before processing.",
        "effort": 15,
        "steps": [
            {
                "title": "Add schema validation",
                "description": "Validate input against expected types and ranges.",
            },
        ],
    },
    "IDOR-001": {
        "title": "Fix insecure direct object reference",
        "fix_type": FixType.CODE_CHANGE,
        "description": "Add authorization checks when accessing objects by ID.",
        "effort": 20,
        "steps": [
            {
                "title": "Verify object ownership",
                "description": "Check that the requesting user owns or has access to the object.",
            },
        ],
    },
    "FILE-001": {
        "title": "Restrict file system access",
        "fix_type": FixType.CONFIG_CHANGE,
        "description": "Limit file access to specific directories using allowlists.",
        "effort": 15,
        "steps": [
            {
                "title": "Implement file access allowlist",
                "description": "Only allow access to files within pre-approved directories.",
            },
        ],
    },
    "LOG-001": {
        "title": "Sanitize log output",
        "fix_type": FixType.CODE_CHANGE,
        "description": "Remove sensitive data from log messages.",
        "effort": 10,
        "steps": [
            {
                "title": "Redact sensitive fields in logs",
                "description": "Mask PII, tokens, and credentials before logging.",
            },
        ],
    },
    "RES-001": {
        "title": "Implement resource limits",
        "fix_type": FixType.CONFIG_CHANGE,
        "description": "Add memory, CPU, and connection limits to prevent resource exhaustion.",
        "effort": 20,
        "steps": [
            {
                "title": "Set resource bounds",
                "description": "Add max_size, timeout, and concurrency limits.",
            },
        ],
    },
    "REDOS-001": {
        "title": "Fix ReDoS-vulnerable regular expressions",
        "fix_type": FixType.CODE_CHANGE,
        "description": "Replace vulnerable regex patterns with safe alternatives.",
        "effort": 15,
        "steps": [
            {
                "title": "Simplify regex pattern",
                "description": "Remove nested quantifiers and use atomic groups where possible.",
            },
        ],
    },
    "CORS-001": {
        "title": "Restrict CORS configuration",
        "fix_type": FixType.CONFIG_CHANGE,
        "description": "Replace wildcard CORS origins with specific allowed domains.",
        "effort": 10,
        "steps": [
            {
                "title": "Specify allowed origins",
                "description": "Replace '*' with specific domain allowlist.",
                "code_before": "Access-Control-Allow-Origin: *",
                "code_after": "Access-Control-Allow-Origin: https://example.com",
            },
        ],
    },
    "TEMP-001": {
        "title": "Secure temporary file handling",
        "fix_type": FixType.CODE_CHANGE,
        "description": "Use tempfile module with secure defaults instead of manual temp paths.",
        "effort": 10,
        "steps": [
            {
                "title": "Use tempfile.mkstemp or NamedTemporaryFile",
                "description": "Let the stdlib create temp files with secure permissions.",
                "code_before": "open('/tmp/myapp_data', 'w')",
                "code_after": "tempfile.NamedTemporaryFile(prefix='myapp_')",
            },
        ],
    },
    "CRYPTO-001": {
        "title": "Use strong cryptographic algorithms",
        "fix_type": FixType.CODE_CHANGE,
        "description": "Replace weak hash/cipher algorithms with modern alternatives.",
        "effort": 15,
        "steps": [
            {
                "title": "Replace MD5/SHA1 with SHA256+",
                "description": "Use hashlib.sha256() instead of hashlib.md5().",
                "code_before": "hashlib.md5(data)",
                "code_after": "hashlib.sha256(data)",
            },
        ],
    },
}

# Default remediation for unknown rules
_DEFAULT_REMEDIATION: dict[str, Any] = {
    "title": "Review and remediate finding",
    "fix_type": FixType.MANUAL,
    "description": "Manually review this finding and apply appropriate fixes.",
    "effort": 30,
    "steps": [
        {
            "title": "Review the finding",
            "description": "Analyze the finding context and determine the appropriate fix.",
        },
        {
            "title": "Apply fix and test",
            "description": "Implement the fix and verify it does not introduce regressions.",
        },
    ],
}


# ---------------------------------------------------------------------------
# Core engine
# ---------------------------------------------------------------------------

def _build_steps(raw_steps: list[dict[str, str]], start: int = 1) -> list[RemediationStep]:
    """Convert raw step dicts into RemediationStep objects."""
    result: list[RemediationStep] = []
    for i, raw in enumerate(raw_steps, start):
        result.append(RemediationStep(
            step_number=i,
            title=raw.get("title", ""),
            description=raw.get("description", ""),
            fix_type=FixType.CODE_CHANGE,
            code_before=raw.get("code_before", ""),
            code_after=raw.get("code_after", ""),
            reference=raw.get("reference", ""),
        ))
    return result


def build_remediation_item(
    rule_id: str,
    findings: list[Finding],
) -> RemediationItem:
    """Build a remediation item for a group of findings with the same rule_id.

    Args:
        rule_id: The rule ID.
        findings: List of findings for this rule.

    Returns:
        RemediationItem with steps and metadata.
    """
    if not findings:
        raise ValueError("At least one finding is required")

    db_entry = _REMEDIATION_DB.get(rule_id, _DEFAULT_REMEDIATION)
    first = findings[0]

    fix_type = db_entry.get("fix_type", FixType.MANUAL)
    if isinstance(fix_type, str):
        fix_type = FixType.from_string(fix_type)

    raw_steps = db_entry.get("steps", [])
    steps = _build_steps(raw_steps)

    # Set fix_type on each step
    for step in steps:
        step.fix_type = fix_type

    effort = db_entry.get("effort", 30)

    affected_files = sorted(set(f.file for f in findings))
    affected_lines = sorted(set(f.line for f in findings))

    return RemediationItem(
        rule_id=rule_id,
        title=db_entry.get("title", first.title),
        priority=RemediationPriority.from_severity(first.severity),
        fix_type=fix_type,
        description=db_entry.get("description", first.description),
        steps=steps,
        affected_files=affected_files,
        affected_lines=affected_lines,
        finding_count=len(findings),
        estimated_effort_minutes=effort * len(findings),
        cwe=first.cwe,
    )


def generate_remediation_plan(
    summary: ScanSummary,
    dry_run: bool = False,
    priority_filter: list[str] | None = None,
    fix_type_filter: list[str] | None = None,
) -> RemediationPlan:
    """Generate a remediation plan from scan results.

    Args:
        summary: ScanSummary from scanner.
        dry_run: If True, mark the plan as a preview.
        priority_filter: Optional list of priorities to include.
        fix_type_filter: Optional list of fix types to include.

    Returns:
        RemediationPlan with prioritized items.
    """
    now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")

    # Group findings by rule_id
    findings_by_rule: dict[str, list[Finding]] = {}
    for result in summary.results:
        for finding in result.findings:
            findings_by_rule.setdefault(finding.rule_id, []).append(finding)

    # Build remediation items
    items: list[RemediationItem] = []
    for rule_id, findings in findings_by_rule.items():
        item = build_remediation_item(rule_id, findings)
        items.append(item)

    # Apply priority filter
    if priority_filter:
        allowed_priorities = set(p.lower().strip() for p in priority_filter)
        items = [i for i in items if i.priority.value in allowed_priorities]

    # Apply fix type filter
    if fix_type_filter:
        allowed_types = set(t.lower().strip().replace("-", "_") for t in fix_type_filter)
        items = [i for i in items if i.fix_type.value in allowed_types]

    # Sort by priority (critical first), then by finding count (most frequent first)
    items.sort(key=lambda i: (i.priority.weight, -i.finding_count))

    # Compute counts
    priority_counts: dict[str, int] = Counter()
    fix_type_counts: dict[str, int] = Counter()
    total_effort = 0
    total_findings = 0

    for item in items:
        priority_counts[item.priority.value] += 1
        fix_type_counts[item.fix_type.value] += 1
        total_effort += item.estimated_effort_minutes
        total_findings += item.finding_count

    return RemediationPlan(
        items=items,
        total_findings=total_findings,
        total_effort_minutes=total_effort,
        priority_counts=dict(priority_counts),
        fix_type_counts=dict(fix_type_counts),
        timestamp=now,
        dry_run=dry_run,
    )


def generate_plan_from_findings(
    findings: list[Finding],
    dry_run: bool = False,
) -> RemediationPlan:
    """Generate a remediation plan from a flat list of findings.

    Args:
        findings: List of Finding objects.
        dry_run: If True, mark as preview.

    Returns:
        RemediationPlan.
    """
    results = [ScanResult(file="<direct>", findings=findings)]
    summary = ScanSummary(results=results)
    return generate_remediation_plan(summary, dry_run=dry_run)


def generate_plan_from_dict(
    data: dict[str, Any],
    dry_run: bool = False,
) -> RemediationPlan:
    """Generate a remediation plan from a scan result dictionary.

    Args:
        data: Scan result dict (as from ScanSummary.to_dict()).
        dry_run: If True, mark as preview.

    Returns:
        RemediationPlan.
    """
    results_data = data.get("results", [])
    results: list[ScanResult] = []
    for rd in results_data:
        findings: list[Finding] = []
        for fd in rd.get("findings", []):
            findings.append(Finding(
                rule_id=fd.get("rule_id", ""),
                severity=Severity.from_string(fd.get("severity", "INFO")),
                title=fd.get("title", ""),
                description=fd.get("description", ""),
                file=fd.get("file", ""),
                line=fd.get("line", 0),
                recommendation=fd.get("recommendation", ""),
                cwe=fd.get("cwe", ""),
            ))
        results.append(ScanResult(
            file=rd.get("file", ""),
            findings=findings,
            lines_scanned=rd.get("lines_scanned", 0),
        ))
    summary = ScanSummary(results=results)
    return generate_remediation_plan(summary, dry_run=dry_run)


# ---------------------------------------------------------------------------
# Formatters
# ---------------------------------------------------------------------------

def format_remediation_text(plan: RemediationPlan) -> str:
    """Format remediation plan as plain text.

    Args:
        plan: RemediationPlan to format.

    Returns:
        Formatted text string.
    """
    lines: list[str] = []
    mode = "[DRY RUN] " if plan.dry_run else ""
    lines.append(f"\n{'=' * 60}")
    lines.append(f"  {mode}MCP Remediation Plan")
    lines.append(f"{'=' * 60}")
    lines.append(f"  Items: {plan.total_items}")
    lines.append(f"  Total findings: {plan.total_findings}")
    lines.append(f"  Estimated effort: {plan.total_effort_minutes} minutes")
    lines.append(f"  Generated: {plan.timestamp}")

    if plan.priority_counts:
        lines.append(f"\n  Priority breakdown:")
        for p in ["critical", "high", "medium", "low", "info"]:
            count = plan.priority_counts.get(p, 0)
            if count > 0:
                lines.append(f"    {p.upper()}: {count}")

    if plan.fix_type_counts:
        lines.append(f"\n  Fix type breakdown:")
        for ft in ["code_change", "config_change", "manual"]:
            count = plan.fix_type_counts.get(ft, 0)
            if count > 0:
                lines.append(f"    {ft}: {count}")

    lines.append(f"\n{'─' * 60}")

    for idx, item in enumerate(plan.items, 1):
        lines.append(f"\n  [{item.priority.value.upper()}] #{idx}: {item.title}")
        lines.append(f"    Rule: {item.rule_id}")
        lines.append(f"    Type: {item.fix_type.value}")
        lines.append(f"    Occurrences: {item.finding_count}")
        lines.append(f"    Effort: {item.estimated_effort_minutes} min")
        if item.cwe:
            lines.append(f"    CWE: {item.cwe}")
        lines.append(f"    {item.description}")

        if item.affected_files:
            lines.append(f"    Files: {', '.join(item.affected_files[:5])}")

        for step in item.steps:
            lines.append(f"      Step {step.step_number}: {step.title}")
            if step.description:
                lines.append(f"        {step.description}")
            if step.code_before:
                lines.append(f"        Before: {step.code_before}")
            if step.code_after:
                lines.append(f"        After:  {step.code_after}")

    lines.append(f"\n{'=' * 60}")
    return "\n".join(lines)


def format_remediation_json(plan: RemediationPlan) -> str:
    """Format remediation plan as JSON.

    Args:
        plan: RemediationPlan to format.

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


def format_remediation_html(plan: RemediationPlan) -> str:
    """Format remediation plan as a self-contained HTML page.

    Args:
        plan: RemediationPlan to format.

    Returns:
        HTML string.
    """
    mode = "[DRY RUN] " if plan.dry_run else ""

    priority_colors = {
        "critical": "#dc2626",
        "high": "#f97316",
        "medium": "#eab308",
        "low": "#22c55e",
        "info": "#6b7280",
    }

    # Build items HTML
    items_html = ""
    for idx, item in enumerate(plan.items, 1):
        pc = priority_colors.get(item.priority.value, "#6b7280")
        steps_html = ""
        for step in item.steps:
            code_html = ""
            if step.code_before or step.code_after:
                code_html = "<div class='code-block'>"
                if step.code_before:
                    code_html += (
                        f"<div class='code-before'>"
                        f"<strong>Before:</strong><pre>{html_mod.escape(step.code_before)}</pre>"
                        f"</div>"
                    )
                if step.code_after:
                    code_html += (
                        f"<div class='code-after'>"
                        f"<strong>After:</strong><pre>{html_mod.escape(step.code_after)}</pre>"
                        f"</div>"
                    )
                code_html += "</div>"
            steps_html += (
                f"<div class='step'>"
                f"<span class='step-num'>Step {step.step_number}</span>"
                f"<strong>{html_mod.escape(step.title)}</strong>"
                f"<p>{html_mod.escape(step.description)}</p>"
                f"{code_html}"
                f"</div>"
            )

        files_html = ""
        if item.affected_files:
            files_list = ", ".join(
                html_mod.escape(f) for f in item.affected_files[:5]
            )
            files_html = f"<div class='files'>Files: {files_list}</div>"

        items_html += (
            f"<div class='item'>"
            f"<div class='item-header'>"
            f"<span class='badge' style='background:{pc}'>"
            f"{html_mod.escape(item.priority.value.upper())}</span>"
            f"<span class='rule-id'>{html_mod.escape(item.rule_id)}</span>"
            f"<span class='fix-type'>{html_mod.escape(item.fix_type.value)}</span>"
            f"</div>"
            f"<h3>#{idx}: {html_mod.escape(item.title)}</h3>"
            f"<p>{html_mod.escape(item.description)}</p>"
            f"<div class='meta'>Occurrences: {item.finding_count} | "
            f"Effort: {item.estimated_effort_minutes} min</div>"
            f"{files_html}"
            f"{steps_html}"
            f"</div>"
        )

    css = """
:root{--bg:#0f172a;--surface:#1e293b;--text:#e2e8f0;--text-dim:#94a3b8;--border:#475569}
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:var(--bg);
color:var(--text);padding:2rem;max-width:900px;margin:0 auto;line-height:1.6}
h1{font-size:1.5rem;margin-bottom:.5rem}
h3{font-size:1.1rem;margin:0.5rem 0}
.subtitle{color:var(--text-dim);font-size:.85rem;margin-bottom:1.5rem}
.summary{background:var(--surface);border-radius:8px;padding:1.2rem;margin-bottom:1.5rem;
display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:1rem}
.summary-item{text-align:center}
.summary-item .label{font-size:.7rem;color:var(--text-dim);text-transform:uppercase}
.summary-item .value{font-size:1.5rem;font-weight:700}
.item{background:var(--surface);border-radius:8px;padding:1.2rem;margin-bottom:1rem}
.item-header{display:flex;gap:.5rem;align-items:center;margin-bottom:.5rem}
.badge{padding:2px 8px;border-radius:4px;color:#fff;font-size:.7rem;font-weight:600}
.rule-id{font-family:monospace;color:#3b82f6;font-size:.85rem}
.fix-type{color:var(--text-dim);font-size:.75rem}
.meta{font-size:.8rem;color:var(--text-dim);margin:.5rem 0}
.files{font-size:.8rem;color:var(--text-dim);margin:.5rem 0}
.step{margin:.75rem 0 .75rem 1rem;padding-left:.75rem;border-left:2px solid var(--border)}
.step-num{font-size:.7rem;color:var(--text-dim);margin-right:.5rem}
.code-block{margin:.5rem 0}
.code-before pre,.code-after pre{background:#1a1a2e;padding:.5rem;border-radius:4px;
font-size:.8rem;overflow-x:auto;margin:.25rem 0}
.code-before strong{color:#ef4444;font-size:.75rem}
.code-after strong{color:#22c55e;font-size:.75rem}
.footer{text-align:center;color:var(--text-dim);font-size:.75rem;margin-top:2rem;
padding-top:1rem;border-top:1px solid var(--border)}"""

    return (
        f"<!DOCTYPE html><html lang='en'><head><meta charset='UTF-8'>"
        f"<meta name='viewport' content='width=device-width,initial-scale=1.0'>"
        f"<title>{mode}MCP Remediation Plan</title>"
        f"<style>{css}</style></head><body>"
        f"<h1>{mode}MCP Remediation Plan</h1>"
        f"<div class='subtitle'>Generated: {html_mod.escape(plan.timestamp)}"
        f" | mcpaudit v2.0.0</div>"
        f"<div class='summary'>"
        f"<div class='summary-item'><div class='label'>Items</div>"
        f"<div class='value'>{plan.total_items}</div></div>"
        f"<div class='summary-item'><div class='label'>Findings</div>"
        f"<div class='value'>{plan.total_findings}</div></div>"
        f"<div class='summary-item'><div class='label'>Effort</div>"
        f"<div class='value'>{plan.total_effort_minutes}m</div></div>"
        f"<div class='summary-item'><div class='label'>Critical</div>"
        f"<div class='value' style='color:#dc2626'>{plan.critical_count}</div></div>"
        f"<div class='summary-item'><div class='label'>High</div>"
        f"<div class='value' style='color:#f97316'>{plan.high_count}</div></div>"
        f"</div>"
        f"{items_html}"
        f"<div class='footer'>Generated by mcpaudit — MCP Security Audit Scanner</div>"
        f"</body></html>"
    )


# ---------------------------------------------------------------------------
# Public helpers
# ---------------------------------------------------------------------------

def get_known_rules() -> list[str]:
    """Return list of rule IDs with known remediations."""
    return sorted(_REMEDIATION_DB.keys())


def get_fix_types() -> list[str]:
    """Return available fix type values."""
    return [ft.value for ft in FixType]


def get_remediation_info(rule_id: str) -> dict[str, Any] | None:
    """Get remediation information for a specific rule.

    Args:
        rule_id: Rule ID to look up.

    Returns:
        Dict with remediation info, or None if rule has no specific remediation.
    """
    entry = _REMEDIATION_DB.get(rule_id)
    if entry is None:
        return None
    fix_type = entry.get("fix_type", FixType.MANUAL)
    if isinstance(fix_type, FixType):
        fix_type = fix_type.value
    return {
        "rule_id": rule_id,
        "title": entry["title"],
        "fix_type": fix_type,
        "description": entry["description"],
        "effort_minutes": entry.get("effort", 30),
        "steps_count": len(entry.get("steps", [])),
    }
