"""Global Settings Page — browser UI for automation config at :4549.

Admin-only page for viewing and editing:
- Suggestion frequency per agent
- Background loop intervals and toggles
- Access tier summary
- Agent model tier assignments

Reads/writes config/automation.yaml.
"""

from __future__ import annotations

import json
import logging
import threading
from http.server import BaseHTTPRequestHandler, HTTPServer
from pathlib import Path

import yaml

logger = logging.getLogger(__name__)

_PROJECT_ROOT = Path(__file__).resolve().parents[3]


def _config_dir() -> Path:
    from vastian.storage.data_root import get_config_dir

    return get_config_dir(_PROJECT_ROOT)


def _load_automation() -> dict:
    path = _config_dir() / "automation.yaml"
    if path.exists():
        return yaml.safe_load(path.read_text(encoding="utf-8")) or {}
    return {}


def _save_automation(data: dict) -> None:
    text = "# Automation Settings — managed by the Settings page\n\n"
    text += yaml.dump(data, default_flow_style=False, allow_unicode=True, sort_keys=False)
    path = _config_dir() / "automation.yaml"
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(text, encoding="utf-8")


def _load_access_tiers() -> dict:
    path = _config_dir() / "access-tiers.yaml"
    if path.exists():
        return yaml.safe_load(path.read_text(encoding="utf-8")) or {}
    return {}


def _load_cloud_worker() -> dict:
    path = _config_dir().parent / "local" / "cloud_worker.yaml"
    if path.exists():
        try:
            return yaml.safe_load(path.read_text(encoding="utf-8")) or {}
        except Exception:
            pass
    return {"url": "https://engine.vastianconnect.com"}


def _save_cloud_worker(data: dict) -> None:
    path = _config_dir().parent / "local" / "cloud_worker.yaml"
    path.parent.mkdir(parents=True, exist_ok=True)
    text = "# Cloud worker URL — for Zo deployment control\n\n"
    text += yaml.dump(data, default_flow_style=False, allow_unicode=True, sort_keys=False)
    path.write_text(text, encoding="utf-8")


def _save_access_tiers_agent_models(updates: dict[str, dict[str, str]]) -> None:
    """Merge agent_models updates into access-tiers.yaml. updates: {tier_name: {agent_id: model_tier}}."""
    cfg = _load_access_tiers()
    tiers = cfg.get("tiers", {})
    for tier_name, agent_models in updates.items():
        if tier_name in tiers:
            existing = tiers[tier_name].get("agent_models", {})
            existing.update(agent_models)
            tiers[tier_name]["agent_models"] = existing
    cfg["tiers"] = tiers
    path = _config_dir() / "access-tiers.yaml"
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(
        yaml.dump(cfg, default_flow_style=False, allow_unicode=True, sort_keys=False),
        encoding="utf-8",
    )
    try:
        from vastian.access_tiers import reload_config

        reload_config()
    except Exception:
        pass


