"""Renderiza un grafo sigma.js como HTML standalone con dark theme y layout ForceAtlas2."""

import json
import os


_HTML_TEMPLATE = """\
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>{title}</title>
    <script src="https://cdn.jsdelivr.net/npm/graphology@0.25.4/dist/graphology.umd.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/graphology-library@0.8.0/dist/graphology-library.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/sigma@2.4.0/build/sigma.min.js"></script>
    <style>
        * {{ box-sizing: border-box; margin: 0; padding: 0; }}
        body {{ background: #1a1a2e; color: #eee; font-family: 'Segoe UI', system-ui, sans-serif; overflow: hidden; }}
        #container {{ width: 100vw; height: 100vh; }}
        #panel {{
            position: absolute; top: 12px; right: 12px;
            background: rgba(10, 10, 30, 0.88);
            border: 1px solid rgba(255,255,255,0.12);
            padding: 16px; border-radius: 10px;
            z-index: 10; min-width: 200px; max-width: 260px;
            backdrop-filter: blur(6px);
        }}
        #panel h3 {{ font-size: 14px; font-weight: 600; margin-bottom: 12px; color: #a0c4ff; letter-spacing: 0.5px; }}
        #stats {{ font-size: 11px; color: #888; margin-bottom: 12px; }}
        #filters {{ display: flex; flex-direction: column; gap: 6px; }}
        .filter-item {{ display: flex; align-items: center; gap: 8px; font-size: 12px; cursor: pointer; }}
        .filter-item input {{ cursor: pointer; accent-color: #a0c4ff; }}
        .color-dot {{ width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }}
        #tooltip {{
            position: absolute; display: none;
            background: rgba(5, 5, 20, 0.95);
            border: 1px solid rgba(255,255,255,0.15);
            padding: 10px 14px; border-radius: 8px;
            pointer-events: none; z-index: 20;
            max-width: 300px; font-size: 12px; line-height: 1.6;
        }}
        #tooltip .tt-title {{ font-weight: 600; color: #a0c4ff; margin-bottom: 6px; font-size: 13px; }}
        #tooltip .tt-row {{ display: flex; gap: 6px; }}
        #tooltip .tt-key {{ color: #888; min-width: 80px; }}
        #tooltip .tt-val {{ color: #eee; word-break: break-all; }}
    </style>
</head>
<body>
    <div id="container"></div>
    <div id="panel">
        <h3>{title}</h3>
        <div id="stats"></div>
        <div id="filters"></div>
    </div>
    <div id="tooltip"></div>

    <script>
    (function () {{
        const graphData = {json_data};

        // ── Build graphology graph ──────────────────────────────────────────────
        const Graph = graphology.Graph || graphology;
        const g = new Graph({{ multi: true, type: 'directed' }});

        // Assign random initial positions
        graphData.nodes.forEach(function (n) {{
            g.addNode(n.key, Object.assign({{
                x: (Math.random() - 0.5) * 10,
                y: (Math.random() - 0.5) * 10,
            }}, n.attributes));
        }});

        graphData.edges.forEach(function (e) {{
            try {{
                g.addEdgeWithKey(e.key, e.source, e.target, e.attributes || {{}});
            }} catch (err) {{
                // skip duplicate edge keys gracefully
            }}
        }});

        // ── ForceAtlas2 layout (synchronous, 500 iterations) ───────────────────
        const FA2 = graphologyLibrary.layoutForceAtlas2;
        FA2.assign(g, {{
            iterations: 500,
            settings: {{
                gravity: 1,
                scalingRatio: 2,
                slowDown: 5,
                barnesHutOptimize: g.order > 300,
            }},
        }});

        // ── Sigma renderer ──────────────────────────────────────────────────────
        const renderer = new Sigma(g, document.getElementById('container'), {{
            renderEdgeLabels: false,
            defaultEdgeColor: '#444',
            defaultNodeColor: '#95a5a6',
            labelColor: {{ color: '#ccc' }},
            labelSize: 11,
            edgeReducer: function (edge, data) {{
                return Object.assign({{}}, data, {{ size: Math.max(1, (data.weight || 1) * 0.8) }});
            }},
        }});

        // ── Stats panel ─────────────────────────────────────────────────────────
        document.getElementById('stats').textContent =
            graphData.nodes.length + ' nodes · ' + graphData.edges.length + ' edges';

        // ── Filter panel by node type ───────────────────────────────────────────
        const typeColors = {{}};
        graphData.nodes.forEach(function (n) {{
            const t = n.attributes.entity_type || 'unknown';
            typeColors[t] = n.attributes.color || '#95a5a6';
        }});

        const hiddenTypes = new Set();
        const filtersDiv = document.getElementById('filters');

        Object.keys(typeColors).sort().forEach(function (type) {{
            const color = typeColors[type];
            const label = document.createElement('label');
            label.className = 'filter-item';

            const cb = document.createElement('input');
            cb.type = 'checkbox';
            cb.checked = true;
            cb.addEventListener('change', function () {{
                if (cb.checked) hiddenTypes.delete(type);
                else hiddenTypes.add(type);
                renderer.refresh();
            }});

            const dot = document.createElement('span');
            dot.className = 'color-dot';
            dot.style.background = color;

            label.appendChild(cb);
            label.appendChild(dot);
            label.appendChild(document.createTextNode(type));
            filtersDiv.appendChild(label);
        }});

        // Node reducer applies type filter
        renderer.setSetting('nodeReducer', function (node, data) {{
            if (hiddenTypes.has(data.entity_type)) return Object.assign({{}}, data, {{ hidden: true }});
            return data;
        }});

        // ── Tooltip on hover ────────────────────────────────────────────────────
        const tooltip = document.getElementById('tooltip');

        renderer.on('enterNode', function (ref) {{
            const nodeAttrs = g.getNodeAttributes(ref.node);
            const reserved = new Set(['x', 'y', 'size', 'color', 'label', 'type', 'hidden']);

            let html = '<div class="tt-title">' + escHtml(nodeAttrs.label || ref.node) + '</div>';
            html += '<div class="tt-row"><span class="tt-key">type</span><span class="tt-val">' + escHtml(nodeAttrs.entity_type || '') + '</span></div>';
            html += '<div class="tt-row"><span class="tt-key">status</span><span class="tt-val">' + escHtml(nodeAttrs.status || '') + '</span></div>';
            html += '<div class="tt-row"><span class="tt-key">domain</span><span class="tt-val">' + escHtml(nodeAttrs.domain || '') + '</span></div>';

            Object.keys(nodeAttrs).sort().forEach(function (k) {{
                if (!reserved.has(k) && !['status', 'domain', 'type', 'label'].includes(k)) {{
                    html += '<div class="tt-row"><span class="tt-key">' + escHtml(k) + '</span><span class="tt-val">' + escHtml(String(nodeAttrs[k])) + '</span></div>';
                }}
            }});

            tooltip.innerHTML = html;
            tooltip.style.display = 'block';
        }});

        renderer.on('leaveNode', function () {{
            tooltip.style.display = 'none';
        }});

        document.getElementById('container').addEventListener('mousemove', function (e) {{
            tooltip.style.left = (e.clientX + 16) + 'px';
            tooltip.style.top = (e.clientY + 16) + 'px';
        }});

        function escHtml(str) {{
            return String(str)
                .replace(/&/g, '&amp;')
                .replace(/</g, '&lt;')
                .replace(/>/g, '&gt;')
                .replace(/"/g, '&quot;');
        }}
    }})();
    </script>
</body>
</html>
"""


