"""Scorecard Generator for MCP Security Audit.

Generates a one-page executive HTML scorecard for a scanned MCP server
with overall grade, trust score dial, severity breakdown, top findings,
compliance status, and recommendations.

Self-contained HTML with inline CSS -- no external dependencies.
"""

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 typing import Any

from .models import Finding, ScanResult, ScanSummary, Severity


# ---------------------------------------------------------------------------
# Color constants
# ---------------------------------------------------------------------------

_SEVERITY_COLORS: dict[str, str] = {
    "CRITICAL": "#dc2626",
    "HIGH": "#f97316",
    "MEDIUM": "#eab308",
    "LOW": "#22c55e",
    "INFO": "#6b7280",
}

_GRADE_COLORS: dict[str, str] = {
    "A+": "#22c55e",
    "A": "#4ade80",
    "B": "#eab308",
    "C": "#f97316",
    "D": "#ef4444",
    "F": "#dc2626",
}

# OWASP Agentic Top 10 categories for compliance badges
OWASP_AGENTIC_CATEGORIES: list[dict[str, str]] = [
    {"id": "A01", "name": "Tool Poisoning", "rule_prefix": "MCP-"},
    {"id": "A02", "name": "Rug Pull", "rule_prefix": "MCP-"},
    {"id": "A03", "name": "Tool Shadowing", "rule_prefix": "MCP-"},
    {"id": "A04", "name": "Cross-Plugin Escalation", "rule_prefix": "AUTH-"},
    {"id": "A05", "name": "Remote Access Trojan", "rule_prefix": "CMD-"},
    {"id": "A06", "name": "PII Harvesting", "rule_prefix": "PRIV-"},
    {"id": "A07", "name": "Prompt Injection", "rule_prefix": "INJ-"},
    {"id": "A08", "name": "Confused Deputy", "rule_prefix": "AUTH-"},
    {"id": "A09", "name": "Server Compromise", "rule_prefix": "TRAV-"},
    {"id": "A10", "name": "Logging Gaps", "rule_prefix": "LOG-"},
]


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

@dataclass
class ScorecardData:
    """Processed data for scorecard generation."""

    overall_grade: str = "A+"
    trust_score: int = 100
    trust_label: str = "EXCELLENT"
    total_findings: int = 0
    severity_counts: dict[str, int] = field(default_factory=dict)
    top_findings: list[dict[str, str]] = field(default_factory=list)
    compliance_status: list[dict[str, Any]] = field(default_factory=list)
    recommendations: list[str] = field(default_factory=list)
    files_scanned: int = 0
    lines_scanned: int = 0
    scan_time_ms: float = 0.0
    target: str = ""

    def to_dict(self) -> dict[str, Any]:
        return {
            "overall_grade": self.overall_grade,
            "trust_score": self.trust_score,
            "trust_label": self.trust_label,
            "total_findings": self.total_findings,
            "severity_counts": self.severity_counts,
            "top_findings": self.top_findings,
            "compliance_status": self.compliance_status,
            "recommendations": self.recommendations,
            "files_scanned": self.files_scanned,
            "lines_scanned": self.lines_scanned,
            "scan_time_ms": self.scan_time_ms,
            "target": self.target,
        }


# ---------------------------------------------------------------------------
# Score calculations
# ---------------------------------------------------------------------------

def _compute_trust_score(severity_counts: dict[str, int]) -> int:
    """Compute trust score 0-100 from severity counts.

    Higher = better. 100 = clean.
    """
    deductions = {
        "CRITICAL": 25,
        "HIGH": 15,
        "MEDIUM": 8,
        "LOW": 3,
        "INFO": 1,
    }
    caps = {
        "CRITICAL": 2,
        "HIGH": 3,
        "MEDIUM": 4,
        "LOW": 5,
        "INFO": 10,
    }
    score = 100
    for sev, ded in deductions.items():
        count = severity_counts.get(sev, 0)
        capped = min(count, caps.get(sev, 10))
        score -= capped * ded
    return max(0, min(100, score))


def _trust_label(score: int) -> str:
    """Map trust score to label."""
    if score >= 90:
        return "EXCELLENT"
    if score >= 75:
        return "GOOD"
    if score >= 50:
        return "MODERATE"
    if score >= 25:
        return "LOW"
    return "CRITICAL"


def _grade_from_score(score: int) -> str:
    """Map trust score to grade letter."""
    if score >= 95:
        return "A+"
    if score >= 85:
        return "A"
    if score >= 70:
        return "B"
    if score >= 50:
        return "C"
    if score >= 25:
        return "D"
    return "F"


def _grade_color(grade: str) -> str:
    """Return hex color for grade."""
    return _GRADE_COLORS.get(grade, "#6b7280")


def _severity_color(sev: str) -> str:
    """Return hex color for severity."""
    return _SEVERITY_COLORS.get(sev, "#6b7280")


