"""
Scan export utilities for generating HTML, CSV, and JSON exports
from stored scan result dicts.
"""

import csv
import io
import json
from datetime import datetime
from typing import Any, Dict


def export_scan_json(scan_data: Dict[str, Any]) -> str:
    """Export scan data as formatted JSON string."""
    return json.dumps(scan_data, indent=2, default=str)


def export_scan_csv(scan_data: Dict[str, Any], scan_type: str) -> str:
    """Export scan data as CSV string.

    Flattens key fields into rows depending on scan type.
    """
    output = io.StringIO()
    writer = csv.writer(output)

    inner = scan_data.get("data", scan_data)

    if scan_type in ("domain", "quantum"):
        # Certificate / quantum scan results
        results = []
        if isinstance(inner, dict):
            results = inner.get("data", {}).get("results", [])
            if not results and inner.get("results"):
                results = inner["results"]
            # Quantum scan: single result with certificate_analysis nested
            if not results and inner.get("certificate_analysis"):
                results = [inner["certificate_analysis"]]
            # Single result directly in inner
            elif not results and inner.get("domain"):
                results = [inner]

        writer.writerow([
            "Domain", "Port", "Algorithm", "Key Size", "Quantum Vulnerability",
            "Risk Score", "Security Grade", "Expiry", "Migration Priority",
        ])
        for r in results:
            writer.writerow([
                r.get("domain", ""),
                r.get("port", ""),
                r.get("public_key_algorithm", ""),
                r.get("key_size", ""),
                r.get("quantum_vulnerability", ""),
                r.get("quantum_risk_score", ""),
                r.get("security_grade", ""),
                r.get("not_after", ""),
                r.get("migration_priority", ""),
            ])

    elif scan_type == "port":
        open_ports = []
        if isinstance(inner, dict):
            raw = inner.get("data", {})
            if isinstance(raw, dict):
                open_ports = raw.get("rawResults", {}).get("open_ports", [])
                if not open_ports:
                    open_ports = raw.get("openPorts", [])

        writer.writerow(["Port", "Service", "SSL", "Response Time (s)", "Banner"])
        for p in open_ports:
            writer.writerow([
                p.get("port", ""),
                p.get("service", ""),
                p.get("ssl_supported", ""),
                f"{p.get('response_time', 0):.3f}",
                p.get("banner", ""),
            ])

    elif scan_type == "binary":
        writer.writerow(["Field", "Value"])
        if isinstance(inner, dict):
            data = inner.get("data", inner)
            for key, value in data.items():
                if isinstance(value, (str, int, float, bool)):
                    writer.writerow([key, value])

    else:
        # Generic fallback
        writer.writerow(["Field", "Value"])
        if isinstance(inner, dict):
            for key, value in inner.items():
                if isinstance(value, (str, int, float, bool)):
                    writer.writerow([key, value])

    return output.getvalue()


