{% extends "base.html" %}
{% block title %}JA4 SOC — Scores ML{% endblock %}
{% block page_title %}
    Scores ML
    <span class="relative inline-block ml-1"><button onclick="docToggle(this)" class="doc-btn">ⓘ</button><div class="doc-panel">
        <h4>Toutes les classifications ML</h4>
        <p>Chaque session analysée reçoit un score composite (EIF + AE + XGBoost). Cette vue montre TOUTES les sessions, pas seulement les anomalies.</p>
        <p><strong>AE Error :</strong> Erreur de reconstruction autoencoder (élevé = inhabituel).</p>
        <p><strong>XGB Prob :</strong> Probabilité supervisée (entraîné sur labels SOC).</p>
        <p class="doc-source">Source : ml_all_scores (3 derniers jours)</p>
    </div></span>
{% endblock %}
{% block content %}
<div class="space-y-4">
    <!-- Score distribution charts -->
    <div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
        <div class="section-card">
            <div class="section-header"><span class="section-title">Distribution des scores
                <span class="relative inline-block"><button onclick="docToggle(this)" class="doc-btn">ⓘ</button><div class="doc-panel">
                    <h4>Histogramme des scores d'anomalie</h4>
                    <p>Score normalisé [0,1] combinant EIF + AE + XGBoost. La majorité des sessions devraient être proches de 0 (normal). Un pic à droite = vague d'attaque.</p>
                    <p><strong>Seuil :</strong> Les sessions au-dessus du seuil (typiquement 0.5–0.7) sont classées HIGH/CRITICAL.</p>
                    <p class="doc-source">Source : ml_all_scores.anomaly_score</p>
                </div></span>
            </span></div>
            <div class="section-body"><div id="score-dist-chart" style="height:180px"></div></div>
        </div>
        <div class="section-card">
            <div class="section-header"><span class="section-title">AE Error vs XGB Probability
                <span class="relative inline-block"><button onclick="docToggle(this)" class="doc-btn">ⓘ</button><div class="doc-panel">
                    <h4>Scatter bi-modèle</h4>
                    <p>X = erreur de reconstruction autoencoder (comportement inhabituel). Y = probabilité XGBoost (supervisé sur labels SOC).</p>
                    <p><strong>Interprétation :</strong> Quadrant haut-droit = consensus des deux modèles. Haut-gauche = XGB dit bot mais AE dit normal → possible label biaisé.</p>
                    <p class="doc-source">Source : ml_all_scores.ae_recon_error, xgb_prob</p>
                </div></span>
            </span></div>
            <div class="section-body"><div id="score-scatter-chart" style="height:180px"></div></div>
        </div>
    </div>
    <div class="flex items-center gap-3 flex-wrap">
        <div class="flex gap-1.5" id="threat-filters">
            <button class="filter-btn active" data-filter="">Tous</button>
            <button class="filter-btn" data-filter="CRITICAL">Critical</button>
            <button class="filter-btn" data-filter="HIGH">High</button>
            <button class="filter-btn" data-filter="NORMAL">Normal</button>
            <button class="filter-btn" data-filter="KNOWN_BOT">Known Bot</button>
            <button class="filter-btn" data-filter="LEGITIMATE_BROWSER">Browser</button>
        </div>
        <input type="text" id="search-input" placeholder="Rechercher IP, JA4, host…" class="px-3 py-1.5 bg-gray-800 border border-gray-700 rounded-lg text-sm text-gray-300 w-64 focus:border-brand-500 focus:outline-none ml-auto">
    </div>
    <div class="bg-gray-900 rounded-xl border border-gray-800 overflow-hidden">
        <div class="overflow-x-auto max-h-[70vh] overflow-y-auto">
            <table class="data-table">
                <thead><tr>
                    <th class="cursor-pointer" data-sort="detected_at">Date ↕</th>
                    <th>IP</th>
                    <th class="cursor-pointer" data-sort="anomaly_score">Score ↕</th>
                    <th class="cursor-pointer" data-sort="raw_anomaly_score">Raw ↕</th>
                    <th>AE Error</th>
                    <th>XGB Prob</th>
                    <th>Threat</th>
                    <th>Model</th>
                    <th>JA4</th>
                    <th>Host</th>
                    <th>Hits</th>
                    <th>Pays</th>
                </tr></thead>
                <tbody id="scores-body"></tbody>
            </table>
        </div>
        <div class="flex items-center justify-between px-4 py-3 border-t border-gray-800">
            <span class="text-xs text-gray-500" id="scores-info">—</span>
            <div class="flex gap-2">
                <button id="prev-btn" class="px-3 py-1 bg-gray-800 rounded text-sm text-gray-400 hover:text-white disabled:opacity-30">&larr;</button>
                <button id="next-btn" class="px-3 py-1 bg-gray-800 rounded text-sm text-gray-400 hover:text-white disabled:opacity-30">&rarr;</button>
            </div>
        </div>
    </div>
</div>
{% endblock %}
{% block scripts %}
<script>
let sPage=1, sSort='detected_at', sOrder='DESC', sThreat='';
const urlParams = new URLSearchParams(window.location.search);
let sASN=urlParams.get('asn_org')||'',
    sCountry=urlParams.get('country_code')||'',
    sJA4=urlParams.get('ja4')||'';
