{% extends "base.html" %}
{% block title %}JA4 SOC — Cluster #{{ cid }}{% endblock %}
{% block page_title %}
    Cluster #{{ cid }}
    <span class="relative inline-block ml-1"><button onclick="docToggle(this)" class="doc-btn" aria-label="Aide">ⓘ</button><div class="doc-panel">
        <h4>Investigation Cluster</h4>
        <p>Analyse complète d'un cluster détecté par HDBSCAN. Un cluster regroupe des IPs aux comportements similaires (features ML proches), indiquant une campagne coordonnée.</p>
        <p><strong>Workflow :</strong> Vérifiez la convergence JA4/ASN → analysez les cibles → classifiez le cluster entier.</p>
        <p class="doc-source">Sources : ml_detected_anomalies WHERE campaign_id=…</p>
    </div></span>
{% endblock %}
{% block header_actions %}
    <div class="flex gap-2">
        <a href="/campaigns" class="px-3 py-1 bg-gray-800 text-gray-300 rounded text-xs hover:text-white">← Tous les clusters</a>
        <select id="cls-select" class="px-2 py-1 bg-gray-900 border border-gray-800 rounded text-xs text-gray-300">
            <option value="bot">🤖 Bot</option><option value="legitimate">✅ Légitime</option><option value="suspicious">⚠️ Suspect</option>
        </select>
        <button id="cls-all-btn" class="px-3 py-1 bg-brand-600 text-white rounded text-xs font-medium hover:bg-brand-500">Classifier tout le cluster</button>
        <span id="cls-result" class="text-xs self-center"></span>
    </div>
{% endblock %}
{% block content %}
<div class="space-y-4">
    <!-- KPIs -->
    <div class="grid grid-cols-2 md:grid-cols-4 xl:grid-cols-8 gap-3" id="kpi-row"></div>

    <!-- Row 1: Timeline + Threat breakdown -->
    <div class="grid grid-cols-1 lg:grid-cols-3 gap-4">
        <div class="section-card lg:col-span-2">
            <div class="section-header">
                <span class="section-title">
                    Timeline du cluster
                    <span class="relative inline-block ml-1"><button onclick="docToggle(this)" class="doc-btn">ⓘ</button><div class="doc-panel">
                        <h4>Activité temporelle</h4>
                        <p>Détections et IPs actives par heure. Les bursts synchronisés confirment la coordination.</p>
                        <p class="doc-source">Source : ml_detected_anomalies GROUP BY hour</p>
                    </div></span>
                </span>
            </div>
            <div class="section-body"><div id="timeline-chart" style="height:220px"></div></div>
        </div>
        <div class="section-card">
            <div class="section-header"><span class="section-title">Menaces</span></div>
            <div class="section-body"><div id="threat-chart" style="height:220px"></div></div>
        </div>
    </div>

    <!-- Row 2: JA4 convergence + ASN infrastructure + Host targets -->
    <div class="grid grid-cols-1 lg:grid-cols-3 gap-4">
        <div class="section-card">
            <div class="section-header">
                <span class="section-title">
                    Convergence JA4
                    <span class="relative inline-block ml-1"><button onclick="docToggle(this)" class="doc-btn">ⓘ</button><div class="doc-panel">
                        <h4>Empreintes TLS du cluster</h4>
                        <p>Répartition des JA4 dans le cluster. Une seule JA4 dominante = même outil/framework. Diversité JA4 = evasion ou rotation.</p>
                        <p><strong>Action :</strong> Cliquez sur une JA4 pour l'investiguer.</p>
                        <p class="doc-source">Source : ml_detected_anomalies GROUP BY ja4</p>
                    </div></span>
                </span>
            </div>
            <div class="section-body">
                <div id="ja4-chart" style="height:180px"></div>
                <div id="ja4-list" class="mt-2 space-y-1"></div>
            </div>
        </div>
        <div class="section-card">
            <div class="section-header">
                <span class="section-title">
                    Infrastructure (ASN)
                    <span class="relative inline-block ml-1"><button onclick="docToggle(this)" class="doc-btn">ⓘ</button><div class="doc-panel">
                        <h4>Systèmes autonomes</h4>
                        <p>D'où viennent les IPs du cluster. Un seul ASN datacenter = hébergé. ASN ISP variés = botnet distribué.</p>
                        <p class="doc-source">Source : ml_detected_anomalies GROUP BY asn_org</p>
                    </div></span>
                </span>
            </div>
            <div class="section-body"><div id="asn-chart" style="height:250px"></div></div>
        </div>
        <div class="section-card">
            <div class="section-header"><span class="section-title">Hosts ciblés</span></div>
            <div class="section-body"><div id="host-chart" style="height:250px"></div></div>
        </div>
    </div>

    <!-- Row 3: Member table -->
    <div class="section-card">
        <div class="section-header">
            <span class="section-title">
                Membres du cluster
                <span class="relative inline-block ml-1"><button onclick="docToggle(this)" class="doc-btn">ⓘ</button><div class="doc-panel">
                    <h4>IPs membres</h4>
                    <p>Toutes les IPs rattachées à ce cluster par HDBSCAN. Triez par score pour prioriser l'investigation.</p>
                    <p><strong>Action :</strong> Cliquez pour investiguer l'IP. Utilisez « Classifier tout le cluster » pour un traitement en masse.</p>
                    <p class="doc-source">Source : ml_detected_anomalies WHERE campaign_id=…</p>
                </div></span>
            </span>
            <span class="text-xs text-gray-500" id="member-count"></span>
        </div>
        <div class="overflow-x-auto" style="max-height:500px;overflow-y:auto">
            <table class="data-table" id="member-table"><thead><tr>
                <th style="width:16px"></th>
                <th>IP</th><th>Score</th><th>Threat</th><th>JA4</th>
                <th>Hits</th><th>Velocity</th><th>Fuzz</th>
                <th>Host</th><th>ASN</th><th>Pays</th><th>Browser</th><th>Bot</th><th>Date</th>
            </tr></thead><tbody id="member-body"></tbody></table>
        </div>
    </div>