def export_scan_html(scan_data: Dict[str, Any], scan_type: str, target: str) -> str:
    """Export scan data as a standalone HTML report."""
    inner = scan_data.get("data", scan_data)
    timestamp = scan_data.get("timestamp", datetime.utcnow().isoformat())

    rows_html = ""

    if scan_type in ("domain", "quantum"):
        results = []
        if isinstance(inner, dict):
            results = inner.get("data", {}).get("results", [])
            if not results and inner.get("results"):
                results = inner["results"]
            # Quantum scan: certificate_analysis is nested under inner
            if not results and inner.get("certificate_analysis"):
                results = [inner["certificate_analysis"]]
            elif not results and inner.get("domain"):
                results = [inner]

        table_header = """
        <tr>
            <th>Domain</th><th>Algorithm</th><th>Key Size</th>
            <th>Quantum Vulnerability</th><th>Risk Score</th>
            <th>Security Grade</th><th>Expiry</th><th>Migration Priority</th>
        </tr>"""
        for r in results:
            vuln = r.get("quantum_vulnerability", "UNKNOWN")
            css_class = _risk_css_class(vuln)
            rows_html += f"""
            <tr>
                <td>{_esc(r.get('domain', ''))}</td>
                <td>{_esc(r.get('public_key_algorithm', ''))}</td>
                <td>{r.get('key_size', '')}</td>
                <td class="{css_class}">{_esc(vuln)}</td>
                <td>{r.get('quantum_risk_score', '')}</td>
                <td>{_esc(r.get('security_grade', ''))}</td>
                <td>{_esc(str(r.get('not_after', '')))}</td>
                <td>{_esc(r.get('migration_priority', ''))}</td>
            </tr>"""

    elif scan_type == "port":
        open_ports = []
        if isinstance(inner, dict):
            raw = inner.get("data", {})
            if isinstance(raw, dict):
                open_ports = raw.get("rawResults", {}).get("open_ports", [])
                if not open_ports:
                    open_ports = raw.get("openPorts", [])

        table_header = """
        <tr>
            <th>Port</th><th>Service</th><th>SSL</th>
            <th>Response Time</th><th>Banner</th>
        </tr>"""
        for p in open_ports:
            rows_html += f"""
            <tr>
                <td>{p.get('port', '')}</td>
                <td>{_esc(p.get('service', ''))}</td>
                <td>{'Yes' if p.get('ssl_supported') else 'No'}</td>
                <td>{p.get('response_time', 0):.3f}s</td>
                <td>{_esc(p.get('banner', '') or '')}</td>
            </tr>"""

    else:
        # Binary / generic
        table_header = "<tr><th>Field</th><th>Value</th></tr>"
        data = inner.get("data", inner) if isinstance(inner, dict) else {}
        if isinstance(data, dict):
            for key, value in data.items():
                if isinstance(value, (str, int, float, bool)):
                    rows_html += f"<tr><td>{_esc(key)}</td><td>{_esc(str(value))}</td></tr>"

    scan_label = {
        "domain": "Certificate Scan",
        "quantum": "Quantum Security Analysis",
        "port": "Port Scan",
        "binary": "Binary Analysis",
    }.get(scan_type, scan_type.title())

    return f"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cerebion Rivet - {_esc(scan_label)} Report</title>