if(urlParams.get('threat_level')) sThreat=urlParams.get('threat_level');
async function loadScores() {
    const params = new URLSearchParams({page:sPage,per_page:50,sort:sSort,order:sOrder});
    if(sThreat) params.set('threat_level',sThreat);
    if(sASN) params.set('asn_org',sASN);
    if(sCountry) params.set('country_code',sCountry);
    if(sJA4) params.set('ja4',sJA4);
    const sSearch = document.getElementById('search-input').value.trim();
    if(sSearch) params.set('search',sSearch);
    try {
        const r = await fetch('/api/scores?'+params);
        const d = await r.json();
        const tbody = document.getElementById('scores-body');
        tbody.innerHTML = (d.data||[]).map(row => `<tr onclick="window.location='/ip/'+encodeURIComponent('${escapeHtml(row.src_ip||'')}')">
            <td class="text-xs whitespace-nowrap">${row.detected_at||''}</td>
            <td class="whitespace-nowrap">${fmtIP(row.src_ip)}</td>
            <td>${fmtScore(row.anomaly_score)}</td>
            <td>${fmtScore(row.raw_anomaly_score)}</td>
            <td class="text-xs">${(row.ae_recon_error||0).toFixed(6)}</td>
            <td class="text-xs">${(row.xgb_prob||0).toFixed(4)}</td>
            <td>${fmtThreatLink(row.threat_level)}</td>
            <td class="text-xs">${escapeHtml(row.model_name||'')}</td>
            <td class="text-xs font-mono max-w-[100px] truncate">${fmtJA4(row.ja4)}</td>
            <td class="text-xs max-w-[120px] truncate">${escapeHtml(row.host||'')}</td>
            <td>${row.hits||0}</td>
            <td>${fmtCountry(row.country_code)}</td>
        </tr>`).join('') || '<tr><td colspan="12" class="text-center text-gray-500 py-8">Aucun score</td></tr>';
        const total = d.total||0;
        document.getElementById('scores-info').textContent = `${total} résultats — page ${sPage}/${Math.max(1,Math.ceil(total/50))}`;
        document.getElementById('prev-btn').disabled = sPage <= 1;
        document.getElementById('next-btn').disabled = sPage * 50 >= total;
    } catch(e) { console.error(e); }
}
document.getElementById('prev-btn').onclick = () => { if(sPage>1){sPage--;loadScores();} };
document.getElementById('next-btn').onclick = () => { sPage++;loadScores(); };
document.querySelectorAll('[data-sort]').forEach(th => th.onclick = () => {
    const s = th.dataset.sort;
    if(sSort===s) sOrder = sOrder==='DESC'?'ASC':'DESC'; else { sSort=s; sOrder='DESC'; }
    sPage=1; loadScores();
});
document.querySelectorAll('[data-filter]').forEach(btn => btn.onclick = () => {
    document.querySelectorAll('[data-filter]').forEach(b=>b.classList.remove('active'));
    btn.classList.add('active');
    sThreat = btn.dataset.filter; sPage=1; loadScores();
});
// Search with debounce
let sSearchTimer;
document.getElementById('search-input').addEventListener('input', () => {
    clearTimeout(sSearchTimer);
    sSearchTimer = setTimeout(() => { sPage=1; loadScores(); }, 300);
});
loadScores();

// Score distribution charts
async function loadScoreSummary() {
    try {
        const r = await fetch('/api/scores?per_page=500');
        const d = await r.json();
        const rows = d.data || [];
        // Histogram of anomaly_score
        const buckets = {};
        rows.forEach(row => {
            const b = Math.round((row.anomaly_score||0)*10)/10;
            buckets[b] = (buckets[b]||0)+1;
        });
        const labels = Object.keys(buckets).sort((a,b)=>a-b);
        const ch1 = echarts.init(document.getElementById('score-dist-chart'));
        ch1.setOption(ecBase({
            tooltip:ecTooltip({trigger:'axis'}),
            grid:{left:40,right:15,top:10,bottom:25},
            xAxis:{type:'category',data:labels,axisLabel:{color:EC_TEXT,fontSize:10},axisLine:{lineStyle:{color:EC_GRID}}},
            yAxis:{type:'value',splitLine:{lineStyle:{color:EC_GRID,type:'dashed'}},axisLabel:{color:EC_TEXT}},
            series:[{type:'bar',data:labels.map(l=>buckets[l]),barWidth:'70%',
                itemStyle:{color:new echarts.graphic.LinearGradient(0,0,0,1,[{offset:0,color:'#6366f1'},{offset:1,color:'#4338ca'}])}}]
        }));
        // Scatter AE vs XGB
        const scatterData = rows.filter(r=>r.ae_recon_error>0||r.xgb_prob>0).map(r=>[r.ae_recon_error||0,r.xgb_prob||0]);
        if (scatterData.length) {
            const ch2 = echarts.init(document.getElementById('score-scatter-chart'));
            ch2.setOption(ecBase({
                tooltip:ecTooltip({trigger:'item',formatter:p=>`AE: ${p.data[0].toFixed(4)}<br>XGB: ${p.data[1].toFixed(4)}`}),
                grid:{left:40,right:15,top:10,bottom:25},
                xAxis:{name:'AE Error',nameTextStyle:{color:EC_TEXT},type:'value',splitLine:{lineStyle:{color:EC_GRID,type:'dashed'}},axisLabel:{color:EC_TEXT}},
                yAxis:{name:'XGB Prob',nameTextStyle:{color:EC_TEXT},type:'value',splitLine:{lineStyle:{color:EC_GRID,type:'dashed'}},axisLabel:{color:EC_TEXT}},
                series:[{type:'scatter',data:scatterData,symbolSize:6,itemStyle:{color:'rgba(99,102,241,0.6)'}}]
            }));
        }
    } catch(e) { console.error(e); }
}
loadScoreSummary();
</script>
{% endblock %}
