import json
from datetime import datetime, timezone

class ReportGenerator:

    def build(self, timing_results: list, static_findings: list, target: str) -> dict:
        findings = []

        for result in timing_results:
            if result.leaking:
                findings.append({
                    "id": f"PQC-TIMING-{result.operation.upper().replace(' ', '_')}",
                    "type": "timing_side_channel",
                    "severity": result.severity,
                    "operation": result.operation,
                    "details": {
                        "t_statistic": result.t_statistic,
                        "threshold": 4.5,
                        "fixed_mean_ns": result.fixed_mean_ns,
                        "random_mean_ns": result.random_mean_ns,
                        "difference_ns": result.difference_ns,
                        "traces": result.num_traces,
                    },
                    "description": (
                        f"Timing leakage in {result.operation}: "
                        f"t={result.t_statistic} exceeds threshold +/-4.5. "
                        f"Fixed vs random input timing differs by {result.difference_ns:.0f}ns."
                    ),
                    "cwe": "CWE-385",
                    "remediation": (
                        "Use OQS_KEM_encaps_derand() with a pre-seeded DRBG instead of OQS_KEM_encaps(). "
                        "Root cause: the OS RNG call (OQS_randombytes()) inside the default encaps API has variable timing. "
                        "The cryptographic core is clean -- timing signal is RNG-induced, not algorithmic."
                        if "encaps" in result.operation.lower() or "decaps" in result.operation.lower()
                        else "Replace secret-dependent branches with constant-time conditional moves. "
                             "Audit all division and table-lookup operations."
                    )
                })

        for f in static_findings:
            findings.append({
                "id": f"PQC-STATIC-{f.issue.upper()}",
                "type": "static_analysis",
                "severity": f.severity,
                "file": f.file,
                "line": f.line,
                "code": f.code,
                "description": f.message,
                "cwe": "CWE-385",
                "remediation": f.message
            })

        summary = {
            "total":    len(findings),
            "critical": sum(1 for f in findings if f["severity"] == "CRITICAL"),
            "high":     sum(1 for f in findings if f["severity"] == "HIGH"),
            "medium":   sum(1 for f in findings if f["severity"] == "MEDIUM"),
            "passed":   len(findings) == 0
        }

        return {
            "scanner":   "pqc-scanner v0.1",
            "timestamp": datetime.now(timezone.utc).isoformat(),
            "target":    target,
            "summary":   summary,
            "findings":  findings
        }

    def to_json(self, report: dict) -> str:
        return json.dumps(report, indent=2)

    def build_with_traces(self, timing_results: list, static_findings: list,
                          target: str, raw_traces: dict) -> dict:
        """Extended build that also stores raw traces for histogram rendering."""
        report = self.build(timing_results, static_findings, target)
        report["_raw_traces"] = raw_traces
        return report

    def to_html(self, report: dict) -> str:
        s = report["summary"]
        raw_traces = report.get("_raw_traces", {})

        badge_colors = {
            "CRITICAL": "#ff4444",
            "HIGH":     "#ff8800",
            "MEDIUM":   "#ffcc00",
            "PASS":     "#44bb44"
        }

        # Build findings HTML
        findings_html = ""
        for f in report["findings"]:
            color = badge_colors.get(f["severity"], "#aaa")
            detail_rows = ""
            if "details" in f:
                for k, v in f["details"].items():
                    detail_rows += f"<tr><td>{k}</td><td>{v}</td></tr>"

            findings_html += f"""
            <div class="finding">
              <div class="finding-header">
                <span class="badge" style="background:{color}">{f['severity']}</span>
                <strong>{f['id']}</strong>
                <span class="ftype">{f['type']}</span>
              </div>
              <p>{f['description']}</p>
              {'<table class="detail-table">' + detail_rows + '</table>' if detail_rows else ''}
              <div class="remediation">Fix: {f['remediation']}</div>
            </div>"""

        # Build histogram charts JS
        charts_js = ""
        chart_canvases = ""
        if raw_traces:
            chart_canvases = "<h2>Timing Distributions</h2><p class='chart-subtitle'>Each histogram shows fixed input (blue) vs random input (red) timing. Overlap = constant-time. Separation = LEAK.</p><div class='charts-grid'>"
            for op_name, traces in raw_traces.items():
                fixed = traces.get("fixed", [])
                random = traces.get("random", [])
                if not fixed or not random:
                    continue

                canvas_id = f"chart_{op_name.replace(' ', '_').lower()}"
                verdict = traces.get("verdict", "PASS")
                verdict_color = "#ff4444" if "LEAK" in verdict else "#3fb950"

                chart_canvases += f"""
                <div class="chart-card">
                  <div class="chart-title">
                    {op_name}
                    <span class="chart-verdict" style="color:{verdict_color}">{verdict}</span>
                  </div>
                  <canvas id="{canvas_id}" height="200"></canvas>
                </div>"""

                # Compute histogram bins in Python, pass data to JS
                fixed_js   = json.dumps(fixed[:500])   # sample 500 for performance
                random_js  = json.dumps(random[:500])

                charts_js += f"""
                (function() {{
                    var fixed  = {fixed_js};
                    var random = {random_js};

                    function makeBins(data, numBins) {{
                        var min = Math.min(...data);
                        var max = Math.max(...data);
                        var binSize = (max - min) / numBins;
                        var bins = Array(numBins).fill(0);
                        var labels = [];
                        for (var i = 0; i < numBins; i++) {{
                            labels.push(((min + i * binSize) / 1e6).toFixed(2) + 'ms');
                        }}
                        data.forEach(function(v) {{
                            var idx = Math.min(Math.floor((v - min) / binSize), numBins - 1);
                            bins[idx]++;
                        }});
                        return {{ bins: bins, labels: labels }};
                    }}

                    var numBins = 40;
                    var allData = fixed.concat(random);
                    var globalMin = Math.min(...allData);
                    var globalMax = Math.max(...allData);
                    var binSize   = (globalMax - globalMin) / numBins;

                    function makeBinsGlobal(data) {{
                        var bins = Array(numBins).fill(0);
                        data.forEach(function(v) {{
                            var idx = Math.min(Math.floor((v - globalMin) / binSize), numBins - 1);
                            bins[idx]++;
                        }});
                        return bins;
                    }}

                    var labels = [];
                    for (var i = 0; i < numBins; i++) {{
                        labels.push(((globalMin + i * binSize) / 1e6).toFixed(2));
                    }}

                    var ctx = document.getElementById('{canvas_id}').getContext('2d');
                    new Chart(ctx, {{
                        type: 'bar',
                        data: {{
                            labels: labels,
                            datasets: [
                                {{
                                    label: 'Fixed input',
                                    data: makeBinsGlobal(fixed),
                                    backgroundColor: 'rgba(88, 166, 255, 0.6)',
                                    borderColor: 'rgba(88, 166, 255, 1)',
                                    borderWidth: 1
                                }},
                                {{
                                    label: 'Random input',
                                    data: makeBinsGlobal(random),
                                    backgroundColor: 'rgba(255, 68, 68, 0.5)',
                                    borderColor: 'rgba(255, 68, 68, 1)',
                                    borderWidth: 1
                                }}
                            ]
                        }},
                        options: {{
                            responsive: true,
                            animation: {{ duration: 600 }},
                            plugins: {{
                                legend: {{ labels: {{ color: '#c9d1d9', font: {{ size: 11 }} }} }},
                                tooltip: {{
                                    callbacks: {{
                                        title: function(items) {{ return items[0].label + 'ms'; }},
                                        label: function(item) {{ return item.dataset.label + ': ' + item.raw + ' traces'; }}
                                    }}
                                }}
                            }},
                            scales: {{
                                x: {{
                                    ticks: {{ color: '#8b949e', maxRotation: 45, font: {{ size: 9 }} }},
                                    grid: {{ color: '#21262d' }},
                                    title: {{ display: true, text: 'Time (ms)', color: '#8b949e' }}
                                }},
                                y: {{
                                    ticks: {{ color: '#8b949e' }},
                                    grid: {{ color: '#21262d' }},
                                    title: {{ display: true, text: 'Count', color: '#8b949e' }}
                                }}
                            }}
                        }}
                    }});
                }})();"""

            chart_canvases += "</div>"

        passed_html = '<div class="pass-box">No side-channel issues detected.</div>'

        return f"""<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>PQC Scanner Report</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.min.js"></script>
  <style>
    * {{ box-sizing: border-box; margin: 0; padding: 0; }}
    body {{ font-family: 'Segoe UI', monospace; background: #0d1117; color: #c9d1d9; padding: 2rem; }}
    h1 {{ color: #58a6ff; font-size: 1.6rem; margin-bottom: 0.25rem; }}
    .subtitle {{ color: #8b949e; margin-bottom: 2rem; font-size: 0.9rem; }}
    .summary {{ display: flex; gap: 1rem; margin-bottom: 2rem; flex-wrap: wrap; }}
    .stat {{ background: #161b22; border-radius: 8px; padding: 1rem 1.5rem; text-align: center; min-width: 100px; }}
    .stat .num {{ font-size: 2rem; font-weight: bold; }}
    .stat .label {{ font-size: 0.75rem; color: #8b949e; text-transform: uppercase; }}
    .finding {{ background: #161b22; border-left: 4px solid #58a6ff; border-radius: 6px;
                padding: 1rem 1.25rem; margin-bottom: 1rem; }}
    .finding-header {{ display: flex; align-items: center; gap: 0.75rem; margin-bottom: 0.5rem; }}
    .badge {{ padding: 2px 10px; border-radius: 12px; color: #000; font-weight: bold; font-size: 0.75rem; }}
    .ftype {{ color: #8b949e; font-size: 0.8rem; }}
    .detail-table {{ width: 100%; margin: 0.75rem 0; border-collapse: collapse; font-size: 0.85rem; }}
    .detail-table td {{ padding: 4px 8px; border-bottom: 1px solid #21262d; }}
    .detail-table td:first-child {{ color: #8b949e; width: 180px; }}
    .remediation {{ background: #0d2137; border-radius: 4px; padding: 0.5rem 0.75rem;
                    font-size: 0.85rem; color: #7ee787; margin-top: 0.5rem; }}
    .pass-box {{ background: #0f2d1a; border: 1px solid #238636; border-radius: 8px;
                 padding: 1.5rem; text-align: center; color: #3fb950; font-size: 1.1rem; }}
    h2 {{ color: #58a6ff; margin: 1.5rem 0 0.5rem; font-size: 1.1rem; }}
    .chart-subtitle {{ color: #8b949e; font-size: 0.82rem; margin-bottom: 1rem; }}
    .charts-grid {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(420px, 1fr)); gap: 1.5rem; margin-bottom: 2rem; }}
    .chart-card {{ background: #161b22; border-radius: 8px; padding: 1.25rem; }}
    .chart-title {{ font-size: 0.95rem; font-weight: bold; color: #c9d1d9; margin-bottom: 0.75rem;
                    display: flex; justify-content: space-between; align-items: center; }}
    .chart-verdict {{ font-size: 0.78rem; font-weight: bold; }}
    .divider {{ border: none; border-top: 1px solid #21262d; margin: 1.5rem 0; }}
  </style>
</head>
<body>
  <h1>PQC Side-Channel Scanner</h1>
  <div class="subtitle">Target: {report['target']} | Scanned: {report['timestamp']} | pqc-scanner v0.1</div>

  <div class="summary">
    <div class="stat"><div class="num" style="color:#ff4444">{s['critical']}</div><div class="label">Critical</div></div>
    <div class="stat"><div class="num" style="color:#ff8800">{s['high']}</div><div class="label">High</div></div>
    <div class="stat"><div class="num" style="color:#ffcc00">{s['medium']}</div><div class="label">Medium</div></div>
    <div class="stat"><div class="num">{s['total']}</div><div class="label">Total Findings</div></div>
  </div>

  {chart_canvases}

  <hr class="divider">
  <h2>Findings</h2>
  {passed_html if s['passed'] else findings_html}

  <script>
    {charts_js}
  </script>
</body>
</html>"""