"""Summary Dashboard -- Self-contained HTML dashboard for scan results.

Generates a single-file HTML dashboard with:
- Executive summary with risk score
- Findings by severity (CSS pie chart)
- Findings by category (CSS bar chart)
- Timeline (if history available)
- Top 5 most common findings
- Remediation priority queue

No external dependencies -- pure inline CSS.
CLI: mcpaudit scan <path> --dashboard output.html
"""

from __future__ import annotations

import html
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",
}


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


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


# ---------------------------------------------------------------------------
# Risk score calculation
# ---------------------------------------------------------------------------

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

    Higher = more risk. 0 = clean.

    Args:
        severity_counts: Mapping of severity name to count.

    Returns:
        Integer risk score 0-100.
    """
    weights = {
        "CRITICAL": 25,
        "HIGH": 15,
        "MEDIUM": 5,
        "LOW": 2,
        "INFO": 0,
    }
    raw = sum(weights.get(sev, 0) * count for sev, count in severity_counts.items())
    return min(100, raw)


def _risk_label(score: int) -> str:
    """Return risk label for a numeric score."""
    if score == 0:
        return "CLEAN"
    if score <= 15:
        return "LOW"
    if score <= 40:
        return "MODERATE"
    if score <= 70:
        return "HIGH"
    return "CRITICAL"


def _risk_color(score: int) -> str:
    """Return color for a risk score."""
    if score == 0:
        return "#22c55e"
    if score <= 15:
        return "#4ade80"
    if score <= 40:
        return "#eab308"
    if score <= 70:
        return "#f97316"
    return "#dc2626"


# ---------------------------------------------------------------------------
# Category extraction
# ---------------------------------------------------------------------------

def _extract_category(rule_id: str) -> str:
    """Extract category prefix from a rule ID (e.g. 'MCP-003' -> 'MCP')."""
    parts = rule_id.rsplit("-", 1)
    return parts[0] if len(parts) == 2 else rule_id


# ---------------------------------------------------------------------------
# Remediation priority
# ---------------------------------------------------------------------------

_SEVERITY_PRIORITY: dict[str, int] = {
    "CRITICAL": 0,
    "HIGH": 1,
    "MEDIUM": 2,
    "LOW": 3,
    "INFO": 4,
}


@dataclass
class RemediationItem:
    """A prioritized remediation action."""

    rule_id: str
    severity: str
    title: str
    count: int
    recommendation: str
    priority: int = 0


def _build_remediation_queue(
    all_findings: list[Finding],
) -> list[RemediationItem]:
    """Build a prioritized remediation queue.

    Groups findings by rule_id, sorts by severity then count.

    Args:
        all_findings: All findings from the scan.

    Returns:
        Sorted list of RemediationItem.
    """
    groups: dict[str, list[Finding]] = {}
    for f in all_findings:
        groups.setdefault(f.rule_id, []).append(f)

    items: list[RemediationItem] = []
    for rule_id, findings in groups.items():
        first = findings[0]
        items.append(RemediationItem(
            rule_id=rule_id,
            severity=first.severity.value,
            title=first.title,
            count=len(findings),
            recommendation=first.recommendation or "Review and fix the identified issue.",
            priority=_SEVERITY_PRIORITY.get(first.severity.value, 4),
        ))

    items.sort(key=lambda x: (x.priority, -x.count))
    return items


# ---------------------------------------------------------------------------
# HTML Sections
# ---------------------------------------------------------------------------

def _build_executive_summary(
    grade: str,
    risk_score: int,
    total_files: int,
    total_findings: int,
    total_lines: int,
    scan_time_ms: float,
    severity_counts: dict[str, int],
    timestamp: str,
) -> str:
    """Build the executive summary section."""
    gc = _grade_color(grade)
    rc = _risk_color(risk_score)
    rl = _risk_label(risk_score)

    return f"""
    <section class="section">
        <h2>Executive Summary</h2>
        <div class="cards">
            <div class="card">
                <div class="card-label">Security Grade</div>
                <div class="card-value" style="color:{gc};font-size:3rem">{html.escape(grade)}</div>
            </div>
            <div class="card">
                <div class="card-label">Risk Score</div>
                <div class="card-value" style="color:{rc}">{risk_score}/100</div>
                <div class="card-sub">{rl}</div>
            </div>
            <div class="card">
                <div class="card-label">Files Scanned</div>
                <div class="card-value">{total_files}</div>
            </div>
            <div class="card">
                <div class="card-label">Total Findings</div>
                <div class="card-value">{total_findings}</div>
            </div>
            <div class="card">
                <div class="card-label">Lines Analyzed</div>
                <div class="card-value">{total_lines:,}</div>
            </div>
            <div class="card">
                <div class="card-label">Scan Time</div>
                <div class="card-value">{scan_time_ms:.0f}ms</div>
            </div>
        </div>
        <div class="meta">Generated: {html.escape(timestamp)}</div>
    </section>"""


def _build_severity_pie(severity_counts: dict[str, int], total: int) -> str:
    """Build a CSS-only pie chart showing findings by severity.

    Uses conic-gradient for the pie chart.

    Args:
        severity_counts: Mapping of severity to count.
        total: Total findings count.

    Returns:
        HTML string for the pie chart section.
    """
    if total == 0:
        return """
    <section class="section">
        <h2>Findings by Severity</h2>
        <p class="empty-msg">No findings detected. Clean scan.</p>
    </section>"""

    # Build conic gradient stops
    stops: list[str] = []
    cumulative = 0.0
    legend_items: list[str] = []
    order = ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO"]

    for sev in order:
        count = severity_counts.get(sev, 0)
        if count == 0:
            continue
        pct = count / total * 100
        color = _severity_color(sev)
        stops.append(f"{color} {cumulative:.1f}% {cumulative + pct:.1f}%")
        cumulative += pct
        legend_items.append(
            f'<div class="legend-item">'
            f'<span class="legend-dot" style="background:{color}"></span>'
            f'{sev}: {count} ({pct:.0f}%)</div>'
        )

    gradient = ", ".join(stops)

    return f"""
    <section class="section">
        <h2>Findings by Severity</h2>
        <div class="chart-container">
            <div class="pie" style="background:conic-gradient({gradient})"></div>
            <div class="legend">
                {''.join(legend_items)}
            </div>
        </div>
    </section>"""


def _build_category_bars(all_findings: list[Finding]) -> str:
    """Build a CSS bar chart showing findings by category.

    Args:
        all_findings: All findings from the scan.

    Returns:
        HTML string for the category bar chart section.
    """
    if not all_findings:
        return ""

    counter: Counter[str] = Counter()
    for f in all_findings:
        counter[_extract_category(f.rule_id)] += 1

    top_cats = counter.most_common(15)
    max_count = top_cats[0][1] if top_cats else 1

    bars = ""
    colors = [
        "#3b82f6", "#8b5cf6", "#ec4899", "#14b8a6", "#f97316",
        "#06b6d4", "#84cc16", "#ef4444", "#a855f7", "#f59e0b",
        "#22d3ee", "#10b981", "#e879f9", "#fb923c", "#64748b",
    ]
    for i, (cat, count) in enumerate(top_cats):
        width_pct = count / max_count * 100
        color = colors[i % len(colors)]
        bars += f"""
            <div class="bar-row">
                <span class="bar-label">{html.escape(cat)}</span>
                <div class="bar-bg">
                    <div class="bar-fill" style="width:{width_pct:.0f}%;background:{color}"></div>
                </div>
                <span class="bar-count">{count}</span>
            </div>"""

    return f"""
    <section class="section">
        <h2>Findings by Category</h2>
        <div class="bar-chart">
            {bars}
        </div>
    </section>"""


def _build_top_findings(all_findings: list[Finding], limit: int = 5) -> str:
    """Build the top N most common findings section.

    Args:
        all_findings: All findings from the scan.
        limit: Maximum number of findings to show.

    Returns:
        HTML string for the top findings section.
    """
    if not all_findings:
        return ""

    counter: Counter[str] = Counter()
    first_finding: dict[str, Finding] = {}
    for f in all_findings:
        counter[f.rule_id] += 1
        if f.rule_id not in first_finding:
            first_finding[f.rule_id] = f

    top = counter.most_common(limit)
    rows = ""
    for rank, (rule_id, count) in enumerate(top, 1):
        f = first_finding[rule_id]
        sc = _severity_color(f.severity.value)
        rows += f"""
            <tr>
                <td class="rank">{rank}</td>
                <td><span class="sev-badge" style="background:{sc}">{f.severity.value}</span></td>
                <td class="rule-id">{html.escape(rule_id)}</td>
                <td>{html.escape(f.title)}</td>
                <td class="count">{count}</td>
            </tr>"""

    return f"""
    <section class="section">
        <h2>Top {limit} Most Common Findings</h2>
        <table class="findings-table">
            <thead>
                <tr>
                    <th>#</th>
                    <th>Severity</th>
                    <th>Rule</th>
                    <th>Title</th>
                    <th>Count</th>
                </tr>
            </thead>
            <tbody>
                {rows}
            </tbody>
        </table>
    </section>"""


def _build_remediation_section(items: list[RemediationItem]) -> str:
    """Build the remediation priority queue section.

    Args:
        items: Sorted remediation items.

    Returns:
        HTML string for the remediation section.
    """
    if not items:
        return ""

    rows = ""
    for i, item in enumerate(items, 1):
        sc = _severity_color(item.severity)
        rows += f"""
            <div class="remed-item">
                <div class="remed-header">
                    <span class="remed-rank">{i}</span>
                    <span class="sev-badge" style="background:{sc}">{item.severity}</span>
                    <span class="remed-rule">{html.escape(item.rule_id)}</span>
                    <span class="remed-title">{html.escape(item.title)}</span>
                    <span class="remed-count">{item.count}x</span>
                </div>
                <div class="remed-body">
                    {html.escape(item.recommendation)}
                </div>
            </div>"""

    return f"""
    <section class="section">
        <h2>Remediation Priority Queue</h2>
        {rows}
    </section>"""


def _build_timeline(history: list[dict[str, Any]]) -> str:
    """Build a timeline section from scan history.

    Args:
        history: List of history entries (from history.load_history).

    Returns:
        HTML string for the timeline section, or empty string if no history.
    """
    if not history:
        return ""

    points: list[dict[str, Any]] = []
    for entry in history[-10:]:  # Last 10 entries
        scan_data = entry.get("scan", {})
        summary_data = scan_data.get("summary", {})
        ts = entry.get("timestamp", "")
        total = summary_data.get("total_findings", 0)
        grade = summary_data.get("overall_grade", "?")
        points.append({"ts": ts, "findings": total, "grade": grade})

    if not points:
        return ""

    max_findings = max((p["findings"] for p in points), default=1) or 1

    bars = ""
    for p in points:
        height_pct = p["findings"] / max_findings * 100
        gc = _grade_color(p["grade"])
        label = p["ts"][:10] if len(p["ts"]) >= 10 else p["ts"]
        bars += f"""
            <div class="timeline-bar">
                <div class="timeline-fill" style="height:{height_pct:.0f}%;background:{gc}">
                    <span class="timeline-val">{p["findings"]}</span>
                </div>
                <div class="timeline-label">{html.escape(label)}</div>
            </div>"""

    return f"""
    <section class="section">
        <h2>Findings Timeline</h2>
        <div class="timeline-chart">
            {bars}
        </div>
    </section>"""


# ---------------------------------------------------------------------------
# HTML wrapper with inline CSS
# ---------------------------------------------------------------------------

def _wrap_html(title: str, body: str) -> str:
    """Wrap dashboard sections in a complete HTML document.

    Args:
        title: Page title.
        body: Inner HTML body content.

    Returns:
        Complete HTML document string.
    """
    return f"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{html.escape(title)}</title>
<style>
:root {{
    --bg: #0f172a;
    --surface: #1e293b;
    --surface2: #334155;
    --text: #e2e8f0;
    --text-dim: #94a3b8;
    --border: #475569;
    --accent: #3b82f6;
}}
* {{ box-sizing: border-box; margin: 0; padding: 0; }}
body {{
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    background: var(--bg);
    color: var(--text);
    line-height: 1.6;
    padding: 2rem;
    max-width: 1200px;
    margin: 0 auto;
}}
h1 {{
    font-size: 1.8rem;
    margin-bottom: 0.5rem;
    color: var(--text);
}}
h2 {{
    font-size: 1.3rem;
    margin-bottom: 1rem;
    color: var(--text);
    border-bottom: 1px solid var(--border);
    padding-bottom: 0.5rem;
}}
.header {{
    text-align: center;
    margin-bottom: 2rem;
}}
.header .subtitle {{
    color: var(--text-dim);
    font-size: 0.9rem;
}}
.section {{
    background: var(--surface);
    border-radius: 12px;
    padding: 1.5rem;
    margin-bottom: 1.5rem;
}}
.cards {{
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 1rem;
    margin-bottom: 1rem;
}}
.card {{
    background: var(--surface2);
    border-radius: 8px;
    padding: 1rem;
    text-align: center;
}}
.card-label {{
    font-size: 0.75rem;
    color: var(--text-dim);
    text-transform: uppercase;
    letter-spacing: 0.05em;
}}
.card-value {{
    font-size: 1.8rem;
    font-weight: 700;
    margin: 0.25rem 0;
}}
.card-sub {{
    font-size: 0.8rem;
    color: var(--text-dim);
}}
.meta {{
    font-size: 0.75rem;
    color: var(--text-dim);
    text-align: right;
    margin-top: 0.5rem;
}}
.empty-msg {{
    color: #22c55e;
    font-size: 1.1rem;
    text-align: center;
    padding: 2rem;
}}
/* Pie chart */
.chart-container {{
    display: flex;
    align-items: center;
    gap: 2rem;
    flex-wrap: wrap;
    justify-content: center;
}}
.pie {{
    width: 200px;
    height: 200px;
    border-radius: 50%;
    flex-shrink: 0;
}}
.legend {{
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}}
.legend-item {{
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.9rem;
}}
.legend-dot {{
    width: 12px;
    height: 12px;
    border-radius: 50%;
    flex-shrink: 0;
}}
/* Bar chart */
.bar-chart {{
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}}
.bar-row {{
    display: flex;
    align-items: center;
    gap: 0.75rem;
}}
.bar-label {{
    min-width: 80px;
    font-size: 0.8rem;
    color: var(--text-dim);
    text-align: right;
}}
.bar-bg {{
    flex: 1;
    background: var(--surface2);
    border-radius: 4px;
    height: 20px;
    overflow: hidden;
}}
.bar-fill {{
    height: 100%;
    border-radius: 4px;
    transition: width 0.3s;
}}
.bar-count {{
    min-width: 30px;
    font-size: 0.8rem;
    color: var(--text-dim);
}}
/* Findings table */
.findings-table {{
    width: 100%;
    border-collapse: collapse;
}}
.findings-table th,
.findings-table td {{
    padding: 0.5rem 0.75rem;
    text-align: left;
    border-bottom: 1px solid var(--border);
    font-size: 0.85rem;
}}
.findings-table th {{
    color: var(--text-dim);
    font-weight: 600;
    text-transform: uppercase;
    font-size: 0.7rem;
    letter-spacing: 0.05em;
}}
.rank {{ width: 30px; text-align: center; }}
.count {{ text-align: center; font-weight: 600; }}
.rule-id {{ font-family: monospace; color: var(--accent); }}
.sev-badge {{
    display: inline-block;
    padding: 2px 8px;
    border-radius: 4px;
    color: #fff;
    font-size: 0.7rem;
    font-weight: 600;
}}
/* Remediation */
.remed-item {{
    background: var(--surface2);
    border-radius: 8px;
    margin-bottom: 0.75rem;
    overflow: hidden;
}}
.remed-header {{
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.75rem 1rem;
    font-size: 0.85rem;
}}
.remed-rank {{
    background: var(--accent);
    color: #fff;
    width: 24px;
    height: 24px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.7rem;
    font-weight: 700;
    flex-shrink: 0;
}}
.remed-rule {{
    font-family: monospace;
    color: var(--accent);
}}
.remed-title {{
    flex: 1;
}}
.remed-count {{
    color: var(--text-dim);
    font-size: 0.8rem;
}}
.remed-body {{
    padding: 0 1rem 0.75rem 3rem;
    color: var(--text-dim);
    font-size: 0.8rem;
}}
/* Timeline */
.timeline-chart {{
    display: flex;
    align-items: flex-end;
    gap: 0.5rem;
    height: 200px;
    padding-top: 1rem;
}}
.timeline-bar {{
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-end;
    height: 100%;
}}
.timeline-fill {{
    width: 100%;
    border-radius: 4px 4px 0 0;
    display: flex;
    align-items: flex-start;
    justify-content: center;
    min-height: 4px;
}}
.timeline-val {{
    font-size: 0.7rem;
    font-weight: 600;
    padding-top: 4px;
}}
.timeline-label {{
    font-size: 0.65rem;
    color: var(--text-dim);
    margin-top: 4px;
    writing-mode: vertical-rl;
    transform: rotate(180deg);
    max-height: 60px;
    overflow: hidden;
}}
.footer {{
    text-align: center;
    color: var(--text-dim);
    font-size: 0.75rem;
    margin-top: 2rem;
    padding-top: 1rem;
    border-top: 1px solid var(--border);
}}
</style>
</head>
<body>
<div class="header">
    <h1>{html.escape(title)}</h1>
    <div class="subtitle">mcpaudit Security Dashboard</div>
</div>
{body}
<div class="footer">
    Generated by mcpaudit &mdash; MCP Security Audit Scanner
</div>
</body>
</html>"""