<style>
    body {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 0; padding: 20px; background: #f5f5f5; color: #333; }}
    .header {{ background: #1a1a2e; color: #fff; padding: 24px; border-radius: 8px; margin-bottom: 24px; }}
    .header h1 {{ margin: 0 0 8px; font-size: 24px; }}
    .header p {{ margin: 4px 0; opacity: 0.85; font-size: 14px; }}
    table {{ width: 100%; border-collapse: collapse; background: #fff; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }}
    th {{ background: #2d3748; color: #fff; padding: 12px 16px; text-align: left; font-size: 13px; text-transform: uppercase; letter-spacing: 0.5px; }}
    td {{ padding: 10px 16px; border-bottom: 1px solid #e2e8f0; font-size: 14px; }}
    tr:last-child td {{ border-bottom: none; }}
    .risk-critical {{ color: #e53e3e; font-weight: 700; }}
    .risk-high {{ color: #dd6b20; font-weight: 700; }}
    .risk-medium {{ color: #d69e2e; font-weight: 600; }}
    .risk-low {{ color: #38a169; }}
    .footer {{ margin-top: 24px; text-align: center; font-size: 12px; color: #718096; }}
</style>
</head>
<body>
<div class="header">
    <h1>Cerebion Rivet &mdash; {_esc(scan_label)} Report</h1>
    <p>Target: {_esc(target)}</p>
    <p>Generated: {_esc(str(timestamp))}</p>
</div>
<table>
{table_header}
{rows_html}
</table>
<div class="footer">Generated by Cerebion Rivet &bull; Post-Quantum Security Platform</div>
</body>
</html>"""


def _add_cert_sarif_result(results: list, rules: Dict[str, Any], cert: Dict[str, Any], location: str) -> None:
    """Append a SARIF result for one certificate_analysis-shaped dict.

    Shared by certificate (domain/quantum) scans, which have a single
    certificate, and network (port) scans, which have one such dict per
    scanned TLS service.
    """
    rule_id = "quantum/weak-certificate-algorithm"
    vuln = cert.get("quantum_vulnerability", "UNKNOWN")
    algo = cert.get("public_key_algorithm", "")
    message = f"{algo} certificate is vulnerable to quantum attack ({vuln})"
    sarif_level = "error" if vuln in ("CRITICAL", "IMMINENT") else "warning"
    if rule_id not in rules:
        rules[rule_id] = {"id": rule_id, "name": "WeakCertificateAlgorithm", "shortDescription": {"text": "Certificate uses quantum-vulnerable algorithm"}}
    results.append({
        "ruleId": rule_id,
        "level": sarif_level,
        "message": {"text": message},
        "locations": [{"physicalLocation": {"artifactLocation": {"uri": location}}}],
    })


def export_scan_sarif(scan_data: Dict[str, Any], scan_type: str, target: str) -> str:
    """Export scan findings as SARIF 2.1.0 for GitHub/Azure DevOps integration."""
    results = []
    rules = {}

    inner = scan_data.get("data", scan_data)

    if scan_type == "code_analysis":
        findings = scan_data.get("enriched_findings", [])
        for f in findings:
            rule_id = f.get("check_id", "unknown")
            message = (f.get("extra", {}).get("message") or f.get("message", ""))
            severity = (f.get("extra", {}).get("severity") or f.get("severity", "WARNING")).upper()
            file_path = f.get("path", target)
            start_line = f.get("start", {}).get("line") or f.get("start_line", 1)

            sarif_level = "error" if severity in ("ERROR", "HIGH", "CRITICAL") else "warning" if severity in ("WARNING", "MEDIUM") else "note"

            if rule_id not in rules:
                rules[rule_id] = {"id": rule_id, "name": rule_id.split(".")[-1], "shortDescription": {"text": message[:100]}}

            results.append({
                "ruleId": rule_id,
                "level": sarif_level,
                "message": {"text": message},
                "locations": [{"physicalLocation": {"artifactLocation": {"uri": file_path}, "region": {"startLine": start_line}}}],
            })

    elif scan_type in ("domain", "quantum"):
        cert_results = []
        if isinstance(inner, dict):
            cert_results = inner.get("data", {}).get("results", []) or inner.get("results", [])
            # Quantum scan: certificate_analysis is nested under inner
            if not cert_results and inner.get("certificate_analysis"):
                cert_results = [inner["certificate_analysis"]]
            elif not cert_results and inner.get("domain"):
                cert_results = [inner]
        for r in cert_results:
            _add_cert_sarif_result(results, rules, r, target)

    elif scan_type == "port":
        # Network scans store one certificate_analysis-shaped dict per scanned
        # TLS service, keyed by port, under data.data.certificates.
        net_data = inner.get("data", {}) if isinstance(inner, dict) else {}
        certs = net_data.get("certificates", {}) if isinstance(net_data, dict) else {}
        cert_list = list(certs.values()) if isinstance(certs, dict) else (certs if isinstance(certs, list) else [])
        for r in cert_list:
            if isinstance(r, dict):
                _add_cert_sarif_result(results, rules, r, r.get("domain", target))

    elif scan_type == "binary":
        data = inner.get("data", inner) if isinstance(inner, dict) else {}
        yara = data.get("yara_analysis", {}) if isinstance(data, dict) else {}
        for match in (yara.get("match_details", []) if isinstance(yara, dict) else []):
            rule_name = match.get("rule", "unknown")
            rule_id = f"quantum/binary/{rule_name}"
            message = match.get("meta", {}).get("description") or f"Quantum-vulnerable pattern: {rule_name}"
            if rule_id not in rules:
                rules[rule_id] = {"id": rule_id, "name": rule_name or "BinaryQuantumVulnerability", "shortDescription": {"text": message[:100]}}
            results.append({
                "ruleId": rule_id,
                "level": "warning",
                "message": {"text": message},
                "locations": [{"physicalLocation": {"artifactLocation": {"uri": target}}}],
            })

    sarif = {
        "version": "2.1.0",
        "$schema": "https://json.schemastore.org/sarif-2.1.0.json",
        "runs": [{
            "tool": {
                "driver": {
                    "name": "Cerebion Rivet",
                    "informationUri": "https://cerebion.com",
                    "rules": list(rules.values()),
                }
            },
            "results": results,
        }],
    }
    return json.dumps(sarif, indent=2)


def _esc(text: str) -> str:
    """Escape HTML special characters."""
    return (
        text.replace("&", "&amp;")
        .replace("<", "&lt;")
        .replace(">", "&gt;")
        .replace('"', "&quot;")
    )


def _risk_css_class(level: str) -> str:
    """Map risk/vulnerability level to CSS class."""
    level = (level or "").upper()
    if level in ("CRITICAL", "IMMINENT"):
        return "risk-critical"
    if level == "HIGH":
        return "risk-high"
    if level == "MEDIUM":
        return "risk-medium"
    return "risk-low"
