{% extends "base.html" %}
{% block title %}{{ t(request, 'page.servers') }} — MoE Admin{% endblock %}

{% block content %}
<div class="d-flex align-items-center justify-content-between mb-3">
  <h5 class="mb-0 fw-bold"><i class="bi bi-hdd-stack me-2 text-primary"></i>{{ t(request, 'page.servers') }}</h5>
  <div class="d-flex gap-2">
    <button class="btn btn-sm btn-outline-primary" onclick="loadAll()">
      <i class="bi bi-arrow-clockwise me-1"></i>{{ t(request, 'btn.refresh') }}
    </button>
    <button class="btn btn-sm btn-outline-success" id="gen-dashboard-btn" onclick="generateDashboard()">
      <i class="bi bi-graph-up-arrow me-1"></i>{{ t(request, 'btn.generate_dashboard') }}
    </button>
  </div>
</div>

<div class="row g-3" id="server-cards">
  {% for srv in inference_servers %}
  <div class="col-12 col-md-6 col-xl-4" id="srv-card-{{ loop.index0 }}">
    <div class="card h-100">
      <div class="card-header d-flex align-items-center justify-content-between">
        <span class="fw-bold"><i class="bi bi-cpu me-2 text-primary"></i>{{ srv.name }}</span>
        <span class="badge bg-secondary" id="srv-badge-{{ loop.index0 }}" data-server-name="{{ srv.name }}">–</span>
      </div>
      <div class="card-body">
        <div class="text-muted small font-monospace mb-1">{{ srv.url }}</div>
        <div class="mb-2 d-flex gap-2 align-items-center">
          {% if srv.get('api_type','ollama') == 'openai' %}
          <span class="badge bg-primary">OpenAI</span>
          {% else %}
          <span class="badge bg-secondary">Ollama</span>
          {% endif %}
          {% set tok = srv.get('token','') %}
          {% if tok and tok != 'ollama' %}
          <span class="badge bg-warning text-dark font-monospace" style="font-size:.7rem">{{ tok[:4] }}****</span>
          {% endif %}
        </div>
        <div class="d-flex gap-3 mb-3">
          <div class="text-center">
            <div class="fs-4 fw-bold" id="srv-latency-{{ loop.index0 }}" data-server-name="{{ srv.name }}">–</div>
            <div class="text-muted" style="font-size:.75rem">{{ t(request, 'table.latency') }}</div>
          </div>
          <div class="text-center">
            <div class="fs-4 fw-bold" id="srv-models-count-{{ loop.index0 }}" data-server-name="{{ srv.name }}">–</div>
            <div class="text-muted" style="font-size:.75rem">{{ t(request, 'lbl.models') }}</div>
          </div>
          <div class="text-center">
            <div class="fs-4 fw-bold">{{ srv.gpu_count }}</div>
            <div class="text-muted" style="font-size:.75rem">{{ t(request, 'table.gpus') }}</div>
          </div>
          <div class="text-center">
            <div class="fs-4 fw-bold">{{ srv.get('cost_factor', 1.0) }}</div>
            <div class="text-muted" style="font-size:.75rem">{{ t(request, 'table.factor') }}</div>
          </div>
        </div>
        <div class="form-check form-switch mb-1">
          <input class="form-check-input" type="checkbox" role="switch"
                 id="srv-active-{{ loop.index0 }}"
                 data-server="{{ srv.name }}"
                 {% if srv.get('enabled', True) %}checked{% endif %}
                 onchange="toggleActive(this)">
          <label class="form-check-label small fw-semibold" for="srv-active-{{ loop.index0 }}">
            {{ t(request, 'lbl.node_active') }}
          </label>
        </div>
        <div class="form-check form-switch mb-1">
          <input class="form-check-input" type="checkbox" role="switch"
                 id="srv-onto-{{ loop.index0 }}"
                 data-server="{{ srv.name }}"
                 data-idx="{{ loop.index0 }}"
                 {% if srv.get('ontology_enabled') %}checked{% endif %}
                 onchange="toggleOntology(this)">
          <label class="form-check-label small" for="srv-onto-{{ loop.index0 }}">
            {{ t(request, 'lbl.ontology_cronjob') }}
          </label>
          <span id="srv-onto-status-{{ loop.index0 }}" class="ms-2 small text-muted"></span>
        </div>
        <div class="form-check form-switch mb-1">
          <input class="form-check-input" type="checkbox" role="switch" checked
                 id="srv-floating-{{ loop.index0 }}"
                 data-server="{{ srv.name }}"
                 onchange="toggleFloating(this)">
          <label class="form-check-label small" for="srv-floating-{{ loop.index0 }}">
            {{ t(request, 'lbl.floating_routing') }}
          </label>
        </div>
        <div class="form-check form-switch mb-2">
          <input class="form-check-input bg-danger border-danger" type="checkbox" role="switch"
                 id="srv-blocked-{{ loop.index0 }}"
                 data-server="{{ srv.name }}"
                 onchange="toggleBlock(this)">
          <label class="form-check-label small text-danger" for="srv-blocked-{{ loop.index0 }}">
            {{ t(request, 'lbl.server_blocked') }}
          </label>
        </div>
        <button class="btn btn-sm btn-outline-secondary w-100"
                onclick="toggleModels({{ loop.index0 }}, '{{ srv.name }}')">
          <i class="bi bi-list-ul me-1"></i>{{ t(request, 'lbl.models') }}
        </button>
        <div id="srv-models-{{ loop.index0 }}" class="mt-2" style="display:none">
          <div class="text-center text-muted small py-2" id="srv-models-loading-{{ loop.index0 }}">
            <span class="spinner-border spinner-border-sm me-1"></span>{{ t(request, 'lbl.loading') }}
          </div>
          <div class="table-responsive" style="max-height:280px;overflow-y:auto">
            <table class="table table-sm mb-0" id="srv-models-table-{{ loop.index0 }}" style="display:none">
              <thead class="sticky-top bg-white">
                <tr>
                  <th>{{ t(request, 'table.model') }}</th>
                  <th style="width:65px" class="text-end">GB</th>
                  <th style="width:80px">{{ t(request, 'table.quant') }}</th>
                  <th style="width:70px" class="text-end" title="Context window in tokens">Ctx</th>
                </tr>
              </thead>
              <tbody></tbody>
            </table>
          </div>
        </div>
      </div>
    </div>
  </div>
  {% endfor %}