# ---------------------------------------------------------------------------
# Scorecard building from scan data
# ---------------------------------------------------------------------------

def build_scorecard_data(scan_data: dict[str, Any]) -> ScorecardData:
    """Build scorecard data from a scan results dict.

    Accepts the dict format returned by ScanSummary.to_dict().

    Args:
        scan_data: Scan results dictionary.

    Returns:
        ScorecardData ready for HTML rendering.
    """
    summary = scan_data.get("summary", {})
    results = scan_data.get("results", [])

    sev_counts = summary.get("severity_counts", {})
    total = summary.get("total_findings", 0)
    trust = _compute_trust_score(sev_counts)

    # Top findings (deduplicated by rule_id, sorted by severity)
    all_findings: list[dict[str, Any]] = []
    for r in results:
        for f in r.get("findings", []):
            all_findings.append(f)

    sev_order = {"CRITICAL": 0, "HIGH": 1, "MEDIUM": 2, "LOW": 3, "INFO": 4}
    all_findings.sort(
        key=lambda x: sev_order.get(x.get("severity", "INFO"), 5)
    )

    seen_rules: set[str] = set()
    top: list[dict[str, str]] = []
    for f in all_findings:
        rid = f.get("rule_id", "")
        if rid not in seen_rules and len(top) < 5:
            seen_rules.add(rid)
            top.append({
                "rule_id": rid,
                "severity": f.get("severity", "INFO"),
                "title": f.get("title", ""),
                "file": f.get("file", ""),
                "line": str(f.get("line", 0)),
            })

    # Compliance status
    compliance = _build_compliance_status(all_findings)

    # Recommendations
    recs = _build_recommendations(sev_counts, total, trust)

    return ScorecardData(
        overall_grade=_grade_from_score(trust),
        trust_score=trust,
        trust_label=_trust_label(trust),
        total_findings=total,
        severity_counts=sev_counts,
        top_findings=top,
        compliance_status=compliance,
        recommendations=recs,
        files_scanned=summary.get("files_scanned", 0),
        lines_scanned=summary.get("lines_scanned", 0),
        scan_time_ms=summary.get("scan_time_ms", 0.0),
    )


def build_scorecard_from_summary(summary: ScanSummary) -> ScorecardData:
    """Build scorecard from a ScanSummary object.

    Args:
        summary: ScanSummary from a scan run.

    Returns:
        ScorecardData ready for rendering.
    """
    return build_scorecard_data(summary.to_dict())


def _build_compliance_status(
    findings: list[dict[str, Any]],
) -> list[dict[str, Any]]:
    """Build OWASP compliance status from findings."""
    # Collect rule_ids from findings
    rule_ids = {f.get("rule_id", "") for f in findings}

    status: list[dict[str, Any]] = []
    for cat in OWASP_AGENTIC_CATEGORIES:
        prefix = cat["rule_prefix"]
        matched = [rid for rid in rule_ids if rid.startswith(prefix)]
        status.append({
            "id": cat["id"],
            "name": cat["name"],
            "pass": len(matched) == 0,
            "findings": len(matched),
        })

    return status


def _build_recommendations(
    sev_counts: dict[str, int],
    total: int,
    trust: int,
) -> list[str]:
    """Generate actionable recommendations."""
    recs: list[str] = []

    if sev_counts.get("CRITICAL", 0) > 0:
        recs.append(
            "Address CRITICAL findings immediately -- "
            "they represent exploitable vulnerabilities."
        )

    if sev_counts.get("HIGH", 0) > 0:
        recs.append(
            "Fix HIGH severity findings before deploying to production."
        )

    if total == 0:
        recs.append(
            "No findings detected. Continue monitoring with regular scans."
        )

    if trust < 50:
        recs.append(
            "Trust score is below 50 -- consider a full security review."
        )

    if sev_counts.get("MEDIUM", 0) > 3:
        recs.append(
            "Multiple MEDIUM findings detected -- "
            "schedule a refactoring sprint."
        )

    if not recs:
        recs.append("Good security posture. Keep scanning regularly.")

    return recs


# ---------------------------------------------------------------------------
# HTML generation
# ---------------------------------------------------------------------------

def generate_scorecard_html(data: ScorecardData) -> str:
    """Generate self-contained HTML scorecard.

    Args:
        data: ScorecardData to render.

    Returns:
        Complete HTML string with inline CSS.
    """
    timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
    grade_color = _grade_color(data.overall_grade)

    # Severity pie chart (CSS conic-gradient)
    pie_css = _build_pie_gradient(data.severity_counts, data.total_findings)

    # Top findings rows
    findings_html = _build_findings_table(data.top_findings)

    # Compliance badges
    compliance_html = _build_compliance_badges(data.compliance_status)

    # Recommendations
    recs_html = "".join(
        f"<li>{html_mod.escape(r)}</li>" for r in data.recommendations
    )

    # Trust score dial angle (0-100 -> 0-180 degrees)
    dial_angle = int(data.trust_score * 1.8)

    target_display = html_mod.escape(data.target) if data.target else "N/A"

    return f"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>MCP Security Scorecard</title>