SETTINGS_HTML = r"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Vastian Settings</title>
<style>
  * { margin: 0; padding: 0; box-sizing: border-box; }
  body { font-family: 'Segoe UI', system-ui, sans-serif; background: #0d1117; color: #c9d1d9; }
  .top-bar {
    background: #161b22; border-bottom: 1px solid #30363d;
    padding: 16px 24px; position: sticky; top: 0; z-index: 100;
  }
  .top-bar h1 { color: #58a6ff; font-size: 1.3em; }
  .top-bar .sub { color: #8b949e; font-size: 0.82em; }
  .main { max-width: 900px; margin: 0 auto; padding: 20px 24px; }
  h2 { color: #58a6ff; font-size: 1.05em; margin: 24px 0 12px; border-bottom: 1px solid #21262d; padding-bottom: 6px; }
  .section { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 16px; margin-bottom: 16px; }
  .row {
    display: flex; justify-content: space-between; align-items: center;
    padding: 8px 0; border-bottom: 1px solid #21262d; font-size: 0.88em;
  }
  .row:last-child { border-bottom: none; }
  .row label { color: #c9d1d9; font-weight: 500; min-width: 140px; }
  .row .hint { color: #6e7681; font-size: 0.8em; margin-left: 8px; }
  .row input[type=number] {
    background: #0d1117; color: #c9d1d9; border: 1px solid #30363d;
    border-radius: 4px; padding: 4px 8px; width: 100px; font-size: 0.88em; font-family: inherit;
    text-align: right;
  }
  .row select {
    background: #0d1117; color: #c9d1d9; border: 1px solid #30363d;
    border-radius: 4px; padding: 4px 8px; font-size: 0.88em; font-family: inherit;
  }
  .row .toggle { display: flex; align-items: center; gap: 8px; }
  .toggle input[type=checkbox] { width: 18px; height: 18px; accent-color: #238636; }
  .save-bar {
    position: sticky; bottom: 0; background: #161b22; border-top: 1px solid #30363d;
    padding: 12px 24px; display: flex; justify-content: flex-end; gap: 12px;
  }
  .btn {
    border: none; border-radius: 6px; padding: 8px 20px; font-size: 0.88em;
    cursor: pointer; font-family: inherit; font-weight: 600;
  }
  .btn-save { background: #238636; color: #fff; }
  .btn-save:hover { background: #2ea043; }
  .btn-reset { background: #30363d; color: #8b949e; }
  .btn-reset:hover { background: #484f58; color: #c9d1d9; }
  .toast {
    position: fixed; bottom: 60px; left: 50%; transform: translateX(-50%);
    background: #238636; color: #fff; padding: 8px 20px; border-radius: 8px;
    font-size: 0.85em; opacity: 0; transition: opacity 0.3s; z-index: 3000; pointer-events: none;
  }
  .toast.show { opacity: 1; }
  .tier-row { display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 8px; }
  .tier-card {
    background: #0d1117; border: 1px solid #21262d; border-radius: 6px;
    padding: 10px 14px; flex: 1; min-width: 180px; font-size: 0.82em;
  }
  .tier-card .tier-name { color: #58a6ff; font-weight: 600; margin-bottom: 4px; }
  .tier-card .tier-detail { color: #8b949e; }
  .loop-row .loop-controls { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
  .loop-row .loop-controls select { width: 80px; }
</style>
</head>
<body>

<div class="top-bar">
  <h1>Global Settings</h1>
  <div class="sub">Automation frequency, background loops, access tiers</div>
</div>

<div class="main">
  <h2 data-section="api-keys" id="api-keys">API Keys</h2>
  <div class="section" id="apiKeysSection">Loading...</div>

  <h2 data-section="mcp-key" id="mcp-key">MCP API Key &amp; API Docs</h2>
  <div class="section" id="mcpKeySection">Loading...</div>

  <h2 data-section="mercury" id="mercury">Mercury Banking</h2>
  <div class="section" id="mercurySection">Loading...</div>
  <p class="hint" style="margin-top:-8px;margin-bottom:16px">Up to 10 Mercury bank accounts. Connect other bank accounts coming soon.</p>

  <h2>Data Location</h2>
  <div class="section" id="dataRootSection">Loading...</div>

  <h2>Storage Provider</h2>
  <div class="section" id="storageSection">Loading...</div>

  <h2>Suggestion Frequency</h2>
  <div class="section" id="freqSection">Loading...</div>

  <h2>Background Loops</h2>
  <div class="section" id="loopSection">Loading...</div>

  <h2>Disabled Agents</h2>
  <div class="section" id="disabledSection">Loading...</div>

  <h2>Zo Polling</h2>
  <div class="section" id="zoSection">Loading...</div>

  <h2>Cloud Worker</h2>
  <div class="section" id="cloudWorkerSection">Loading...</div>

  <h2>Access Tier Summary</h2>
  <div class="section" id="tierSection">Loading...</div>

  <h2>Agent Model Tiers <span id="modelAdminBadge" style="display:none;color:#a371f7;font-size:0.8em">(Admin)</span></h2>
  <div class="section" id="modelSection">Loading...</div>

  <h2>Admin: Tier Simulation</h2>
  <div class="section" id="tierSimSection">Loading...</div>

  <h2>Token Usage</h2>
  <div class="section" id="tokenUsageSection">Loading...</div>
</div>

<div class="save-bar">
  <button class="btn btn-reset" onclick="loadSettings()">Reset</button>
  <button class="btn btn-save" onclick="saveSettings()">Save Changes</button>
</div>

<div class="toast" id="toast"></div>

<script>
let settings = {};

function showToast(msg) {
  const t = document.getElementById('toast');
  t.textContent = msg; t.classList.add('show');
  setTimeout(() => t.classList.remove('show'), 2000);
}

function esc(s) { const d=document.createElement('div'); d.textContent=s||''; return d.innerHTML; }

function minuteLabel(secs) {
  if (secs < 60) return secs + 's';
  if (secs < 3600) return Math.round(secs/60) + ' min';
  return Math.round(secs/3600*10)/10 + ' hr';
}

async function loadSettings() {
  try {
    const resp = await fetch('/api/settings');
    settings = await resp.json();
    renderApiKeys();
    renderMcpKey();
    renderMercury();
    renderDataRoot();
    renderStorageProvider();
    renderFreq();
    renderLoops();
    renderDisabled();
    renderZoPolling();
    renderCloudWorker();
    renderTiers();
    renderModels();
    renderTierSimulation();
    renderTokenUsage();
  } catch(e) { showToast('Error loading settings'); }
}

async function renderApiKeys() {
  const el = document.getElementById('apiKeysSection');
  try {
    const resp = await fetch('/api/settings/api-keys');
    const data = await resp.json();
    if (data.error) { el.innerHTML = `<div style="color:#f85149">${esc(data.error)}</div>`; return; }
    let html = '';
    html += `<div class="row"><label>OpenAI API Key</label><div style="display:flex;gap:8px;align-items:center;flex:1"><input type="password" id="akOpenai" value="${esc(data.openai_api_key || '')}" placeholder="sk-..." style="flex:1;min-width:180px;background:#0d1117;color:#c9d1d9;border:1px solid #30363d;border-radius:4px;padding:6px 10px;font-size:0.85em"><span style="color:${data.has_openai ? '#3fb950' : '#8b949e'};font-size:0.8em">${data.has_openai ? 'Set' : 'Not set'}</span></div></div>`;
    html += `<div class="row"><label>Anthropic API Key</label><div style="display:flex;gap:8px;align-items:center;flex:1"><input type="password" id="akAnthropic" value="${esc(data.anthropic_api_key || '')}" placeholder="sk-ant-..." style="flex:1;min-width:180px;background:#0d1117;color:#c9d1d9;border:1px solid #30363d;border-radius:4px;padding:6px 10px;font-size:0.85em"><span style="color:${data.has_anthropic ? '#3fb950' : '#8b949e'};font-size:0.8em">${data.has_anthropic ? 'Set' : 'Not set'}</span></div></div>`;
    html += `<div class="row"><label>Ollama Base URL</label><div style="display:flex;gap:8px;align-items:center;flex:1"><input type="text" id="akOllamaUrl" value="${esc(data.ollama_base_url || '')}" placeholder="http://localhost:11434" style="flex:1;min-width:180px;background:#0d1117;color:#c9d1d9;border:1px solid #30363d;border-radius:4px;padding:6px 10px;font-size:0.85em"></div></div>`;
    html += `<div class="row"><label>Ollama Model</label><div style="display:flex;gap:8px;align-items:center;flex:1"><input type="text" id="akOllamaModel" value="${esc(data.ollama_model || '')}" placeholder="llama3" style="flex:1;min-width:180px;background:#0d1117;color:#c9d1d9;border:1px solid #30363d;border-radius:4px;padding:6px 10px;font-size:0.85em"></div></div>`;
    html += `<div class="row"><label>Pocket API Key</label><div style="display:flex;gap:8px;align-items:center;flex:1"><input type="password" id="akPocket" value="${esc(data.pocket_api_key || '')}" placeholder="Pocket consumer key..." style="flex:1;min-width:180px;background:#0d1117;color:#c9d1d9;border:1px solid #30363d;border-radius:4px;padding:6px 10px;font-size:0.85em"><span style="color:${data.has_pocket ? '#3fb950' : '#8b949e'};font-size:0.8em">${data.has_pocket ? 'Set' : 'Not set'}</span></div></div>`;
    html += `<div class="row"><button class="btn btn-save" onclick="saveApiKeys()">Save API Keys</button><span class="hint">Restart required after changes to take effect.</span></div>`;
    el.innerHTML = html;
  } catch(e) {
    el.innerHTML = '<div style="color:#f85149">Could not load API keys</div>';
  }
}

async function renderMcpKey() {
  const el = document.getElementById('mcpKeySection');
  try {
    const resp = await fetch('/api/settings/mcp-key');
    const data = await resp.json();
    if (data.error) { el.innerHTML = `<div style="color:#f85149">${esc(data.error)}</div>`; return; }
    let html = '';
    html += '<div class="row"><label>MCP API Key</label><div style="display:flex;gap:8px;align-items:center;flex:1">';
    if (data.has_key) {
      html += `<span style="color:#3fb950;font-size:0.85em">Set (${esc(data.masked_key || '***')})</span>`;
    } else {
      html += '<span style="color:#8b949e;font-size:0.85em">Not set</span>';
    }
    html += '<button class="btn btn-save" onclick="generateMcpKey()">Generate Key</button></div></div>';
    html += '<div class="row"><span class="hint">Use this key with external MCP clients (e.g. Cursor). Header: Authorization: Bearer &lt;key&gt;</span></div>';
    html += '<div class="row" style="margin-top:12px"><a href="/api/docs" target="_blank" style="color:#58a6ff;font-size:0.9em">&#x1F4DD; OpenAPI Docs (Swagger)</a>';
    html += ' &middot; <a href="/api/redoc" target="_blank" style="color:#58a6ff;font-size:0.9em">ReDoc</a></div>';
    el.innerHTML = html;
  } catch(e) {
    el.innerHTML = '<div style="color:#f85149">Could not load MCP key</div>';
  }
}

async function generateMcpKey() {
  try {
    const resp = await fetch('/api/settings/mcp-key/generate', { method: 'POST' });
    const data = await resp.json();
    if (data.ok) {
      showToast('Key generated! Copy it now — it won\'t be shown again.');
      const key = data.key || '';
      if (key && navigator.clipboard) {
        navigator.clipboard.writeText(key).then(() => showToast('Key copied to clipboard'));
      }
      renderMcpKey();
    } else {
      showToast('Error: ' + (data.error || 'Failed'));
    }
  } catch(e) { showToast('Error: ' + e.message); }
}

async function saveApiKeys() {
  const body = {};
  const oai = document.getElementById('akOpenai');
  const ant = document.getElementById('akAnthropic');
  const olUrl = document.getElementById('akOllamaUrl');
  const olModel = document.getElementById('akOllamaModel');
  const pkt = document.getElementById('akPocket');
  if (oai && oai.value) body.openai_api_key = oai.value;
  if (ant && ant.value) body.anthropic_api_key = ant.value;
  if (olUrl && olUrl.value) body.ollama_base_url = olUrl.value;
  if (olModel && olModel.value) body.ollama_model = olModel.value;
  if (pkt && pkt.value) body.pocket_api_key = pkt.value;
  try {
    const resp = await fetch('/api/settings/api-keys', {
      method: 'POST',
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify(body)
    });
    const data = await resp.json();
    if (data.ok) {
      showToast('API keys saved' + (data.restart_required ? ' — restart required' : ''));
      renderApiKeys();
    } else {
      showToast('Error: ' + (data.error || 'Failed'));
    }
  } catch(e) { showToast('Error: ' + e.message); }
}

async function renderMercury() {
  const el = document.getElementById('mercurySection');
  try {
    const resp = await fetch('/api/settings/mercury');
    const data = await resp.json();
    if (data.error) { el.innerHTML = `<div style="color:#f85149">${esc(data.error)}</div>`; return; }
    const tokenPlaceholder = data.has_token ? '•••••••• (leave blank to keep)' : 'Mercury API token';
    let html = '';
    html += `<div class="row"><label>Mercury API Token</label><div style="display:flex;gap:8px;align-items:center;flex:1"><input type="password" id="mToken" value="" placeholder="${esc(tokenPlaceholder)}" style="flex:1;min-width:200px;background:#0d1117;color:#c9d1d9;border:1px solid #30363d;border-radius:4px;padding:6px 10px;font-size:0.85em"><span style="color:${data.has_token ? '#3fb950' : '#8b949e'};font-size:0.8em">${data.has_token ? 'Set' : 'Not set'}</span></div></div>`;
    html += '<div class="row"><label>Account 1 (primary, read-write)</label><div style="display:flex;gap:8px;flex-wrap:wrap;flex:1">';
    html += `<input type="text" id="mAccId1" value="${esc(data.mercury_account_id || '')}" placeholder="Account ID" style="min-width:140px;background:#0d1117;color:#c9d1d9;border:1px solid #30363d;border-radius:4px;padding:6px 10px;font-size:0.85em">`;
    html += `<input type="text" id="mAccName1" value="${esc(data.mercury_account_name || '')}" placeholder="Label (e.g. Operating)" style="min-width:120px;flex:1;background:#0d1117;color:#c9d1d9;border:1px solid #30363d;border-radius:4px;padding:6px 10px;font-size:0.85em"></div></div>`;
    for (let i = 2; i <= 10; i++) {
      html += `<div class="row"><label>Account ${i} (read-only)</label><div style="display:flex;gap:8px;flex-wrap:wrap;flex:1">`;
      html += `<input type="text" id="mRo${i}" value="${esc(data['mercury_ro_' + (i-1)] || '')}" placeholder="Account ID" style="min-width:140px;background:#0d1117;color:#c9d1d9;border:1px solid #30363d;border-radius:4px;padding:6px 10px;font-size:0.85em">`;
      html += `<input type="text" id="mRn${i}" value="${esc(data['mercury_rn_' + (i-1)] || '')}" placeholder="Label" style="min-width:120px;flex:1;background:#0d1117;color:#c9d1d9;border:1px solid #30363d;border-radius:4px;padding:6px 10px;font-size:0.85em"></div></div>`;
    }
    html += '<div class="row"><button class="btn btn-save" onclick="saveMercury()">Save Mercury</button><span class="hint">Restart Vastian to connect. Connect other bank accounts coming soon.</span></div>';
    el.innerHTML = html;
  } catch(e) {
    el.innerHTML = '<div style="color:#f85149">Could not load Mercury settings</div>';
  }
}

async function saveMercury() {
  const body = {
    mercury_account_id: (document.getElementById('mAccId1') || {}).value || '',
    mercury_account_name: (document.getElementById('mAccName1') || {}).value || '',
  };
  const tokenEl = document.getElementById('mToken');
  if (tokenEl && tokenEl.value && !tokenEl.value.startsWith('•')) body.mercury_api_token = tokenEl.value;
  for (let i = 2; i <= 10; i++) {
    body['mercury_ro_' + (i-1)] = (document.getElementById('mRo' + i) || {}).value || '';
    body['mercury_rn_' + (i-1)] = (document.getElementById('mRn' + i) || {}).value || '';
  }
  try {
    const resp = await fetch('/api/settings/mercury', {
      method: 'POST',
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify(body)
    });
    const data = await resp.json();
    if (data.ok) {
      showToast(data.message || 'Mercury settings saved. Restart required.');
      renderMercury();
    } else {
      showToast('Error: ' + (data.error || 'Failed'));
    }
  } catch(e) { showToast('Error: ' + e.message); }
}

async function renderDataRoot() {
  const el = document.getElementById('dataRootSection');
  try {
    const resp = await fetch('/api/settings/data-root');
    const data = await resp.json();
    const root = data.data_root || '(not set)';
    el.innerHTML = `<div class="row">
      <label>Current data root</label>
      <span style="font-size:0.85em;color:#c9d1d9;word-break:break-all">${esc(root)}</span>
    </div>
    <div class="row">
      <label>Change data root</label>
      <div style="display:flex;gap:8px;align-items:center;flex:1">
        <input type="text" id="newDataRoot" value="${esc(root)}" placeholder="e.g. C:/Users/You/Documents/myvastian-data" style="flex:1;min-width:200px;background:#0d1117;color:#c9d1d9;border:1px solid #30363d;border-radius:4px;padding:6px 10px;font-size:0.85em">
        <button class="btn btn-save" onclick="changeDataRoot()">Set</button>
      </div>
    </div>
    <div class="row"><span class="hint">Where docs, state, config, and saved_prompts live. Must be outside the repo. Restart required after change.</span></div>`;
  } catch(e) {
    el.innerHTML = '<div style="color:#f85149">Could not load data root</div>';
  }
}

async function changeDataRoot() {
  const input = document.getElementById('newDataRoot');
  const path = input ? input.value.trim() : '';
  if (!path) { showToast('Enter a path'); return; }
  try {
    const resp = await fetch('/api/settings/data-root', {
      method: 'POST',
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify({path})
    });
    const data = await resp.json();
    if (data.ok) {
      showToast(data.message || 'Data root updated. Restart required.');
      renderDataRoot();
    } else {
      showToast('Error: ' + (data.error || 'Failed'));
    }
  } catch(e) { showToast('Error: ' + e.message); }
}

async function renderStorageProvider() {
  const el = document.getElementById('storageSection');
  try {
    const resp = await fetch('/api/settings/storage');
    const data = await resp.json();
    const current = data.provider || 'local';
    const options = data.options || ['local', 'google_drive', 'dropbox', 'box', 'tresorit'];
    const labels = {local:'Local (Zo-resident)', google_drive:'Google Drive', dropbox:'Dropbox', box:'Box', tresorit:'Tresorit'};
    let opts = options.map(function(o) {
      return '<option value="' + esc(o) + '"' + (o === current ? ' selected' : '') + '>' + esc(labels[o] || o) + '</option>';
    }).join('');
    el.innerHTML = '<div class="row">' +
      '<label>Storage Provider</label>' +
      '<select id="storageProviderSelect" style="background:#0d1117;color:#c9d1d9;border:1px solid #30363d;border-radius:4px;padding:6px 10px;font-size:0.85em">' + opts + '</select>' +
      '</div>' +
      '<div class="row">' +
      '<label style="flex:1"><input type="checkbox" id="storageMigrate" style="margin-right:6px">Migrate existing data to new provider</label>' +
      '<button class="btn btn-save" onclick="changeStorageProvider()">Update</button>' +
      '</div>' +
      '<div class="row"><span class="hint">Choose where your documents, state, and config are stored. Migration copies everything to the new provider.</span></div>';
  } catch(e) {
    el.innerHTML = '<div style="color:#f85149">Could not load storage settings</div>';
  }
}

async function changeStorageProvider() {
  const sel = document.getElementById('storageProviderSelect');
  const mig = document.getElementById('storageMigrate');
  const provider = sel ? sel.value : '';
  const migrate = mig ? mig.checked : false;
  if (!provider) { showToast('Select a provider'); return; }
  try {
    const resp = await fetch('/api/settings/storage', {
      method: 'POST',
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify({provider, migrate})
    });
    const data = await resp.json();
    if (data.ok) {
      showToast(data.message || 'Storage provider updated.');
      renderStorageProvider();
    } else {
      showToast('Error: ' + (data.error || 'Failed'));
    }
  } catch(e) { showToast('Error: ' + e.message); }
}

async function renderTierSimulation() {
  var el = document.getElementById('tierSimSection');
  try {
    var resp = await fetch('/api/admin/tier');
    var data = await resp.json();
    var current = data.tier;
    var isOverride = data.override;
    var tiers = [{v:0,l:'Tier 0 — New User'},{v:1,l:'Tier 1 — Onboarded'},{v:2,l:'Tier 2 — Active'},{v:3,l:'Tier 3 — Power User'}];
    var opts = tiers.map(function(t) {
      return '<option value="' + t.v + '"' + (t.v === current ? ' selected' : '') + '>' + esc(t.l) + '</option>';
    }).join('');
    var html = '<div class="row">';
    html += '<label>Simulate Tier</label>';
    html += '<select id="tierSimSelect" style="background:#0d1117;color:#c9d1d9;border:1px solid #30363d;border-radius:4px;padding:6px 10px;font-size:0.85em">' + opts + '</select>';
    html += '<button class="btn btn-save" onclick="setTierSim()" style="margin-left:8px">Set</button>';
    if (isOverride) {
      html += '<button class="btn btn-reset" onclick="resetTierSim()" style="margin-left:4px">Reset</button>';
    }
    html += '</div>';
    if (isOverride) {
      html += '<div class="row"><span style="color:#a371f7;font-size:0.82em">Override active: Simulating Tier ' + current + '</span></div>';
    }
    html += '<div class="row"><span class="hint">Simulate any progressive unlock level to test the user experience. This only affects your session&rsquo;s sidebar and content gating.</span></div>';
    el.innerHTML = html;
  } catch(e) {
    el.innerHTML = '<div style="color:#f85149">Could not load tier simulation</div>';
  }
}

async function setTierSim() {
  var sel = document.getElementById('tierSimSelect');
  var tier = sel ? parseInt(sel.value) : -1;
  try {
    var resp = await fetch('/api/admin/set-tier', {
      method: 'POST',
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify({tier: tier})
    });
    var data = await resp.json();
    if (data.ok) {
      showToast(data.message || 'Tier set');
      renderTierSimulation();
    } else {
      showToast('Error: ' + (data.error || 'Failed'));
    }
  } catch(e) { showToast('Error: ' + e.message); }
}

async function resetTierSim() {
  try {
    var resp = await fetch('/api/admin/tier', { method: 'DELETE' });
    var data = await resp.json();
    if (data.ok) {
      showToast('Tier override cleared');
      renderTierSimulation();
    } else {
      showToast('Error: ' + (data.error || 'Failed'));
    }
  } catch(e) { showToast('Error: ' + e.message); }
}

async function renderTokenUsage() {
  var el = document.getElementById('tokenUsageSection');
  try {
    var resp = await fetch('/api/token-usage');
    var data = await resp.json();
    var usage = data.usage || [];
    if (!usage.length) {
      el.innerHTML = '<div style="color:var(--muted);font-size:0.85em">No token usage recorded yet.</div>';
      return;
    }
    var html = '<table style="width:100%;font-size:0.82em;border-collapse:collapse">';
    html += '<tr style="border-bottom:1px solid #30363d;color:#8b949e"><th style="text-align:left;padding:4px 8px">Agent</th><th style="text-align:left;padding:4px 8px">Provider</th><th style="text-align:left;padding:4px 8px">Model</th><th style="text-align:right;padding:4px 8px">Input</th><th style="text-align:right;padding:4px 8px">Output</th><th style="text-align:right;padding:4px 8px">Calls</th></tr>';
    var totalIn = 0, totalOut = 0, totalCalls = 0;
    usage.forEach(function(u) {
      html += '<tr style="border-bottom:1px solid #21262d">';
      html += '<td style="padding:4px 8px">' + esc(u.agent_id) + '</td>';
      html += '<td style="padding:4px 8px">' + esc(u.provider) + '</td>';
      html += '<td style="padding:4px 8px">' + esc(u.model) + '</td>';
      html += '<td style="padding:4px 8px;text-align:right">' + (u.input_tokens || 0).toLocaleString() + '</td>';
      html += '<td style="padding:4px 8px;text-align:right">' + (u.output_tokens || 0).toLocaleString() + '</td>';
      html += '<td style="padding:4px 8px;text-align:right">' + (u.calls || 0) + '</td>';
      html += '</tr>';
      totalIn += (u.input_tokens || 0);
      totalOut += (u.output_tokens || 0);
      totalCalls += (u.calls || 0);
    });
    html += '<tr style="border-top:2px solid #30363d;font-weight:700"><td style="padding:4px 8px" colspan="3">Total</td><td style="padding:4px 8px;text-align:right">' + totalIn.toLocaleString() + '</td><td style="padding:4px 8px;text-align:right">' + totalOut.toLocaleString() + '</td><td style="padding:4px 8px;text-align:right">' + totalCalls + '</td></tr>';
    html += '</table>';
    html += '<div class="row"><span class="hint">Token usage by agent. Useful for BYOK users to monitor spend.</span></div>';
    el.innerHTML = html;
  } catch(e) {
    el.innerHTML = '<div style="color:#f85149">Could not load token usage</div>';
  }
}

const FREQ_OPTIONS = [
  {label: 'Every 15 min', value: 900},
  {label: 'Every 30 min', value: 1800},
  {label: 'Every 1 hour', value: 3600},
  {label: 'Every 2 hours', value: 7200},
  {label: 'Every 4 hours', value: 14400},
  {label: 'Every 8 hours', value: 28800},
  {label: 'Every 12 hours', value: 43200},
  {label: 'Every 24 hours', value: 86400},
  {label: 'Disabled', value: 0},
];

const LOOP_INTERVAL_OPTIONS = [
  {label: '15 min', value: 900},
  {label: '30 min', value: 1800},
  {label: '1 hr', value: 3600},
  {label: '2 hr', value: 7200},
  {label: '3 hr', value: 10800},
  {label: '4 hr', value: 14400},
  {label: '6 hr', value: 21600},
  {label: '8 hr', value: 28800},
  {label: '12 hr', value: 43200},
  {label: '24 hr', value: 86400},
  {label: 'Off (0)', value: 0},
];

const MODEL_TIER_OPTIONS = ['default', 'cheap', 'mid', 'premium'];

function renderFreq() {
  const intervals = settings.suggestion_intervals || {};
  const el = document.getElementById('freqSection');
  el.innerHTML = Object.entries(intervals).map(([agent, secs]) => {
    const opts = FREQ_OPTIONS.map(o =>
      `<option value="${o.value}" ${Math.abs(o.value - secs) < 60 ? 'selected' : ''}>${o.label}</option>`
    ).join('');
    return `<div class="row">
      <label>${esc(agent)}</label>
      <select id="freq-${agent}">${opts}</select>
    </div>`;
  }).join('');
}

function renderLoops() {
  const loops = settings.background_loops || {};
  const el = document.getElementById('loopSection');
  el.innerHTML = Object.entries(loops).map(([name, cfg]) => {
    const intVal = cfg.interval ?? 3600;
    const intOpts = LOOP_INTERVAL_OPTIONS.map(o =>
      `<option value="${o.value}" ${Math.abs(o.value - intVal) < 60 ? 'selected' : ''}>${o.label}</option>`
    ).join('');
    const modelVal = cfg.model_tier || 'default';
    const modelOpts = MODEL_TIER_OPTIONS.map(t =>
      `<option value="${t}" ${t===modelVal?'selected':''}>${t}</option>`
    ).join('');
    const runAt = cfg.run_at || '';
    return `<div class="row loop-row">
      <label>${esc(name)}<span class="hint">${esc(cfg.agent || '')}</span></label>
      <div class="loop-controls">
        <input type="checkbox" id="loop-${name}" ${cfg.enabled ? 'checked' : ''} title="Enable">
        <select id="loop-int-${name}" title="Interval">${intOpts}</select>
        <select id="loop-model-${name}" title="Model tier">${modelOpts}</select>
        <input type="text" id="loop-runat-${name}" value="${esc(runAt)}" placeholder="HH:MM" maxlength="5" style="width:60px;background:#0d1117;color:#c9d1d9;border:1px solid #30363d;border-radius:4px;padding:4px 6px;font-size:0.8em" title="Optional run time (e.g. 08:00)">
      </div>
    </div>`;
  }).join('');
}

function renderDisabled() {
  const disabled = settings.disabled_agents || [];
  const el = document.getElementById('disabledSection');
  const val = Array.isArray(disabled) ? disabled.join(', ') : '';
  el.innerHTML = `<div class="row">
    <label>Agent IDs (comma-separated)</label>
    <input type="text" id="disabledAgents" value="${esc(val)}" placeholder="e.g. henry, dominic" style="flex:1;min-width:200px;background:#0d1117;color:#c9d1d9;border:1px solid #30363d;border-radius:4px;padding:6px 10px;font-size:0.88em">
  </div>
  <div class="row"><span class="hint">Agents in this list do not run in suggestion or background loops.</span></div>`;
}

function renderCloudWorker() {
  const cw = settings.cloud_worker || {};
  const url = (cw.url || 'https://engine.vastianconnect.com').replace(/\/$/, '');
  const el = document.getElementById('cloudWorkerSection');
  el.innerHTML = `<div class="row">
    <label>Cloud URL</label>
    <input type="text" id="cloudWorkerUrl" value="${esc(url)}" placeholder="https://engine.vastianconnect.com" style="flex:1;min-width:200px;background:#0d1117;color:#c9d1d9;border:1px solid #30363d;border-radius:4px;padding:6px 10px;font-size:0.88em">
  </div>
  <div class="row">
    <label>Status</label>
    <span id="cloudWorkerStatus">checking...</span>
  </div>
  <div class="row">
    <label>Controls</label>
    <div>
      <button class="btn btn-save" id="cloudWorkerStart" style="margin-right:8px" onclick="cloudWorkerControl('start')">Start</button>
      <button class="btn btn-reset" id="cloudWorkerStop" onclick="cloudWorkerControl('stop')">Stop</button>
    </div>
  </div>`;
  refreshCloudWorkerStatus();
  if (window._cloudWorkerRefresh) clearInterval(window._cloudWorkerRefresh);
  window._cloudWorkerRefresh = setInterval(refreshCloudWorkerStatus, 5000);
}

async function refreshCloudWorkerStatus() {
  const url = (document.getElementById('cloudWorkerUrl')?.value || '').trim().replace(/\/$/, '') || 'https://engine.vastianconnect.com';
  const statusEl = document.getElementById('cloudWorkerStatus');
  if (!statusEl) return;
  try {
    const [healthResp, statusResp] = await Promise.all([
      fetch(url + '/api/health', {mode: 'cors'}),
      fetch(url + '/api/worker/status', {mode: 'cors'})
    ]);
    const health = await healthResp.json();
    const status = await statusResp.json();
    const running = status.running;
    const last = status.last_poll || 'never';
    statusEl.textContent = running ? 'Running | last poll: ' + last : 'Stopped';
    statusEl.style.color = running ? '#3fb950' : '#8b949e';
  } catch (e) {
    statusEl.textContent = 'Unreachable: ' + (e.message || 'check URL');
    statusEl.style.color = '#f85149';
  }
}

async function cloudWorkerControl(action) {
  const url = (document.getElementById('cloudWorkerUrl')?.value || '').trim().replace(/\/$/, '') || 'https://engine.vastianconnect.com';
  try {
    await fetch(url + '/api/worker/control', {
      method: 'POST',
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify({action}),
      mode: 'cors'
    });
    setTimeout(refreshCloudWorkerStatus, 500);
  } catch (e) {
    showToast('Error: ' + (e.message || 'Failed'));
  }
}

function renderZoPolling() {
  const zo = settings.zo_polling || {};
  const el = document.getElementById('zoSection');
  const intVal = zo.interval ?? 300;
  const intOpts = LOOP_INTERVAL_OPTIONS.filter(o => o.value >= 60).map(o =>
    `<option value="${o.value}" ${Math.abs(o.value - intVal) < 60 ? 'selected' : ''}>${o.label}</option>`
  ).join('');
  const modelVal = zo.model_tier || 'default';
  const modelOpts = MODEL_TIER_OPTIONS.map(t =>
    `<option value="${t}" ${t===modelVal?'selected':''}>${t}</option>`
  ).join('');
  el.innerHTML = `<div class="row">
    <label>Interval</label>
    <select id="zo-interval">${intOpts}</select>
  </div>
  <div class="row">
    <label>Enabled</label>
    <input type="checkbox" id="zo-enabled" ${zo.enabled !== false ? 'checked' : ''}>
  </div>
  <div class="row">
    <label>Model tier</label>
    <select id="zo-model">${modelOpts}</select>
  </div>`;
}

function renderTiers() {
  const tiers = settings.tier_summary || {};
  const el = document.getElementById('tierSection');
  el.innerHTML = '<div class="tier-row">' + Object.entries(tiers).map(([name, info]) =>
    `<div class="tier-card">
      <div class="tier-name">${esc(name)} (${esc(info.label || '')})</div>
      <div class="tier-detail">Boards: ${(info.boards || []).join(', ')}</div>
      <div class="tier-detail">Disabled: ${(info.disabled_agents || []).join(', ') || 'none'}</div>
    </div>`
  ).join('') + '</div>';
}

const MODEL_TIERS = ['cheap', 'mid', 'premium'];

function renderModels() {
  const el = document.getElementById('modelSection');
  const badge = document.getElementById('modelAdminBadge');
  const byTier = settings.agent_models_by_tier || {};
  const isAdmin = settings.user_tier === 'admin';

  if (isAdmin && Object.keys(byTier).length) {
    badge.style.display = 'inline';
    let html = '';
    for (const [tierName, models] of Object.entries(byTier)) {
      html += `<div class="tier-block" style="margin-bottom:16px">
        <div class="tier-name" style="color:#58a6ff;font-weight:600;margin-bottom:8px;font-size:0.9em">${esc(tierName)}</div>
        <div class="tier-agents">` + Object.entries(models).map(([agent, tier]) => {
        const opts = MODEL_TIERS.map(t =>
          `<option value="${t}" ${t===tier?'selected':''}>${t}</option>`
        ).join('');
        return `<div class="row">
          <label>${esc(agent)}</label>
          <select id="model-${esc(tierName)}-${esc(agent)}">${opts}</select>
        </div>`;
      }).join('') + `</div></div>`;
    }
    el.innerHTML = html;
  } else {
    badge.style.display = 'none';
    const models = settings.agent_models || {};
    if (!Object.keys(models).length) { el.innerHTML = '<div style="color:#8b949e">No model data</div>'; return; }
    el.innerHTML = Object.entries(models).map(([agent, tier]) =>
      `<div class="row">
        <label>${esc(agent)}</label>
        <span style="color:${tier==='cheap'?'#8b949e':tier==='mid'?'#d29922':'#a371f7'}">${esc(tier)}</span>
      </div>`
    ).join('');
  }
}

async function saveSettings() {
  // Collect frequency changes
  const intervals = {};
  const freqInputs = document.querySelectorAll('select[id^="freq-"]');
  freqInputs.forEach(sel => {
    const agent = sel.id.replace('freq-', '');
    intervals[agent] = parseInt(sel.value) || 3600;
  });

  // Collect loop toggles, intervals, model_tier, run_at
  const loops = {};
  const loopNames = Object.keys(settings.background_loops || {});
  loopNames.forEach(name => {
    const orig = (settings.background_loops || {})[name] || {};
    const cb = document.getElementById('loop-' + name);
    const intSel = document.getElementById('loop-int-' + name);
    const modelSel = document.getElementById('loop-model-' + name);
    const runAtInp = document.getElementById('loop-runat-' + name);
    loops[name] = {
      ...orig,
      enabled: cb ? cb.checked : orig.enabled,
      interval: intSel ? parseInt(intSel.value) || 0 : orig.interval,
      model_tier: modelSel ? modelSel.value : (orig.model_tier || 'default'),
      run_at: runAtInp && runAtInp.value.trim() ? runAtInp.value.trim() : null,
    };
  });

  // Collect disabled agents
  const disabledEl = document.getElementById('disabledAgents');
  const disabled = disabledEl ? disabledEl.value.split(',').map(s => s.trim()).filter(Boolean) : (settings.disabled_agents || []);

  // Collect cloud_worker URL
  const cloudWorkerUrl = document.getElementById('cloudWorkerUrl');
  const cloud_worker = {
    url: cloudWorkerUrl ? cloudWorkerUrl.value.trim().replace(/\/$/, '') || 'https://engine.vastianconnect.com' : (settings.cloud_worker?.url || 'https://engine.vastianconnect.com'),
  };

  // Collect zo_polling
  const zoInterval = document.getElementById('zo-interval');
  const zoEnabled = document.getElementById('zo-enabled');
  const zoModel = document.getElementById('zo-model');
  const zo_polling = {
    interval: zoInterval ? parseInt(zoInterval.value) || 300 : (settings.zo_polling?.interval || 300),
    enabled: zoEnabled ? zoEnabled.checked : (settings.zo_polling?.enabled !== false),
    model_tier: zoModel ? zoModel.value : (settings.zo_polling?.model_tier || 'default'),
  };

  const payload = { suggestion_intervals: intervals, background_loops: loops, disabled_agents: disabled, zo_polling, cloud_worker };

  // Admin: collect agent model changes
  const byTier = settings.agent_models_by_tier || {};
  if (settings.user_tier === 'admin' && Object.keys(byTier).length) {
    const updates = {};
    for (const [tierName, models] of Object.entries(byTier)) {
      const tierUpdates = {};
      for (const agent of Object.keys(models)) {
        const sel = document.getElementById('model-' + tierName + '-' + agent);
        if (sel) tierUpdates[agent] = sel.value;
      }
      if (Object.keys(tierUpdates).length) updates[tierName] = tierUpdates;
    }
    if (Object.keys(updates).length) payload.agent_models_by_tier = updates;
  }

  try {
    const resp = await fetch('/api/settings', {
      method: 'POST',
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify(payload),
    });
    if (resp.status === 403) {
      showToast('Admin only');
      return;
    }
    const data = await resp.json();
    if (data.ok) {
      showToast('Settings saved! Restart CLI to apply.');
      loadSettings();
    } else {
      showToast('Error: ' + (data.message || 'Failed'));
    }
  } catch(e) { showToast('Error: ' + e.message); }
}

loadSettings();
</script>
</body>
</html>"""


class SettingsPage:
    """Global settings browser page at a configurable port."""

    def __init__(self, port: int = 4549, user_tier: str = "open") -> None:
        self.port = port
        self.user_tier = user_tier
        self._server: HTTPServer | None = None
        self._thread: threading.Thread | None = None

    def start(self) -> str:
        page = self

        class Handler(BaseHTTPRequestHandler):
            def do_GET(self) -> None:  # noqa: N802
                try:
                    if self.path == "/api/settings":
                        self._serve_json(page._get_settings())
                    else:
                        html = SETTINGS_HTML.encode("utf-8")
                        self.send_response(200)
                        self.send_header("Content-Type", "text/html; charset=utf-8")
                        self.send_header("Content-Length", str(len(html)))
                        self.send_header("Cache-Control", "no-store")
                        self.end_headers()
                        self.wfile.write(html)
                except (ConnectionAbortedError, ConnectionResetError, BrokenPipeError):
                    pass

            def do_POST(self) -> None:  # noqa: N802
                try:
                    content_len = int(self.headers.get("Content-Length", 0))
                    body = self.rfile.read(content_len).decode("utf-8") if content_len else "{}"
                    data = json.loads(body)
                    # Admin-only: agent model editing
                    if "agent_models_by_tier" in data and page.user_tier != "admin":
                        self.send_response(403)
                        self.send_header("Content-Type", "application/json")
                        self.end_headers()
                        self.wfile.write(
                            json.dumps({"ok": False, "message": "Admin only"}).encode("utf-8")
                        )
                        return
                    result = page._save_settings(data)
                    self._serve_json(result)
                except (ConnectionAbortedError, ConnectionResetError, BrokenPipeError):
                    pass

            def _serve_json(self, data: object) -> None:
                body = json.dumps(data, default=str).encode("utf-8")
                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Content-Length", str(len(body)))
                self.end_headers()
                self.wfile.write(body)

            def log_message(self, *args: object) -> None:
                pass

        class ReusableHTTPServer(HTTPServer):
            allow_reuse_address = True
            allow_reuse_port = True

        try:
            self._server = ReusableHTTPServer(("127.0.0.1", self.port), Handler)
        except OSError:
            self.port += 10
            self._server = ReusableHTTPServer(("127.0.0.1", self.port), Handler)

        self._thread = threading.Thread(
            target=self._server.serve_forever,
            daemon=True,
            name="settings-page",
        )
        self._thread.start()
        url = f"http://127.0.0.1:{self.port}"
        logger.info("Settings page started at %s", url)
        return url

    def stop(self) -> None:
        if self._server:
            self._server.shutdown()
            self._server = None

    def _get_settings(self) -> dict:
        """Return all settings for the UI."""
        automation = _load_automation()

        # Tier summary
        try:
            from vastian.access_tiers import VALID_TIERS, tier_summary

            tiers = {t: tier_summary(t) for t in VALID_TIERS}
        except Exception:
            tiers = {}

        # Agent models for current tier (read-only for non-admin)
        try:
            from vastian.access_tiers import _tier_config

            tc = _tier_config(self.user_tier)
            agent_models = tc.get("agent_models", {})
        except Exception:
            agent_models = {}

        # Agent models by tier (admin only, for editable grid)
        agent_models_by_tier: dict[str, dict[str, str]] = {}
        if self.user_tier == "admin":
            try:
                at_cfg = _load_access_tiers()
                for tier_name, tier_cfg in (at_cfg.get("tiers") or {}).items():
                    agent_models_by_tier[tier_name] = dict(tier_cfg.get("agent_models", {}))
            except Exception:
                pass

        cloud_worker = _load_cloud_worker()

        # Data root
        try:
            from vastian.storage.data_root import get_data_root

            data_root = str(get_data_root(_PROJECT_ROOT))
        except Exception:
            data_root = ""

        return {
            "suggestion_intervals": automation.get("suggestion_intervals", {}),
            "background_loops": automation.get("background_loops", {}),
            "disabled_agents": automation.get("disabled_agents", []),
            "zo_polling": automation.get(
                "zo_polling", {"interval": 300, "enabled": True, "model_tier": "default"}
            ),
            "cloud_worker": cloud_worker,
            "tier_summary": tiers,
            "agent_models": agent_models,
            "agent_models_by_tier": agent_models_by_tier,
            "user_tier": self.user_tier,
            "data_root": data_root,
        }

    def _save_settings(self, data: dict) -> dict:
        """Save updated settings to automation.yaml and optionally access-tiers.yaml."""
        try:
            current = _load_automation()
            if "suggestion_intervals" in data:
                current["suggestion_intervals"] = data["suggestion_intervals"]
            if "background_loops" in data:
                bl = current.setdefault("background_loops", {})
                for name, cfg in data["background_loops"].items():
                    if name in bl:
                        bl[name]["enabled"] = cfg.get("enabled", True)
                        if "interval" in cfg:
                            bl[name]["interval"] = cfg["interval"]
                        if "model_tier" in cfg:
                            bl[name]["model_tier"] = cfg["model_tier"]
                        if "run_at" in cfg:
                            bl[name]["run_at"] = cfg["run_at"] or None
            if "disabled_agents" in data:
                current["disabled_agents"] = data["disabled_agents"]
            if "zo_polling" in data:
                existing = current.get("zo_polling", {})
                existing.update(data["zo_polling"])
                current["zo_polling"] = existing
            if "cloud_worker" in data:
                _save_cloud_worker(data["cloud_worker"])
            _save_automation(current)

            # Admin-only: agent model assignments per tier
            if self.user_tier == "admin" and "agent_models_by_tier" in data:
                _save_access_tiers_agent_models(data["agent_models_by_tier"])

            return {"ok": True, "message": "Settings saved. Restart CLI to apply."}
        except Exception as e:
            return {"ok": False, "message": str(e)}