# ---------------------------------------------------------------------------
# Public API
# ---------------------------------------------------------------------------

def generate_dashboard(
    summary: ScanSummary,
    title: str = "Scan Summary Dashboard",
    history: list[dict[str, Any]] | None = None,
) -> str:
    """Generate a self-contained HTML dashboard from scan results.

    Args:
        summary: ScanSummary object from the scanner.
        title: Dashboard title.
        history: Optional list of history entries for timeline.

    Returns:
        Complete HTML document string.
    """
    now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
    severity_counts = summary.severity_counts
    total_findings = summary.total_findings
    risk_score = _compute_risk_score(severity_counts)

    all_findings: list[Finding] = []
    for r in summary.results:
        all_findings.extend(r.findings)

    remediation_items = _build_remediation_queue(all_findings)

    sections: list[str] = [
        _build_executive_summary(
            grade=summary.overall_grade,
            risk_score=risk_score,
            total_files=summary.total_files,
            total_findings=total_findings,
            total_lines=summary.total_lines,
            scan_time_ms=summary.total_scan_time_ms,
            severity_counts=severity_counts,
            timestamp=now,
        ),
        _build_severity_pie(severity_counts, total_findings),
        _build_category_bars(all_findings),
        _build_timeline(history or []),
        _build_top_findings(all_findings, limit=5),
        _build_remediation_section(remediation_items),
    ]

    body = "\n".join(s for s in sections if s)
    return _wrap_html(title, body)


def generate_dashboard_from_dict(
    data: dict[str, Any],
    title: str = "Scan Summary Dashboard",
    history: list[dict[str, Any]] | None = None,
) -> str:
    """Generate dashboard from a scan result dictionary.

    Useful when loading from JSON.

    Args:
        data: Scan result dictionary (as from ScanSummary.to_dict()).
        title: Dashboard title.
        history: Optional history entries for timeline.

    Returns:
        Complete HTML document string.
    """
    summary_data = data.get("summary", data)
    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),
            scan_time_ms=rd.get("scan_time_ms", 0.0),
        ))

    summary = ScanSummary(results=results)
    summary.total_scan_time_ms = summary_data.get("scan_time_ms", 0.0)

    return generate_dashboard(summary, title=title, history=history)