<style>
  * {{ margin: 0; padding: 0; box-sizing: border-box; }}
  body {{
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    background: #0f172a; color: #e2e8f0; padding: 2em;
  }}
  .header {{ text-align: center; margin-bottom: 2em; }}
  .header h1 {{ color: #38bdf8; font-size: 1.8em; margin-bottom: 0.3em; }}
  .header .meta {{ color: #64748b; font-size: 0.9em; }}
  .grid {{ display: grid; grid-template-columns: 1fr 1fr; gap: 1.5em; margin-bottom: 1.5em; }}
  .card {{
    background: #1e293b; border-radius: 12px; padding: 1.5em;
    border: 1px solid #334155;
  }}
  .card h2 {{ color: #94a3b8; font-size: 1em; margin-bottom: 1em;
    border-bottom: 1px solid #334155; padding-bottom: 0.5em; }}
  .grade-display {{ text-align: center; }}
  .grade {{ font-size: 4em; font-weight: bold; color: {grade_color}; }}
  .grade-label {{ color: #94a3b8; font-size: 0.9em; }}
  .dial-container {{ text-align: center; position: relative; }}
  .dial {{
    width: 120px; height: 60px; border-radius: 60px 60px 0 0;
    background: conic-gradient(
      from 180deg at 50% 100%,
      #dc2626 0deg, #f97316 45deg, #eab308 90deg,
      #22c55e 135deg, #22c55e 180deg, #1e293b 180deg
    );
    margin: 0 auto; position: relative; overflow: hidden;
  }}
  .dial-needle {{
    position: absolute; bottom: 0; left: 50%;
    width: 2px; height: 55px; background: #e2e8f0;
    transform-origin: bottom center;
    transform: translateX(-50%) rotate({dial_angle - 90}deg);
  }}
  .dial-value {{
    font-size: 2em; font-weight: bold; color: #38bdf8; margin-top: 0.3em;
  }}
  .dial-label {{ color: #94a3b8; font-size: 0.9em; }}
  .pie {{
    width: 100px; height: 100px; border-radius: 50%;
    background: {pie_css}; margin: 0 auto 1em;
  }}
  .sev-legend {{ display: flex; flex-wrap: wrap; gap: 0.5em; justify-content: center; }}
  .sev-item {{ display: flex; align-items: center; gap: 4px; font-size: 0.85em; }}
  .sev-dot {{ width: 10px; height: 10px; border-radius: 50%; }}
  table {{ width: 100%; border-collapse: collapse; }}
  th, td {{ text-align: left; padding: 6px 10px; border-bottom: 1px solid #334155; }}
  th {{ color: #64748b; font-weight: 600; font-size: 0.85em; }}
  td {{ font-size: 0.9em; }}
  .badge {{
    display: inline-block; padding: 3px 10px; border-radius: 12px;
    font-size: 0.8em; font-weight: 600;
  }}
  .badge-pass {{ background: #064e3b; color: #34d399; }}
  .badge-fail {{ background: #7f1d1d; color: #fca5a5; }}
  .compliance-grid {{ display: flex; flex-wrap: wrap; gap: 0.5em; }}
  .recs {{ list-style: disc; padding-left: 1.5em; }}
  .recs li {{ margin-bottom: 0.5em; color: #cbd5e1; }}
  .stats {{ display: flex; gap: 2em; justify-content: center; margin: 1em 0; }}
  .stat {{ text-align: center; }}
  .stat-val {{ font-size: 1.5em; font-weight: bold; color: #38bdf8; }}
  .stat-lbl {{ color: #64748b; font-size: 0.8em; }}
  .full-width {{ grid-column: 1 / -1; }}
  .footer {{ text-align: center; color: #475569; font-size: 0.8em; margin-top: 2em; }}
</style>
</head>
<body>
<div class="header">
  <h1>MCP Security Scorecard</h1>
  <div class="meta">Generated: {timestamp} | Target: {target_display}</div>
</div>

<div class="stats">
  <div class="stat"><div class="stat-val">{data.files_scanned}</div><div class="stat-lbl">Files</div></div>
  <div class="stat"><div class="stat-val">{data.lines_scanned:,}</div><div class="stat-lbl">Lines</div></div>
  <div class="stat"><div class="stat-val">{data.total_findings}</div><div class="stat-lbl">Findings</div></div>
  <div class="stat"><div class="stat-val">{data.scan_time_ms:.0f}ms</div><div class="stat-lbl">Scan Time</div></div>
</div>

<div class="grid">
  <div class="card">
    <h2>Overall Grade</h2>
    <div class="grade-display">
      <div class="grade">{data.overall_grade}</div>
      <div class="grade-label">{data.trust_label}</div>
    </div>
  </div>

  <div class="card">
    <h2>Trust Score</h2>
    <div class="dial-container">
      <div class="dial"><div class="dial-needle"></div></div>
      <div class="dial-value">{data.trust_score}/100</div>
      <div class="dial-label">{data.trust_label}</div>
    </div>
  </div>

  <div class="card">
    <h2>Severity Breakdown</h2>
    <div class="pie"></div>
    <div class="sev-legend">
      {_build_severity_legend(data.severity_counts)}
    </div>
  </div>

  <div class="card">
    <h2>Compliance Status</h2>
    <div class="compliance-grid">
      {compliance_html}
    </div>
  </div>

  <div class="card full-width">
    <h2>Top Findings</h2>
    {findings_html}
  </div>

  <div class="card full-width">
    <h2>Recommendations</h2>
    <ul class="recs">{recs_html}</ul>
  </div>
</div>

<div class="footer">
  Generated by mcpaudit | MCP Security Audit Scanner
</div>
</body>
</html>"""


def _build_pie_gradient(
    sev_counts: dict[str, int],
    total: int,
) -> str:
    """Build CSS conic-gradient for severity pie chart."""
    if total == 0:
        return "#22c55e"

    segments: list[str] = []
    cumulative = 0.0
    order = ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO"]

    for sev in order:
        count = sev_counts.get(sev, 0)
        if count == 0:
            continue
        pct = (count / total) * 100
        color = _severity_color(sev)
        start = cumulative
        end = cumulative + pct
        segments.append(f"{color} {start:.1f}% {end:.1f}%")
        cumulative = end

    if not segments:
        return "#22c55e"

    return f"conic-gradient({', '.join(segments)})"


def _build_severity_legend(sev_counts: dict[str, int]) -> str:
    """Build HTML for severity legend items."""
    items: list[str] = []
    for sev in ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO"]:
        count = sev_counts.get(sev, 0)
        color = _severity_color(sev)
        items.append(
            f'<div class="sev-item">'
            f'<div class="sev-dot" style="background:{color}"></div>'
            f'{sev}: {count}</div>'
        )
    return "\n".join(items)


def _build_findings_table(
    findings: list[dict[str, str]],
) -> str:
    """Build HTML table for top findings."""
    if not findings:
        return "<p style='color:#64748b;'>No findings detected.</p>"

    rows = ""
    for f in findings:
        sev = f.get("severity", "INFO")
        color = _severity_color(sev)
        rows += f"""
    <tr>
      <td><span style="color:{color};font-weight:bold;">{html_mod.escape(sev)}</span></td>
      <td><code>{html_mod.escape(f.get('rule_id', ''))}</code></td>
      <td>{html_mod.escape(f.get('title', ''))}</td>
      <td>{html_mod.escape(f.get('file', ''))}:{html_mod.escape(f.get('line', '0'))}</td>
    </tr>"""

    return f"""<table>
    <tr><th>Severity</th><th>Rule</th><th>Title</th><th>Location</th></tr>
    {rows}
    </table>"""


def _build_compliance_badges(
    compliance: list[dict[str, Any]],
) -> str:
    """Build HTML badges for OWASP compliance."""
    badges: list[str] = []
    for item in compliance:
        passed = item.get("pass", True)
        cls = "badge-pass" if passed else "badge-fail"
        label = "PASS" if passed else f"FAIL ({item.get('findings', 0)})"
        name = html_mod.escape(f"{item['id']}: {item['name']}")
        badges.append(f'<span class="badge {cls}">{name} - {label}</span>')
    return "\n".join(badges)


# ---------------------------------------------------------------------------
# Convenience functions
# ---------------------------------------------------------------------------

def generate_scorecard(scan_data: dict[str, Any]) -> str:
    """Generate HTML scorecard from scan results dict.

    Args:
        scan_data: Scan results dict (ScanSummary.to_dict() format).

    Returns:
        Complete HTML string.
    """
    data = build_scorecard_data(scan_data)
    return generate_scorecard_html(data)


def generate_scorecard_from_summary(
    summary: ScanSummary,
    target: str = "",
) -> str:
    """Generate HTML scorecard from ScanSummary.

    Args:
        summary: ScanSummary from a scan run.
        target: Optional target path for display.

    Returns:
        Complete HTML string.
    """
    data = build_scorecard_from_summary(summary)
    data.target = target
    return generate_scorecard_html(data)


def format_scorecard_json(data: ScorecardData) -> str:
    """Format scorecard data as JSON."""
    return json.dumps(data.to_dict(), indent=2, ensure_ascii=False)
