"""Fleet HTML Executive Report -- Self-contained HTML for multi-server scans.

Generates a comprehensive HTML executive report for scanning multiple
MCP servers. Includes executive summary, risk heatmap, per-server detail,
trend data, and top recommendations.

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

from __future__ import annotations

import html
import json
from datetime import datetime, timezone
from typing import Any

from .models import ScanResult, Severity


# ---------------------------------------------------------------------------
# Grade utilities
# ---------------------------------------------------------------------------

_GRADE_ORDER: list[str] = ["A+", "A", "B", "C", "D", "F"]

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

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

_HEATMAP_COLORS: dict[int, str] = {
    0: "#1e293b",   # none
    1: "#1e3a2f",   # low
    2: "#3a3a1e",   # medium
    3: "#3a2a1e",   # high
    4: "#3a1e1e",   # critical
}


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


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


def _heatmap_color(count: int) -> str:
    """Return background color for heatmap cell based on finding count."""
    if count == 0:
        return _HEATMAP_COLORS[0]
    if count <= 2:
        return _HEATMAP_COLORS[1]
    if count <= 5:
        return _HEATMAP_COLORS[2]
    if count <= 10:
        return _HEATMAP_COLORS[3]
    return _HEATMAP_COLORS[4]


def _compute_grade(scan_result: ScanResult) -> str:
    """Compute grade from a ScanResult."""
    return scan_result.grade


def _compute_overall_grade(results: list[ScanResult]) -> str:
    """Compute worst grade across all results."""
    if not results:
        return "A+"
    worst = 0
    for r in results:
        grade = r.grade
        idx = _GRADE_ORDER.index(grade) if grade in _GRADE_ORDER else 5
        worst = max(worst, idx)
    return _GRADE_ORDER[worst]


# ---------------------------------------------------------------------------
# Report generation
# ---------------------------------------------------------------------------

def generate_fleet_report(
    scan_results: list[ScanResult],
    title: str = "Fleet Security Report",
    history: list[dict[str, Any]] | None = None,
) -> str:
    """Generate a self-contained HTML executive report for fleet scanning.

    Args:
        scan_results: List of ScanResult objects from scanning multiple servers.
        title: Report title.
        history: Optional list of previous scan summaries for trend data.

    Returns:
        Complete HTML document string (self-contained, no external deps).
    """
    now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
    overall_grade = _compute_overall_grade(scan_results)
    total_findings = sum(len(r.findings) for r in scan_results)
    total_files = len(scan_results)
    total_lines = sum(r.lines_scanned for r in scan_results)

    # Severity counts
    severity_counts: dict[str, int] = {s.value: 0 for s in Severity}
    for r in scan_results:
        for f in r.findings:
            severity_counts[f.severity.value] += 1

    critical_pct = (
        (severity_counts["CRITICAL"] / total_findings * 100) if total_findings > 0 else 0
    )

    # Build rule violation frequency
    rule_freq: dict[str, int] = {}
    for r in scan_results:
        for f in r.findings:
            rule_freq[f.rule_id] = rule_freq.get(f.rule_id, 0) + 1
    top_rules = sorted(rule_freq.items(), key=lambda x: x[1], reverse=True)[:10]

    # Build heatmap data: server x category
    categories = sorted({f.rule_id.rsplit("-", 1)[0] for r in scan_results for f in r.findings})
    if not categories:
        categories = ["N/A"]
    heatmap_data: list[dict[str, Any]] = []
    for r in scan_results:
        row: dict[str, int] = {cat: 0 for cat in categories}
        for f in r.findings:
            prefix = f.rule_id.rsplit("-", 1)[0]
            if prefix in row:
                row[prefix] += 1
        heatmap_data.append({"file": r.file, "categories": row, "grade": r.grade})

    # Build sections
    exec_summary = _build_executive_summary(
        overall_grade, total_files, total_findings, total_lines,
        severity_counts, critical_pct, now,
    )
    heatmap_section = _build_heatmap(heatmap_data, categories)
    server_details = _build_server_details(scan_results)
    trend_section = _build_trend_section(history) if history else ""
    recommendations = _build_recommendations(top_rules, scan_results, severity_counts)

    return _wrap_html(title, now, exec_summary + heatmap_section + server_details + trend_section + recommendations)


# ---------------------------------------------------------------------------
# HTML Section Builders
# ---------------------------------------------------------------------------

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

    severity_bars = ""
    for sev in ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO"]:
        count = severity_counts.get(sev, 0)
        pct = (count / total_findings * 100) if total_findings > 0 else 0
        sc = _severity_color(sev)
        severity_bars += f"""
        <div class="sev-row">
            <span class="sev-label">{sev}</span>
            <div class="sev-bar-bg">
                <div class="sev-bar" style="width:{pct:.0f}%;background:{sc}"></div>
            </div>
            <span class="sev-count">{count}</span>
        </div>"""

    return f"""
    <section class="section">
        <h2>Executive Summary</h2>
        <div class="cards">
            <div class="card">
                <div class="card-label">Fleet Grade</div>
                <div class="card-value" style="color:{gc};font-size:3rem">{html.escape(grade)}</div>
            </div>
            <div class="card">
                <div class="card-label">Servers 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">Critical %</div>
                <div class="card-value" style="color:#ef4444">{critical_pct:.1f}%</div>
            </div>
        </div>
        <div class="severity-breakdown">
            <h3>Severity Distribution</h3>
            {severity_bars}
        </div>
    </section>"""


def _build_heatmap(
    heatmap_data: list[dict[str, Any]],
    categories: list[str],
) -> str:
    """Build the risk heatmap section (server x category)."""
    header_cells = "".join(
        f'<th class="hm-th">{html.escape(c)}</th>' for c in categories
    )

    rows = ""
    for entry in heatmap_data:
        gc = _grade_color(entry["grade"])
        # Truncate filename for display
        fname = entry["file"]
        display = fname if len(fname) <= 40 else "..." + fname[-37:]
        cells = ""
        for cat in categories:
            count = entry["categories"].get(cat, 0)
            bg = _heatmap_color(count)
            cells += f'<td class="hm-td" style="background:{bg}">{count if count > 0 else ""}</td>'
        rows += f"""
        <tr>
            <td class="hm-file" title="{html.escape(fname)}">{html.escape(display)}</td>
            <td class="hm-grade" style="color:{gc}">{html.escape(entry["grade"])}</td>
            {cells}
        </tr>"""

    return f"""
    <section class="section">
        <h2>Risk Heatmap</h2>
        <div class="table-wrap">
        <table class="heatmap">
            <thead>
                <tr>
                    <th class="hm-th">Server</th>
                    <th class="hm-th">Grade</th>
                    {header_cells}
                </tr>
            </thead>
            <tbody>
                {rows}
            </tbody>
        </table>
        </div>
    </section>"""


def _build_server_details(scan_results: list[ScanResult]) -> str:
    """Build the per-server detail section."""
    server_cards = ""
    for r in scan_results:
        grade = r.grade
        gc = _grade_color(grade)
        fname = r.file
        display = fname if len(fname) <= 50 else "..." + fname[-47:]

        findings_html = ""
        if r.findings:
            for f in r.findings[:20]:  # Cap at 20 findings per server
                sc = _severity_color(f.severity.value)
                snippet = html.escape(f.code_snippet[:80]) if f.code_snippet else ""
                findings_html += f"""
                <div class="finding">
                    <span class="finding-sev" style="color:{sc}">{f.severity.value}</span>
                    <span class="finding-rule">{html.escape(f.rule_id)}</span>
                    <span class="finding-title">{html.escape(f.title)}</span>
                    <span class="finding-line">L{f.line}</span>
                    {f'<code class="finding-code">{snippet}</code>' if snippet else ''}
                </div>"""
            if len(r.findings) > 20:
                findings_html += f'<div class="finding-more">... and {len(r.findings) - 20} more findings</div>'
        else:
            findings_html = '<div class="finding-clean">No findings -- clean scan</div>'

        server_cards += f"""
        <div class="server-card">
            <div class="server-header">
                <span class="server-name" title="{html.escape(fname)}">{html.escape(display)}</span>
                <span class="server-grade" style="color:{gc}">{html.escape(grade)}</span>
                <span class="server-count">{len(r.findings)} findings | {r.lines_scanned} lines</span>
            </div>
            <div class="server-findings">
                {findings_html}
            </div>
        </div>"""

    return f"""
    <section class="section">
        <h2>Per-Server Detail</h2>
        {server_cards}
    </section>"""


def _build_trend_section(history: list[dict[str, Any]] | None) -> str:
    """Build the trend section from historical data."""
    if not history:
        return ""

    rows = ""
    for entry in history[-10:]:  # Last 10 entries
        summary = entry.get("scan", {}).get("summary", entry.get("summary", {}))
        timestamp = entry.get("timestamp", "")
        grade = summary.get("overall_grade", "?")
        findings = summary.get("total_findings", 0)
        files = summary.get("files_scanned", 0)
        gc = _grade_color(grade)
        rows += f"""
        <tr>
            <td>{html.escape(str(timestamp))}</td>
            <td style="color:{gc};font-weight:bold">{html.escape(grade)}</td>
            <td>{findings}</td>
            <td>{files}</td>
        </tr>"""

    return f"""
    <section class="section">
        <h2>Trend (Last {min(len(history), 10)} Scans)</h2>
        <table class="detail-table">
            <thead>
                <tr>
                    <th>Timestamp</th>
                    <th>Grade</th>
                    <th>Findings</th>
                    <th>Files</th>
                </tr>
            </thead>
            <tbody>
                {rows}
            </tbody>
        </table>
    </section>"""


def _build_recommendations(
    top_rules: list[tuple[str, int]],
    scan_results: list[ScanResult],
    severity_counts: dict[str, int],
) -> str:
    """Build top recommendations section."""
    recs: list[str] = []

    # 1. Most common violations
    if top_rules:
        recs.append("<h3>Most Common Rule Violations</h3>")
        recs.append('<table class="detail-table"><thead><tr><th>Rule</th><th>Count</th></tr></thead><tbody>')
        for rule_id, count in top_rules:
            recs.append(f"<tr><td>{html.escape(rule_id)}</td><td>{count}</td></tr>")
        recs.append("</tbody></table>")

    # 2. Priority actions
    recs.append("<h3>Priority Actions</h3><ol>")
    if severity_counts.get("CRITICAL", 0) > 0:
        recs.append(
            f'<li><strong style="color:#dc2626">Address {severity_counts["CRITICAL"]} '
            f"CRITICAL findings immediately.</strong> These represent exploitable vulnerabilities.</li>"
        )
    if severity_counts.get("HIGH", 0) > 0:
        recs.append(
            f'<li>Remediate {severity_counts["HIGH"]} HIGH severity findings within this sprint.</li>'
        )

    # 3. Worst servers
    worst = sorted(scan_results, key=lambda r: r.weighted_score, reverse=True)[:3]
    if worst and worst[0].weighted_score > 0:
        recs.append("<li>Focus remediation on highest-risk servers:")
        recs.append("<ul>")
        for r in worst:
            if r.weighted_score > 0:
                recs.append(
                    f"<li><code>{html.escape(r.file)}</code> "
                    f"(Grade {r.grade}, {len(r.findings)} findings)</li>"
                )
        recs.append("</ul></li>")

    if severity_counts.get("MEDIUM", 0) > 0:
        recs.append(
            f'<li>Plan remediation for {severity_counts["MEDIUM"]} MEDIUM findings in upcoming sprints.</li>'
        )

    recs.append("</ol>")

    return f"""
    <section class="section">
        <h2>Top Recommendations</h2>
        {''.join(recs)}
    </section>"""


# ---------------------------------------------------------------------------
# HTML Wrapper
# ---------------------------------------------------------------------------

def generate_fleet_report_from_dict(
    scan_results: dict[str, Any],
    output_path: str | None = None,
) -> str:
    """Generate fleet HTML report from a dict (FleetReport.to_dict() output).

    This is the dict-based entry point used by the CLI --fleet-report flag
    and by code that already has serialized scan results.

    Args:
        scan_results: Dictionary with keys: servers, total_servers,
                      total_findings, fleet_grade, severity_counts.
        output_path: Optional path to write HTML file.

    Returns:
        Complete HTML document string.
    """
    servers_data = scan_results.get("servers", [])
    mock_results: list[ScanResult] = []

    for srv in servers_data:
        sr_dict = srv.get("scan_result", {})
        findings_raw = []
        if isinstance(sr_dict, dict):
            findings_raw = sr_dict.get("findings", [])
        elif isinstance(sr_dict, ScanResult):
            mock_results.append(sr_dict)
            continue

        from .models import Finding
        findings: list[Finding] = []
        for fd in findings_raw:
            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", srv.get("file", "")),
                line=fd.get("line", 0),
                code_snippet=fd.get("code_snippet", ""),
                cwe=fd.get("cwe", ""),
            ))

        mock_results.append(ScanResult(
            file=srv.get("file", ""),
            findings=findings,
            lines_scanned=sr_dict.get("lines_scanned", 0)
            if isinstance(sr_dict, dict) else 0,
        ))

    report_html = generate_fleet_report(mock_results)

    if output_path is not None:
        from pathlib import Path as _Path
        out = _Path(output_path)
        out.parent.mkdir(parents=True, exist_ok=True)
        out.write_text(report_html, encoding="utf-8")

    return report_html


def _wrap_html(title: str, timestamp: str, body: str) -> str:
    """Wrap sections in a complete HTML document."""
    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)} -- mcpaudit</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: 24px; line-height: 1.6;
}}
h1 {{ font-size: 1.8rem; margin-bottom: 4px; }}
h2 {{ font-size: 1.3rem; margin-bottom: 16px; color: #f1f5f9; }}
h3 {{ font-size: 1.1rem; margin: 16px 0 8px; color: #cbd5e1; }}
.subtitle {{ color: #94a3b8; margin-bottom: 32px; font-size: 0.9rem; }}
.section {{
    background: #1e293b; border-radius: 12px; padding: 24px;
    margin-bottom: 24px;
}}
.cards {{
    display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
    gap: 16px; margin-bottom: 24px;
}}
.card {{
    background: #0f172a; border-radius: 8px; padding: 16px; text-align: center;
}}
.card-label {{
    font-size: 0.7rem; text-transform: uppercase; color: #94a3b8;
    letter-spacing: 0.05em;
}}
.card-value {{ font-size: 2rem; font-weight: 700; margin-top: 4px; }}
.severity-breakdown {{ max-width: 500px; }}
.sev-row {{ display: flex; align-items: center; margin: 6px 0; gap: 8px; }}
.sev-label {{ width: 70px; font-size: 0.75rem; font-weight: 600; text-transform: uppercase; }}
.sev-bar-bg {{
    flex: 1; height: 12px; background: #334155; border-radius: 6px; overflow: hidden;
}}
.sev-bar {{ height: 100%; border-radius: 6px; transition: width 0.3s; }}
.sev-count {{ width: 30px; text-align: right; font-size: 0.8rem; color: #94a3b8; }}
.table-wrap {{ overflow-x: auto; }}
.heatmap {{
    width: 100%; border-collapse: collapse; font-size: 0.8rem;
}}
.hm-th {{
    padding: 8px 10px; text-align: center; background: #334155;
    font-weight: 600; color: #94a3b8; text-transform: uppercase; font-size: 0.7rem;
}}
.hm-td {{
    padding: 8px 10px; text-align: center; border: 1px solid #1e293b;
    font-size: 0.8rem; font-weight: 600;
}}
.hm-file {{
    padding: 8px 10px; text-align: left; max-width: 250px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    border: 1px solid #1e293b;
}}
.hm-grade {{
    padding: 8px 10px; text-align: center; font-weight: 700;
    border: 1px solid #1e293b;
}}
.server-card {{
    background: #0f172a; border-radius: 8px; margin-bottom: 12px;
    overflow: hidden;
}}
.server-header {{
    display: flex; align-items: center; gap: 12px; padding: 12px 16px;
    background: #334155;
}}
.server-name {{ flex: 1; font-weight: 600; font-size: 0.9rem; }}
.server-grade {{ font-weight: 700; font-size: 1.2rem; }}
.server-count {{ color: #94a3b8; font-size: 0.8rem; }}
.server-findings {{ padding: 8px 16px; }}
.finding {{
    display: flex; align-items: center; gap: 8px; padding: 4px 0;
    font-size: 0.8rem; border-bottom: 1px solid #1e293b;
}}
.finding-sev {{ font-weight: 700; width: 70px; font-size: 0.7rem; }}
.finding-rule {{ color: #94a3b8; width: 90px; font-family: monospace; }}
.finding-title {{ flex: 1; }}
.finding-line {{ color: #64748b; font-family: monospace; font-size: 0.75rem; }}
.finding-code {{
    display: block; font-size: 0.7rem; color: #94a3b8;
    background: #0f172a; padding: 2px 6px; border-radius: 4px;
    max-width: 300px; overflow: hidden; text-overflow: ellipsis;
}}
.finding-clean {{ color: #22c55e; padding: 8px 0; font-size: 0.85rem; }}
.finding-more {{ color: #64748b; padding: 4px 0; font-size: 0.8rem; font-style: italic; }}
.detail-table {{
    width: 100%; border-collapse: collapse; background: #0f172a;
    border-radius: 8px; overflow: hidden; margin: 8px 0;
}}
.detail-table th {{
    text-align: left; padding: 10px 14px; background: #334155;
    font-size: 0.75rem; text-transform: uppercase; color: #94a3b8;
}}
.detail-table td {{
    padding: 8px 14px; border-bottom: 1px solid #1e293b; font-size: 0.85rem;
}}
.detail-table tr:hover {{ background: #1e3a5f; }}
ol {{ padding-left: 20px; }}
ol li {{ margin: 8px 0; font-size: 0.9rem; }}
ul {{ padding-left: 20px; margin: 4px 0; }}
code {{ font-family: 'Fira Code', monospace; font-size: 0.85em; }}
.footer {{
    text-align: center; color: #475569; margin-top: 32px;
    font-size: 0.75rem; padding: 16px;
}}
</style>
</head>
<body>
<h1>{html.escape(title)}</h1>
<p class="subtitle">Generated by mcpaudit v1.1.0 | {html.escape(timestamp)}</p>

{body}

<div class="footer">
    Generated by mcpaudit v1.1.0 | Carlos Miret | {html.escape(timestamp)}
</div>
</body>
</html>"""