</div>
{% endblock %}

{% block scripts %}
<script>
const _modelsLoaded = {};
const _modelsVisible = {};

const _tservers = {
  error:            {{ t(request, 'js.load_error') | tojson }},
  confirmBlock:     {{ t(request, 'js.confirm_block_server') | tojson }},
  floatingFail:     {{ t(request, 'js.load_error') | tojson }},
  blockFail:        {{ t(request, 'js.load_error') | tojson }},
  dashboardUpdated: {{ t(request, 'js.dashboard_updated') | tojson }},
};

async function loadAll() {
  // Reset all badges to loading state
  document.querySelectorAll('[data-server-name][id^="srv-badge-"]').forEach(b => {
    b.className = 'badge bg-secondary'; b.textContent = '...';
  });

  let data;
  try {
    const r = await fetch('/api/servers/health');
    if (!r.ok) throw new Error(`HTTP ${r.status}`);
    data = await r.json();
  } catch(e) {
    document.querySelectorAll('[data-server-name][id^="srv-badge-"]').forEach(b => {
      b.className = 'badge bg-danger'; b.textContent = _tservers.error;
    });
    return;
  }

  // Match by server name (robust against ordering differences)
  data.forEach(srv => {
    const name  = srv.name;
    const badge = document.querySelector(`[id^="srv-badge-"][data-server-name="${CSS.escape(name)}"]`);
    const lat   = document.querySelector(`[id^="srv-latency-"][data-server-name="${CSS.escape(name)}"]`);
    const mCnt  = document.querySelector(`[id^="srv-models-count-"][data-server-name="${CSS.escape(name)}"]`);
    if (!badge) return;
    if (srv.ok) {
      badge.className   = 'badge bg-success';
      badge.textContent = 'online';
      if (lat)  lat.textContent  = `${srv.latency_ms} ms`;
      if (mCnt) mCnt.textContent = srv.models_count;
    } else {
      badge.className   = 'badge bg-danger';
      badge.textContent = 'offline';
      if (lat)  lat.textContent  = '–';
      if (mCnt) mCnt.textContent = '–';
    }
  });
}