def render_sigma_html(
    graph_data: dict,
    output_path: str,
    title: str = "OSINT Graph",
) -> str:
    """Genera un HTML standalone con sigma.js que visualiza el grafo OSINT.

    Recibe el dict producido por ops_to_sigma_json, embebe los datos como JSON
    en el HTML, aplica ForceAtlas2 (500 iteraciones sincrono) y renderiza con
    sigma.js v2.4. Incluye dark theme, panel de filtros por tipo de nodo y
    tooltip con metadata al hacer hover.

    Args:
        graph_data: Dict con claves 'nodes' y 'edges' en formato graphology/sigma.
        output_path: Ruta del archivo HTML a escribir.
        title: Titulo del grafo mostrado en el panel y la pestana.

    Returns:
        Ruta absoluta del archivo HTML escrito.

    Raises:
        Exception: Si no se puede escribir el archivo en output_path.
    """
    json_data = json.dumps(graph_data, ensure_ascii=False)

    html = _HTML_TEMPLATE.format(
        title=title,
        json_data=json_data,
    )

    abs_path = os.path.abspath(output_path)
    os.makedirs(os.path.dirname(abs_path) or ".", exist_ok=True)

    try:
        with open(abs_path, "w", encoding="utf-8") as f:
            f.write(html)
    except OSError as exc:
        raise Exception(f"render_sigma_html: no se pudo escribir '{abs_path}': {exc}") from exc

    return abs_path
