{% extends "base.html" %}
{% block title %}Erreurs & Bannis{% endblock %}

{% block content %}
<section class="card">

  <div class="err-header">
    <h2>Sécurité</h2>
    <div class="sec-tabs">
      <button type="button" class="sec-tab" data-tab="errors">
        Erreurs 404 <span class="tab-badge">{{ total }}</span>
      </button>
      <button type="button" class="sec-tab" data-tab="banned">
        Bannissements <span class="tab-badge" id="banned-tab-badge">{{ banned_total }}</span>
      </button>
    </div>
    <button type="button" class="btn btn-sm" onclick="location.reload()">↺ Actualiser</button>
  </div>

  {# ─────────────── Onglet Erreurs 404 ─────────────── #}
  <div id="tab-errors" class="sec-tab-panel">
    {% if not log_configured %}
      <p style="color:var(--muted);margin-top:.5rem">STATS_LOG_FILE non configuré — impossible d'analyser les logs.</p>
    {% elif not entries %}
      <p style="color:var(--muted);margin-top:.5rem">Aucune erreur 404 dans les logs récents.</p>
    {% else %}
    <div class="err-search-wrap">
      <input type="search" id="err-search" class="err-search" placeholder="Filtrer par chemin ou adresse IP…" autocomplete="off">
      <span class="err-search-count" id="err-search-count"></span>
    </div>
    <div id="err-summary" class="err-summary" style="display:none"></div>
    <table class="file-table err-table">
      <thead>
        <tr>
          <th class="col-err-status"></th>
          <th>Chemin</th>
          <th class="col-err-count">Requêtes</th>
          <th class="col-date">Dernière vue</th>
          <th class="col-err-ips">IPs</th>
          <th class="col-actions"></th>
        </tr>
      </thead>
      <tbody>
      {% for path, info in entries %}
      <tr class="err-row" data-path="{{ path }}" data-ips="{{ info.ips.keys() | join(' ') }}">
        <td class="col-err-status">
          <span class="err-status-dot err-status-dot--{% if not info.last_seen %}unk{% elif (info.ips | length) > 0 %}active{% else %}ok{% endif %}"
                data-path="{{ path }}" title="Cliquer pour vérifier">●</span>
        </td>
        <td class="err-path" title="{{ path }}"><code>{{ path }}</code></td>
        <td class="col-err-count"><span class="hits-badge hits-active">{{ info.count }}</span></td>
        <td class="col-date">{{ info.last_seen.strftime('%d/%m/%Y %H:%M') }}</td>
        <td class="col-err-ips">{{ info.ips | length }}</td>
        <td class="col-actions">
          <button type="button" class="btn-detail" data-path="{{ path }}" title="Voir le détail">▼</button>
        </td>
      </tr>
      <tr class="err-detail-row" id="detail-{{ loop.index }}" style="display:none">
        <td colspan="6" class="err-detail-cell">
          <div class="err-detail-content" id="detail-content-{{ loop.index }}">
            <span class="err-loading">Chargement…</span>
          </div>
        </td>
      </tr>
      {% endfor %}
      </tbody>
    </table>
    {% endif %}

    {% if ignored_ips %}
    <div style="margin-top:1.25rem">
      <h3 style="font-size:.9rem;margin-bottom:.5rem;color:var(--muted)">IPs ignorées</h3>
      <div class="err-ignored-list">
        {% for ip in ignored_ips %}
        <span class="err-ignored-chip">
          {{ ip }}
          <button type="button" class="err-ignored-remove" data-ip="{{ ip }}" title="Retirer">✕</button>
        </span>
        {% endfor %}
      </div>
    </div>
    {% endif %}
  </div>{# /tab-errors #}

  {# ─────────────── Onglet Bannissements (chargé en AJAX) ─────────────── #}
  <div id="tab-banned" class="sec-tab-panel" style="display:none">
    <p id="ban-loading" style="color:var(--muted);margin-top:.5rem;font-style:italic">Chargement…</p>
    <p id="ban-empty"   style="display:none;color:var(--muted);margin-top:.5rem">Aucune IP bannie dans global-blacklist.</p>
    <p id="ban-error"   style="display:none;color:#b91c1c;margin-top:.5rem">Erreur lors du chargement des bannissements.</p>
    <div id="ban-content" style="display:none">
      <div class="err-search-wrap" style="margin-top:.5rem">
        <input type="search" id="ban-search" class="err-search" placeholder="Filtrer par IP, CIDR ou nom d'AS…" autocomplete="off">
        <span class="err-search-count" id="ban-search-count"></span>
        <button id="btn-ban-expand-all" type="button" class="btn btn-sm">▼ Tout déplier</button>
      </div>
      <table class="file-table banned-table">
        <thead>
          <tr>
            <th>IP / CIDR</th>
            <th class="col-asn">AS</th>
            <th>Opérateur</th>
            <th class="col-country">Pays</th>
            <th class="col-actions"></th>
          </tr>
        </thead>
        <tbody id="ban-tbody"></tbody>
      </table>
    </div>
  </div>{# /tab-banned #}

</section>

<script>
(function () {
  /* ── URLs ── */
  const DETAIL_URL        = {{ url_for('errors_detail')        | tojson }};
  const IGNORE_URL        = {{ url_for('errors_ignore')        | tojson }};
  const BAN_URL           = {{ url_for('errors_ban')           | tojson }};
  const ASINFO_URL        = {{ url_for('errors_asinfo')        | tojson }};
  const UNBAN_URL         = {{ url_for('errors_unban')         | tojson }};
  const BANNED_GROUPS_URL = {{ url_for('errors_banned_groups') | tojson }};

  /* ── Onglets ── */
  const tabBtns   = document.querySelectorAll('.sec-tab');
  const tabPanels = document.querySelectorAll('.sec-tab-panel');

  function activateTab(name) {
    tabBtns.forEach(b => b.classList.toggle('sec-tab--active', b.dataset.tab === name));
    tabPanels.forEach(p => p.style.display = p.id === 'tab-' + name ? '' : 'none');
    history.replaceState(null, '', location.pathname + (name !== 'errors' ? '#' + name : ''));
    if (name === 'banned') loadBannedGroups();
  }

  tabBtns.forEach(b => b.addEventListener('click', () => activateTab(b.dataset.tab)));
  activateTab(location.hash === '#banned' ? 'banned' : 'errors');

  /* ═══════════════════════════════════════════════
     ONGLET ERREURS 404
  ═══════════════════════════════════════════════ */

  /* ── Filtre erreurs ── */
  const errSearch  = document.getElementById('err-search');
  const errCount   = document.getElementById('err-search-count');
  const errRows    = [...document.querySelectorAll('#tab-errors .err-row')];

  function applyErrFilter() {
    const q = errSearch?.value.trim().toLowerCase() || '';
    let vis = 0;
    errRows.forEach(row => {
      const match = !q || row.dataset.path.toLowerCase().includes(q) || row.dataset.ips.toLowerCase().includes(q);
      const dr = row.nextElementSibling;
      row.style.display = match ? '' : 'none';
      if (!match && dr?.classList.contains('err-detail-row')) {
        dr.style.display = 'none';
        row.querySelector('.btn-detail').textContent = '▼';
        if (openDetailRow === dr) openDetailRow = null;
      }
      if (match) vis++;
    });
    if (errCount) errCount.textContent = q ? `${vis} / ${errRows.length}` : '';
  }
  errSearch?.addEventListener('input', applyErrFilter);

  /* ── Expand/collapse détail ── */
  let openDetailRow = null;
  document.querySelectorAll('.btn-detail').forEach(btn => {
    btn.addEventListener('click', () => {
      const detailRow = btn.closest('tr').nextElementSibling;
      const content   = detailRow.querySelector('.err-detail-content');
      const open      = detailRow.style.display !== 'none';
      if (openDetailRow && openDetailRow !== detailRow) {
        openDetailRow.style.display = 'none';
        openDetailRow.previousElementSibling.querySelector('.btn-detail').textContent = '▼';
      }
      if (open) { detailRow.style.display = 'none'; btn.textContent = '▼'; openDetailRow = null; return; }
      detailRow.style.display = ''; btn.textContent = '▲'; openDetailRow = detailRow;
      loadDetail(btn.dataset.path, content);
    });
  });

  async function loadDetail(path, container) {
    container.innerHTML = '<span class="err-loading">Chargement…</span>';
    try {
      const d = await fetch(DETAIL_URL + '?path=' + encodeURIComponent(path)).then(r => r.json());
      renderDetail(d, container);
    } catch { container.innerHTML = '<span style="color:#b91c1c">Erreur réseau.</span>'; }
  }

  function renderDetail(d, container) {
    const statusBadge = d.still_404
      ? '<span class="err-badge err-badge--active">✗ Toujours actif</span>'
      : '<span class="err-badge err-badge--ok">✓ Résolu</span>';
    let html = `<div class="err-detail-meta">${statusBadge}<span class="err-detail-info">${d.count} requête(s) · dernière le ${d.last_seen}</span></div>`;
    html += '<table class="err-ip-table"><thead><tr><th>IP</th><th>Requêtes</th><th>Dernière vue</th><th>Referers</th><th></th></tr></thead><tbody>';
    for (const ip of d.ips) {
      const referers = [...new Set(ip.hits.map(h => h.referer).filter(Boolean))];
      const refHtml  = referers.length
        ? referers.slice(0,3).map(r => `<span class="err-referer" title="${escHtml(r)}">${escHtml(trimUrl(r))}</span>`).join(' ')
        : '<span style="color:var(--muted)">—</span>';
      html += `<tr class="${ip.ignored ? 'err-ip-ignored' : ''}">
        <td><code class="err-ip">${ip.ip}</code>${ip.ignored ? ' <span class="err-ignored-tag">ignorée</span>' : ''}</td>
        <td><span class="hits-badge ${ip.count > 10 ? 'hits-active' : 'hits-zero'}">${ip.count}</span></td>
        <td class="col-date">${ip.last_seen}</td>
        <td class="err-referers">${refHtml}</td>
        <td class="col-actions"><div class="row-actions">
          <button type="button" class="btn-ignore-ip ${ip.ignored ? 'btn-ignore-ip--active' : ''}"
                  data-ip="${ip.ip}" data-action="${ip.ignored ? 'remove' : 'add'}"
                  title="${ip.ignored ? "Retirer des ignorées" : "Ignorer cette IP"}">${ip.ignored ? '👁' : '🙈'}</button>
          <button type="button" class="btn-ban-ip" data-ip="${ip.ip}" title="Bannir dans fail2ban">🔨</button>
        </div></td>
      </tr>`;
    }
    html += '</tbody></table>';
    container.innerHTML = html;

    container.querySelectorAll('.btn-ignore-ip').forEach(b => {
      b.addEventListener('click', async () => {
        const fd = new FormData(); fd.append('ip', b.dataset.ip); fd.append('action', b.dataset.action);
        const j = await fetch(IGNORE_URL, { method: 'POST', body: fd }).then(r => r.json());
        if (j.ok) loadDetail(d.path, container);
      });
    });

    container.querySelectorAll('.btn-ban-ip').forEach(b => {
      b.addEventListener('click', async () => {
        const ip = b.dataset.ip;
        const div = b.closest('.row-actions');
        b.disabled = true; b.textContent = '⏳';
        let asInfo = {};
        try { asInfo = await fetch(ASINFO_URL + '?ip=' + encodeURIComponent(ip)).then(r => r.json()); } catch {}
        const asLabel = asInfo.asn
          ? `AS${asInfo.asn} · ${escHtml(asInfo.name)}${asInfo.country ? ' ['+asInfo.country+']' : ''}`
          : '<span style="color:var(--muted)">AS inconnu</span>';
        const n = asInfo.prefix_count || 0;
        div.innerHTML =
          `<div class="ban-panel"><span class="ban-panel-as">${asLabel}</span><div class="ban-panel-btns">
            <button class="btn-do-ban" data-ban-as="0" data-ip="${escHtml(ip)}">🔨 IP</button>`
          + (n ? `<button class="btn-do-ban btn-do-ban--as" data-ban-as="1" data-ip="${escHtml(ip)}" data-count="${n}">🔨 AS (${n})</button>` : '')
          + `<button class="btn-ban-cancel">✕</button></div></div>`;
        div.querySelector('.btn-ban-cancel').addEventListener('click', () => loadDetail(d.path, container));
        div.querySelectorAll('.btn-do-ban').forEach(btn => {
          btn.addEventListener('click', async () => {
            const banAs = btn.dataset.banAs === '1';
            if (banAs && !confirm(`Bannir les ${btn.dataset.count} préfixes de cet AS ?`)) return;
            div.querySelectorAll('button').forEach(x => x.disabled = true);
            const fd = new FormData(); fd.append('ip', btn.dataset.ip);
            if (banAs) fd.append('ban_as', '1');
            const j = await fetch(BAN_URL, { method: 'POST', body: fd }).then(r => r.json());
            if (j.ok) {
              div.innerHTML = `<span class="ban-ok">${banAs ? '✓ AS banni (' + j.count + ' préfixes)' : '✓ Banni'}</span>`;
              setTimeout(() => loadDetail(d.path, container), 1500);
            } else {
              div.innerHTML = `<span class="ban-err" title="${escHtml(j.error||'')}">✗ ${escHtml(j.error||'Erreur')}</span>`;
            }
          });
        });
      });
    });
  }

  document.querySelectorAll('.err-ignored-remove').forEach(b => {
    b.addEventListener('click', async () => {
      const fd = new FormData(); fd.append('ip', b.dataset.ip); fd.append('action', 'remove');
      await fetch(IGNORE_URL, { method: 'POST', body: fd });
      location.reload();
    });
  });

  const STATUS_BATCH_URL = '{{ url_for("errors_status_batch") }}';

  function applyDotResult(dot, still_404) {
    dot.textContent = '●';
    dot.className = 'err-status-dot err-status-dot--' + (still_404 ? 'active' : 'ok');
    dot.title = still_404 ? 'Toujours actif' : 'Résolu';
  }

  function updateSummary() {
    const el = document.getElementById('err-summary');
    if (!el) return;
    let aReqs = 0, aIPs = 0, oReqs = 0, oIPs = 0;
    errRows.forEach(row => {
      if (row.style.display === 'none') return;
      const dot = row.querySelector('.err-status-dot');
      const reqs = parseInt(row.querySelector('.hits-badge')?.textContent, 10) || 0;
      const ips  = parseInt(row.querySelector('.col-err-ips')?.textContent,  10) || 0;
      if (dot?.classList.contains('err-status-dot--active'))     { aReqs += reqs; aIPs += ips; }
      else if (dot?.classList.contains('err-status-dot--ok'))    { oReqs += reqs; oIPs += ips; }
    });
    if (!aReqs && !oReqs) { el.style.display = 'none'; return; }
    el.style.display = '';
    el.innerHTML =
      `<span class="err-summary-item err-summary-item--active">● ${aReqs} req. · ${aIPs} IPs actives</span>` +
      `<span class="err-summary-item err-summary-item--ok">● ${oReqs} req. · ${oIPs} IPs résolues</span>`;
    const stillActive = document.querySelectorAll('.err-status-dot--active').length;
    document.title = stillActive > 0
      ? `Erreurs 404 (${stillActive}) — Static Alpinux`
      : `Erreurs 404 — Static Alpinux`;
  }

  document.querySelectorAll('.err-status-dot').forEach(dot => {
    dot.style.cursor = 'pointer';
    dot.addEventListener('click', async () => {
      dot.textContent = '○'; dot.className = 'err-status-dot';
      const r = await fetch(DETAIL_URL + '?path=' + encodeURIComponent(dot.dataset.path)).then(r => r.json());
      applyDotResult(dot, r.still_404);
      updateSummary();
    });
  });

  /* Auto-vérification au chargement : tous les dots actifs (rouges) */
  (async () => {
    const activeDots = Array.from(document.querySelectorAll('.err-status-dot--active'));
    if (!activeDots.length) { updateSummary(); return; }
    activeDots.forEach(d => { d.textContent = '○'; d.className = 'err-status-dot'; });
    try {
      const paths = activeDots.map(d => d.dataset.path);
      const data = await fetch(STATUS_BATCH_URL, {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({paths})
      }).then(r => r.json());
      activeDots.forEach(dot => applyDotResult(dot, data.results[dot.dataset.path] !== false));
    } catch {
      activeDots.forEach(d => { d.textContent = '●'; d.className = 'err-status-dot err-status-dot--active'; });
    }
    updateSummary();
  })();

  /* ═══════════════════════════════════════════════
     ONGLET BANNISSEMENTS (chargement AJAX)
  ═══════════════════════════════════════════════ */

  let bannedLoaded = false;
  let banTotal = {{ banned_total }};

  async function loadBannedGroups() {
    if (bannedLoaded) return;
    bannedLoaded = true;
    try {
      const data = await fetch(BANNED_GROUPS_URL).then(r => r.json());
      renderBannedGroups(data);
    } catch {
      document.getElementById('ban-loading').style.display = 'none';
      document.getElementById('ban-error').style.display = '';
      bannedLoaded = false;
    }
  }

  function renderBannedGroups(data) {
    document.getElementById('ban-loading').style.display = 'none';
    if (!data.groups || data.groups.length === 0) {
      document.getElementById('ban-empty').style.display = '';
      return;
    }
    banTotal = data.total;
    document.getElementById('banned-tab-badge').textContent = data.total;
    let html = '';
    for (const g of data.groups) {
      const asnLabel = g.asn ? `<strong>AS${escHtml(g.asn)}</strong>` : '<strong>AS inconnu</strong>';
      const n = g.entries.length;
      html += `<tr class="banned-as-row" data-asn="${escHtml(g.asn)}" data-name="${escHtml((g.name||'').toLowerCase())}" data-open="false">
        <td colspan="4" class="banned-as-cell">
          <span class="banned-toggle">▶</span>
          <span class="banned-as-label">${asnLabel}${g.name ? ' · '+escHtml(g.name) : ''}${g.country ? ' <span class="banned-country">['+escHtml(g.country)+']</span>' : ''}</span>
          <span class="err-total-badge" style="margin-left:.5rem">${n} entrée${n!==1?'s':''}</span>
        </td>
        <td class="col-actions">
          <button type="button" class="btn-unban-as btn btn-sm" data-asn="${escHtml(g.asn)}" data-entries="${escHtml(JSON.stringify(g.entries))}" title="Débloquer tout l'AS">🔓 AS</button>
        </td>
      </tr>`;
      for (const entry of g.entries) {
        html += `<tr class="banned-entry-row" data-asn="${escHtml(g.asn)}" data-entry="${escHtml(entry)}" style="display:none">
          <td colspan="4"><code class="err-ip">${escHtml(entry)}</code></td>
          <td class="col-actions">
            <button type="button" class="btn-unban btn btn-sm" data-entry="${escHtml(entry)}" title="Débloquer ${escHtml(entry)}">🔓</button>
          </td>
        </tr>`;
      }
    }
    document.getElementById('ban-tbody').innerHTML = html;
    document.getElementById('ban-content').style.display = '';
  }

  /* ── Replier/déplier un bloc AS ── */
  function toggleAsBlock(asRow, forceOpen) {
    const open = forceOpen !== undefined ? !forceOpen : asRow.dataset.open === 'true';
    const nowOpen = !open;
    asRow.dataset.open = nowOpen;
    asRow.querySelector('.banned-toggle').textContent = nowOpen ? '▼' : '▶';
    document.querySelectorAll(`#ban-tbody .banned-entry-row[data-asn="${asRow.dataset.asn}"]`).forEach(r => {
      r.style.display = nowOpen ? '' : 'none';
    });
  }

  document.addEventListener('click', e => {
    if (e.target.closest('button')) return;
    const asRow = e.target.closest('#ban-tbody .banned-as-row');
    if (asRow) toggleAsBlock(asRow);
  });

  /* ── Tout déplier / Tout replier ── */
  let allExpanded = false;
  document.getElementById('btn-ban-expand-all')?.addEventListener('click', () => {
    allExpanded = !allExpanded;
    document.querySelectorAll('#ban-tbody .banned-as-row').forEach(r => toggleAsBlock(r, !allExpanded));
    document.getElementById('btn-ban-expand-all').textContent = allExpanded ? '▲ Tout replier' : '▼ Tout déplier';
  });

  /* ── Filtre bannis ── */
  const banSearch = document.getElementById('ban-search');
  const banCount  = document.getElementById('ban-search-count');

  function applyBanFilter() {
    const q = banSearch?.value.trim().toLowerCase() || '';
    let vis = 0;
    document.querySelectorAll('#ban-tbody .banned-as-row').forEach(asRow => {
      const asn  = (asRow.dataset.asn  || '').toLowerCase();
      const name = (asRow.dataset.name || '').toLowerCase();
      const open = asRow.dataset.open === 'true';
      const rows = [...document.querySelectorAll(`#ban-tbody .banned-entry-row[data-asn="${asRow.dataset.asn}"]`)];
      let any = false;
      rows.forEach(er => {
        const match = !q || er.dataset.entry.toLowerCase().includes(q) || asn.includes(q) || name.includes(q);
        // avec filtre : afficher les matchs même si replié ; sans filtre : respecter l'état
        er.style.display = (q ? match : open) ? '' : 'none';
        if (match) { any = true; vis++; }
      });
      asRow.style.display = (!q || any) ? '' : 'none';
    });
    if (banCount) banCount.textContent = q ? `${vis} / ${banTotal}` : '';
  }
  banSearch?.addEventListener('input', applyBanFilter);

  /* ── Débloquer IP/CIDR ── */
  document.addEventListener('click', async e => {
    const btn = e.target.closest('.btn-unban:not(.btn-unban-as)');
    if (!btn) return;
    if (!confirm(`Débloquer ${btn.dataset.entry} ?`)) return;
    await doUnban(btn, [btn.dataset.entry]);
  });

  /* ── Débloquer AS entier ── */
  document.addEventListener('click', async e => {
    const btn = e.target.closest('.btn-unban-as');
    if (!btn) return;
    let entries; try { entries = JSON.parse(btn.dataset.entries); } catch { return; }
    if (!confirm(`Débloquer les ${entries.length} entrée(s) de cet AS ?`)) return;
    await doUnban(btn, entries, btn.dataset.asn);
  });

  async function doUnban(btn, entries, asn) {
    btn.disabled = true; btn.textContent = '⏳';
    const fd = new FormData(); entries.forEach(e => fd.append('entries', e));
    try {
      const j = await fetch(UNBAN_URL, { method: 'POST', body: fd }).then(r => r.json());
      if (j.ok) {
        if (asn !== undefined) {
          document.querySelectorAll(`[data-asn="${asn}"]`).forEach(row => row.remove());
        } else {
          entries.forEach(entry => document.querySelector(`.banned-entry-row[data-entry="${CSS.escape(entry)}"]`)?.remove());
          document.querySelectorAll('#tab-banned .banned-as-row').forEach(ar => {
            if (!document.querySelector(`.banned-entry-row[data-asn="${ar.dataset.asn}"]`)) ar.remove();
          });
        }
        const rem = document.querySelectorAll('#tab-banned .banned-entry-row').length;
        document.getElementById('banned-tab-badge').textContent = rem;
      } else {
        btn.disabled = false; btn.textContent = asn !== undefined ? '🔓 AS' : '🔓';
        alert('Erreur : ' + (j.error || 'voir console'));
      }
    } catch { btn.disabled = false; btn.textContent = asn !== undefined ? '🔓 AS' : '🔓'; }
  }

  /* ── Utilitaires ── */
  function escHtml(s) {
    return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
  }
  function trimUrl(u) {
    try { const p = new URL(u); return p.hostname + (p.pathname !== '/' ? p.pathname : ''); }
    catch { return u.length > 40 ? u.slice(0,40)+'…' : u; }
  }
})();
</script>
{% endblock %}