async function toggleModels(idx, serverName) {
  const container = document.getElementById(`srv-models-${idx}`);
  const isVisible = _modelsVisible[idx];
  _modelsVisible[idx] = !isVisible;
  container.style.display = isVisible ? 'none' : 'block';
  if (!isVisible && !_modelsLoaded[idx]) {
    await loadModels(idx, serverName);
  }
}

async function loadModels(idx, serverName) {
  const loading = document.getElementById(`srv-models-loading-${idx}`);
  const table   = document.getElementById(`srv-models-table-${idx}`);
  try {
    const models = await fetch(`/api/servers/models?server=${encodeURIComponent(serverName)}`).then(r => {
      if (!r.ok) throw new Error(r.status);
      return r.json();
    });
    if (loading) loading.style.display = 'none';
    if (table) {
      const tbody = table.querySelector('tbody');
      tbody.innerHTML = '';
      models.forEach(m => {
        const tr = document.createElement('tr');
        const ctxFmt = m.context_window ? (m.context_window >= 1000 ? `${Math.round(m.context_window/1024)}K` : String(m.context_window)) : '–';
        tr.innerHTML = `<td class="font-monospace" style="font-size:.82rem">${escHtml(m.name)}</td>
                        <td class="text-end" style="font-size:.82rem">${m.size_gb}</td>
                        <td style="font-size:.78rem">${escHtml(m.quantization || '–')}</td>
                        <td class="text-end text-muted" style="font-size:.78rem" title="${m.context_window ? m.context_window + ' tokens' : ''}">${ctxFmt}</td>`;
        tbody.appendChild(tr);
      });
      table.style.display = '';
    }
    _modelsLoaded[idx] = true;
  } catch (e) {
    if (loading) { loading.innerHTML = `<span class="text-danger">${_tservers.error}: ${e.message}</span>`; }
  }
}

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

const _ontoPolls = {};
async function toggleOntology(input) {
  const name = input.dataset.server;
  const idx  = input.dataset.idx;
  const statusEl = document.getElementById(`srv-onto-status-${idx}`);
  statusEl.textContent = input.checked ? 'queued…' : '';
  try {
    const r = await fetch(`/api/servers/${encodeURIComponent(name)}/ontology-toggle`, {
      method: 'POST',
      headers: {'Content-Type':'application/json'},
      body: JSON.stringify({enabled: input.checked}),
    });
    if (!r.ok) throw new Error(`HTTP ${r.status}`);
  } catch (e) {
    statusEl.textContent = _tservers.error + ': ' + e.message;
    statusEl.className = 'ms-2 small text-danger';
    input.checked = !input.checked;
    return;
  }
  if (input.checked) {
    clearInterval(_ontoPolls[idx]);
    _ontoPolls[idx] = setInterval(() => pollOntologyStatus(name, idx, input), 2000);
    pollOntologyStatus(name, idx, input);
  } else {
    clearInterval(_ontoPolls[idx]);
    statusEl.textContent = '';
    statusEl.className = 'ms-2 small text-muted';
  }
}

async function pollOntologyStatus(name, idx, input) {
  const statusEl = document.getElementById(`srv-onto-status-${idx}`);
  try {
    const d = await fetch(`/api/servers/${encodeURIComponent(name)}/ontology-status`).then(r => r.json());
    const s = d.status || 'unknown';
    statusEl.textContent = `${s}${d.message ? ': ' + d.message : ''}`;
    statusEl.className = 'ms-2 small ' + (
      s === 'ready' ? 'text-success' :
      s === 'failed' ? 'text-danger' :
      s === 'pulling' ? 'text-warning' :
      'text-muted'
    );
    if (s === 'ready' || s === 'failed') {
      clearInterval(_ontoPolls[idx]);
    }
  } catch (e) {
    statusEl.textContent = 'poll error';
    statusEl.className = 'ms-2 small text-danger';
  }
}