</div>
{% endblock %}
{% block scripts %}
<script>
const CID = {{ cid }};
const THREAT_COLORS = {CRITICAL:'#ef4444',HIGH:'#f97316',MEDIUM:'#eab308',NORMAL:'#6b7280',KNOWN_BOT:'#3b82f6',LEGITIMATE_BROWSER:'#22c55e',ANUBIS_DENY:'#dc2626'};
let clusterIPs = [];

async function loadCluster() {
    try {
        const r = await fetch('/api/cluster/' + CID);
        const d = await r.json();
        const p = d.profile || {};

        // KPIs
        document.getElementById('kpi-row').innerHTML = [
            kpi('IPs', fmtNum(p.unique_ips)),
            kpi('JA4 uniques', fmtNum(p.unique_ja4)),
            kpi('Hosts ciblés', fmtNum(p.unique_hosts)),
            kpi('ASNs', fmtNum(p.unique_asns)),
            kpi('Score moyen', p.avg_score != null ? parseFloat(p.avg_score).toFixed(4) : '—'),
            kpi('Hits totaux', fmtNum(p.total_hits)),
            kpi('Menaces', fmtNum(p.threat_count)),
            kpi('Bots connus', fmtNum(p.known_bot_count)),
        ].join('');

        // Store IPs for bulk classify
        clusterIPs = (p.ip_list || []).map(ip => String(ip).replace('::ffff:',''));

        // Timeline
        const tl = d.timeline || [];
        if (tl.length) {
            const ch = echarts.init(document.getElementById('timeline-chart'));
            ch.setOption(ecBase({
                tooltip: ecTooltip({trigger:'axis'}),
                grid:{left:40,right:20,top:20,bottom:30},
                xAxis:{type:'category',data:tl.map(r=>(r.hour||'').substring(11,16)),axisLabel:{color:EC_TEXT,fontSize:10}},
                yAxis:[
                    {type:'value',name:'Détections',nameTextStyle:{color:EC_TEXT,fontSize:10},axisLabel:{color:EC_TEXT,fontSize:10}},
                    {type:'value',name:'IPs',nameTextStyle:{color:EC_TEXT,fontSize:10},axisLabel:{color:EC_TEXT,fontSize:10},splitLine:{show:false}}
                ],
                series:[
                    {name:'Détections',type:'bar',data:tl.map(r=>r.detections),itemStyle:{color:'#6366f1'},barWidth:'60%'},
                    {name:'IPs actives',type:'line',yAxisIndex:1,data:tl.map(r=>r.active_ips),lineStyle:{color:'#f97316'},itemStyle:{color:'#f97316'},smooth:true}
                ]
            }));
        }

        // Threat pie
        const threats = d.threats || [];
        if (threats.length) {
            const ch2 = echarts.init(document.getElementById('threat-chart'));
            ch2.setOption(ecBase({
                tooltip:ecTooltip({trigger:'item'}),
                series:[{type:'pie',radius:['30%','70%'],
                    label:{color:EC_TEXT,fontSize:10},
                    data:threats.map(t=>({name:t.threat_level,value:t.cnt,itemStyle:{color:THREAT_COLORS[t.threat_level]||'#6b7280'}}))}]
            }));
        }

        // JA4 breakdown
        const ja4s = d.ja4_breakdown || [];
        if (ja4s.length) {
            const ch3 = echarts.init(document.getElementById('ja4-chart'));
            ch3.setOption(ecBase({
                tooltip:ecTooltip({trigger:'item'}),
                series:[{type:'pie',radius:['25%','65%'],
                    label:{color:EC_TEXT,fontSize:9,formatter:'{b|{b}}\n{c}',rich:{b:{fontSize:8,color:'#9ca3af'}}},
                    data:ja4s.slice(0,10).map(r=>({name:(r.ja4||'').substring(0,20),value:r.sessions,fullJa4:r.ja4}))}]
            }));
            ch3.on('click', params => {
                if (params.data && params.data.fullJa4) window.location = '/ja4/' + encodeURIComponent(params.data.fullJa4);
            });
            // JA4 clickable list
            document.getElementById('ja4-list').innerHTML = ja4s.slice(0,5).map(r =>
                `<a href="/ja4/${encodeURIComponent(r.ja4)}" class="block text-xs font-mono text-purple-400 hover:underline truncate" title="${escapeHtml(r.ja4)}">${escapeHtml(r.ja4)} <span class="text-gray-500">(${r.sessions} sess, ${r.unique_ips} IPs)</span></a>`
            ).join('');
        }

        // ASN breakdown
        const asns = d.asn_breakdown || [];
        if (asns.length) {
            const ch4 = echarts.init(document.getElementById('asn-chart'));
            ch4.setOption(ecBase({tooltip:ecTooltip({trigger:'axis'}),
                grid:{left:10,right:30,top:5,bottom:5,containLabel:true},
                yAxis:{type:'category',data:asns.map(r=>r.asn_org).reverse(),axisLabel:{color:EC_TEXT,fontSize:9,width:100,overflow:'truncate'},axisLine:{show:false}},
                xAxis:{type:'value',show:false},
                series:[{type:'bar',data:asns.map(r=>r.sessions).reverse(),barWidth:'60%',itemStyle:{color:'#f97316'},
                    label:{show:true,position:'right',color:EC_TEXT,fontSize:10}}]}));
        }

        // Host breakdown
        const hosts = d.host_breakdown || [];
        if (hosts.length) {
            const ch5 = echarts.init(document.getElementById('host-chart'));
            ch5.setOption(ecBase({tooltip:ecTooltip({trigger:'axis'}),
                grid:{left:10,right:30,top:5,bottom:5,containLabel:true},
                yAxis:{type:'category',data:hosts.map(r=>r.host).reverse(),axisLabel:{color:EC_TEXT,fontSize:9,width:100,overflow:'truncate'},axisLine:{show:false}},
                xAxis:{type:'value',show:false},
                series:[{type:'bar',data:hosts.map(r=>r.sessions).reverse(),barWidth:'60%',itemStyle:{color:'#22c55e'},
                    label:{show:true,position:'right',color:EC_TEXT,fontSize:10}}]}));
        }

        // Member table
        const members = d.members || [];
        document.getElementById('member-count').textContent = members.length + ' membres';
        document.getElementById('member-body').innerHTML = members.map(row => {
            const ip = String(row.src_ip||'').replace('::ffff:','');
            return `<tr onclick="window.location='/ip/${encodeURIComponent(ip)}'">
                <td><span class="w-2 h-2 rounded-full inline-block" style="background:${THREAT_COLORS[row.threat_level]||'#6b7280'}"></span></td>
                <td>${fmtIP(row.src_ip)}</td>
                <td>${fmtScore(row.anomaly_score)}</td>
                <td>${threatBadge(row.threat_level)}</td>
                <td><a href="/ja4/${encodeURIComponent(row.ja4||'')}" onclick="event.stopPropagation()" class="text-purple-400 hover:underline font-mono text-[11px]" title="${escapeHtml(row.ja4||'')}">${escapeHtml((row.ja4||'').substring(0,22))}…</a></td>
                <td class="font-mono text-xs">${row.hits||0}</td>
                <td class="font-mono text-xs">${parseFloat(row.hit_velocity||0).toFixed(1)}</td>
                <td class="font-mono text-xs">${parseFloat(row.fuzzing_index||0).toFixed(3)}</td>
                <td class="text-xs max-w-[100px] truncate">${escapeHtml(row.host||'')}</td>
                <td class="text-xs max-w-[100px] truncate">${fmtASN(row.asn_org)}</td>
                <td>${fmtCountry(row.country_code)}</td>
                <td class="text-xs">${escapeHtml(row.browser_family||'')}</td>
                <td class="text-xs">${row.bot_name ? fmtBotName(row.bot_name) : ''}</td>
                <td class="text-[11px] text-gray-400">${(row.detected_at||'').substring(0,16)}</td>
            </tr>`;
        }).join('') || '<tr><td colspan="14" class="text-center text-gray-500 py-6">Aucun membre</td></tr>';

    } catch(e) { console.error('Cluster load error:', e); }
}

function kpi(label, value) {
    return `<div class="kpi-card"><div class="text-[10px] text-gray-500 uppercase tracking-wider mb-1">${label}</div><div class="text-lg font-bold text-white">${value}</div></div>`;
}

// Bulk classify all IPs in cluster
document.getElementById('cls-all-btn').onclick = async () => {
    if (!clusterIPs.length) return;
    const type = document.getElementById('cls-select').value;
    const result = document.getElementById('cls-result');
    let ok = 0, fail = 0;
    result.textContent = '⏳ Classification en cours…';
    for (const ip of clusterIPs) {
        try {
            const r = await fetch('/api/classify', {
                method:'POST', headers:{'Content-Type':'application/json'},
                body: JSON.stringify({ip, classification: type, comment: `Cluster #${CID} bulk classify`})
            });
            if (r.ok) ok++; else fail++;
        } catch(e) { fail++; }
    }
    result.textContent = `✓ ${ok} IP(s) → ${type}` + (fail ? ` (${fail} erreurs)` : '');
    result.className = 'text-xs self-center ' + (fail ? 'text-orange-400' : 'text-green-400');
};

loadCluster();
</script>
{% endblock %}