async function toggleActive(input) {
  const name = input.dataset.server;
  const active = input.checked;
  const card = input.closest('.card');
  if (card) card.style.borderColor = active ? '' : '#fd7e14';
  try {
    const r = await fetch(`/api/servers/${encodeURIComponent(name)}/active-toggle`, {
      method: 'POST', headers: {'Content-Type':'application/json'},
      body: JSON.stringify({active}),
    });
    if (!r.ok) throw new Error(`HTTP ${r.status}`);
  } catch (e) {
    alert('Error: ' + e.message);
    input.checked = !input.checked;
    if (card) card.style.borderColor = '';
  }
}

async function toggleFloating(input) {
  const name = input.dataset.server;
  const enabled = input.checked;
  try {
    const r = await fetch(`/api/servers/${encodeURIComponent(name)}/floating-toggle`, {
      method: 'POST', headers: {'Content-Type':'application/json'},
      body: JSON.stringify({enabled}),
    });
    if (!r.ok) throw new Error(`HTTP ${r.status}`);
  } catch (e) {
    alert(_tservers.floatingFail + ': ' + e.message);
    input.checked = !input.checked;
  }
}

async function toggleBlock(input) {
  const name = input.dataset.server;
  const blocked = input.checked;
  if (blocked && !confirm(`{{ t(request, 'js.confirm_block_server') }}`.replace('{name}', name))) {
    input.checked = false;
    return;
  }
  // Visual feedback: card border
  const card = input.closest('.card');
  if (card) card.style.borderColor = blocked ? '#dc3545' : '';
  try {
    const r = await fetch(`/api/servers/${encodeURIComponent(name)}/block-toggle`, {
      method: 'POST', headers: {'Content-Type':'application/json'},
      body: JSON.stringify({blocked}),
    });
    if (!r.ok) throw new Error(`HTTP ${r.status}`);
  } catch (e) {
    alert(_tservers.blockFail + ': ' + e.message);
    input.checked = !input.checked;
    if (card) card.style.borderColor = '';
  }
}

async function loadRoutingState() {
  try {
    const d = await fetch('/api/servers/routing-state').then(r => r.json());
    // Sync floating toggles
    document.querySelectorAll('input[id^="srv-floating-"]').forEach(inp => {
      const name = inp.dataset.server;
      inp.checked = !(d.floating_disabled || []).includes(name);
    });
    // Sync block toggles + card border
    document.querySelectorAll('input[id^="srv-blocked-"]').forEach(inp => {
      const name = inp.dataset.server;
      const isBlocked = (d.blocked || []).includes(name);
      inp.checked = isBlocked;
      const card = inp.closest('.card');
      if (card) card.style.borderColor = isBlocked ? '#dc3545' : '';
    });
  } catch (e) { /* ignore */ }
}

async function generateDashboard() {
  const btn = document.getElementById('gen-dashboard-btn');
  const orig = btn.innerHTML;
  btn.disabled = true;
  btn.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>…';
  try {
    const res = await fetch('/api/dashboards/regenerate', {method:'POST'}).then(r => {
      if (!r.ok) throw new Error(`HTTP ${r.status}`);
      return r.json();
    });
    const msg = `${_tservers.dashboardUpdated}\nProbed: ${res.probed.length}\nPrometheus reloaded: ${res.prometheus_reloaded}\n${res.prometheus_error ? 'Warning: ' + res.prometheus_error : ''}`;
    alert(msg);
    if (res.dashboard_url) window.open(res.dashboard_url, '_blank');
  } catch (e) {
    alert(_tservers.error + ': ' + e.message);
  } finally {
    btn.disabled = false;
    btn.innerHTML = orig;
  }
}

document.addEventListener('DOMContentLoaded', () => {
  loadAll();
  loadRoutingState();
  // Mark inactive nodes with orange border on initial load
  document.querySelectorAll('input[id^="srv-active-"]').forEach(inp => {
    if (!inp.checked) {
      const card = inp.closest('.card');
      if (card) card.style.borderColor = '#fd7e14';
    }
  });
  // On page load, start polling for any server that has ontology enabled
  document.querySelectorAll('input[id^="srv-onto-"]:checked').forEach(inp => {
    const name = inp.dataset.server, idx = inp.dataset.idx;
    pollOntologyStatus(name, idx, inp);
  });
});
</script>
{% endblock %}
