'use strict';
/* /api/lab — каталог симуляций лаборатории (контент-движок, Фазы 4-5).
 *
 * GET  /api/lab/sims              — каталог из БД (lab_sims) + legacy-флаги. auth.
 * PATCH /api/lab/sims/:id         — enabled/featured/tags/subject/grade. admin.
 * POST  /api/lab/sims/reorder     — задать порядок (массив id). admin.
 * GET  /api/lab/sims/:id/related  — связанные § / темы / kmap / задачи. auth. (Ф5)
 * POST  /api/lab/sims/:id/links   — добавить связь. admin. (Ф5)
 * DELETE /api/lab/sims/:id/links/:linkId — удалить связь. admin. (Ф5)
 * GET  /api/lab/links?kind=&ref_id= — обратный поиск: какие симуляции привязаны
 *                                     к данному учебнику/теме. auth. (Ф5)
 *
 * Совместимость: enabled зеркалится в app_settings.sim_disabled_ids, поэтому
 * существующая логика lab.html (которая читает /api/settings/sims) продолжает
 * корректно скрывать отключённые симуляции без правок фронта. */
const router = require('express').Router();
const db = require('../db/db');
const { authMiddleware, requireRole } = require('../middleware/auth');

const CATS = ['math', 'phys', 'chem', 'bio', 'game'];
const LINK_KINDS = ['textbook', 'topic', 'kmap', 'question'];

router.use(authMiddleware);

/* ── helpers ───────────────────────────────────────────────────────────── */
function readModuleDisabled() {
  const row = db.prepare(`SELECT value FROM app_settings WHERE key = 'sim_module_disabled'`).get();
  return row ? row.value === '1' : false;
}
function readLegacyDisabledIds() {
  const row = db.prepare(`SELECT value FROM app_settings WHERE key = 'sim_disabled_ids'`).get();
  try { return new Set(JSON.parse(row && row.value || '[]')); } catch { return new Set(); }
}
function writeLegacyDisabledIds(set) {
  db.prepare(`INSERT INTO app_settings (key, value) VALUES ('sim_disabled_ids', ?)
              ON CONFLICT(key) DO UPDATE SET value = excluded.value`)
    .run(JSON.stringify([...set]));
}
function parseTags(raw) { try { return JSON.parse(raw || '[]'); } catch { return []; } }

function rowToSim(r) {
  return {
    id: r.id, cat: r.cat, title: r.title,
    subject: r.subject || null, grade: r.grade != null ? r.grade : null,
    sort: r.sort_order, enabled: !!r.enabled, featured: !!r.featured,
    tags: parseTags(r.tags),
  };
}

/* ── GET /api/lab/sims ─────────────────────────────────────────────────── */
router.get('/sims', (_req, res) => {
  let rows;
  try {
    rows = db.prepare(`SELECT * FROM lab_sims ORDER BY sort_order, id`).all();
  } catch (e) {
    // Деградация вместо 500: если миграция lab_sims ещё не применена на этом
    // инстансе (старый процесс/другая БД) — отдаём пустой каталог, чтобы админка
    // не падала. Нужно применить миграцию и перезапустить сервер.
    console.warn('[lab] lab_sims недоступна (нужна миграция/перезапуск):', e.message);
    return res.json({ module_disabled: readModuleDisabled(), sims: [], needs_migration: true });
  }
  const legacyDisabled = readLegacyDisabledIds();
  const sims = rows.map(r => {
    const s = rowToSim(r);
    // Симуляция считается выключенной, если так сказано в lab_sims ИЛИ в legacy-списке.
    s.enabled = s.enabled && !legacyDisabled.has(r.id);
    return s;
  });
  res.json({ module_disabled: readModuleDisabled(), sims });
});

/* ── admin mutations ───────────────────────────────────────────────────────
   ВАЖНО: НЕ используем blanket `router.use(requireRole('admin'))` — он применялся
   бы и к ниже определённым READ-роутам Фазы 5 (/related, /links), которые должны
   быть доступны любому авторизованному пользователю. Каждая мутация защищена
   INLINE requireRole('admin') (так же видит route-auth линтер). */

/* PATCH /api/lab/sims/:id  body: { enabled?, featured?, tags?, subject?, grade?, title?, cat? } */
router.patch('/sims/:id', requireRole('admin'), (req, res) => {
  const id = String(req.params.id || '');
  const row = db.prepare('SELECT * FROM lab_sims WHERE id = ?').get(id);
  if (!row) return res.status(404).json({ error: 'симуляция не найдена' });

  const b = req.body || {};
  const sets = [];
  const vals = [];

  if (b.enabled !== undefined) { sets.push('enabled = ?'); vals.push(b.enabled ? 1 : 0); }
  if (b.featured !== undefined) { sets.push('featured = ?'); vals.push(b.featured ? 1 : 0); }
  if (b.title !== undefined) {
    const t = String(b.title).trim();
    if (!t) return res.status(400).json({ error: 'пустой title' });
    sets.push('title = ?'); vals.push(t);
  }
  if (b.cat !== undefined) {
    if (!CATS.includes(b.cat)) return res.status(400).json({ error: 'неверная категория' });
    sets.push('cat = ?'); vals.push(b.cat);
  }
  if (b.subject !== undefined) { sets.push('subject = ?'); vals.push(b.subject ? String(b.subject) : null); }
  if (b.grade !== undefined) {
    const g = b.grade === null || b.grade === '' ? null : Number(b.grade);
    if (g !== null && (!Number.isInteger(g) || g < 1 || g > 11)) {
      return res.status(400).json({ error: 'grade должен быть 1..11 или null' });
    }
    sets.push('grade = ?'); vals.push(g);
  }
  if (b.tags !== undefined) {
    if (!Array.isArray(b.tags)) return res.status(400).json({ error: 'tags должен быть массивом' });
    const clean = b.tags.map(t => String(t).trim()).filter(Boolean).slice(0, 20);
    sets.push('tags = ?'); vals.push(JSON.stringify(clean));
  }

  if (!sets.length) return res.status(400).json({ error: 'нет полей для обновления' });

  sets.push("updated_at = datetime('now')");
  vals.push(id);
  db.prepare(`UPDATE lab_sims SET ${sets.join(', ')} WHERE id = ?`).run(...vals);

  // Зеркалим enabled в legacy sim_disabled_ids для совместимости с lab.html.
  if (b.enabled !== undefined) {
    const set = readLegacyDisabledIds();
    if (b.enabled) set.delete(id); else set.add(id);
    writeLegacyDisabledIds(set);
  }

  const updated = db.prepare('SELECT * FROM lab_sims WHERE id = ?').get(id);
  res.json({ ok: true, sim: rowToSim(updated) });
});

/* POST /api/lab/sims/reorder  body: { order: [id, id, ...] } */
router.post('/sims/reorder', requireRole('admin'), (req, res) => {
  const order = (req.body && req.body.order) || [];
  if (!Array.isArray(order) || !order.length) {
    return res.status(400).json({ error: 'order должен быть непустым массивом id' });
  }
  const exists = new Set(db.prepare('SELECT id FROM lab_sims').all().map(r => r.id));
  for (const id of order) {
    if (!exists.has(id)) return res.status(400).json({ error: 'неизвестный id: ' + id });
  }
  const upd = db.prepare("UPDATE lab_sims SET sort_order = ?, updated_at = datetime('now') WHERE id = ?");
  db.transaction(() => {
    order.forEach((id, i) => upd.run(i + 1, id));
  })();
  res.json({ ok: true, count: order.length });
});

/* ════════════════════════════════════════════════════════════════════════
   Курикулумная привязка (Фаза 5) — связи симуляции ↔ контент.
   ════════════════════════════════════════════════════════════════════════ */

// Безопасно прочитать связи симуляции (если таблицы ещё нет — пустой массив).
function readLinks(simId) {
  try {
    return db.prepare(
      'SELECT id, sim_id, kind, ref_id, label FROM lab_sim_links WHERE sim_id = ? ORDER BY kind, id'
    ).all(simId);
  } catch (e) {
    return null; // null => таблица недоступна (нужна миграция)
  }
}

// Обогатить связь человекочитаемой меткой и навигационным href.
function decorateLink(l) {
  const out = { id: l.id, kind: l.kind, ref_id: l.ref_id, label: l.label || null };
  if (l.kind === 'textbook') {
    const tb = db.prepare('SELECT title, subject, grade FROM textbooks WHERE slug = ?').get(l.ref_id);
    if (tb) { out.label = out.label || tb.title; out.subject = tb.subject; out.grade = tb.grade; }
    out.href = '/textbooks?book=' + encodeURIComponent(l.ref_id);
  } else if (l.kind === 'topic') {
    const tp = db.prepare('SELECT name FROM topics WHERE id = ?').get(Number(l.ref_id));
    if (tp) out.label = out.label || tp.name;
  } else if (l.kind === 'question') {
    out.href = null; // задачи открываются в банке вопросов отдельным контекстом
  }
  if (!out.label) out.label = l.kind + ':' + l.ref_id;
  return out;
}

/* GET /api/lab/sims/:id/related → { sim, links:{ textbook:[], topic:[], kmap:[], question:[] } } */
router.get('/sims/:id/related', authMiddleware, (req, res) => {
  const id = String(req.params.id || '');
  const sim = db.prepare('SELECT id, title FROM lab_sims WHERE id = ?').get(id);
  // sim может отсутствовать в lab_sims (если миграция 042 не применена) — не 404,
  // т.к. связи всё равно могут существовать; вернём то, что есть.
  const rows = readLinks(id);
  if (rows === null) return res.json({ sim: sim || { id }, links: {}, needs_migration: true });
  const links = { textbook: [], topic: [], kmap: [], question: [] };
  for (const l of rows) {
    const d = decorateLink(l);
    (links[l.kind] || (links[l.kind] = [])).push(d);
  }
  res.json({ sim: sim || { id }, links });
});

/* GET /api/lab/links?kind=textbook&ref_id=algebra-8
   → { sims:[{id,title,cat,enabled}] } — какие (включённые) симуляции привязаны. */
router.get('/links', (req, res) => {
  const kind = String(req.query.kind || '');
  const refId = String(req.query.ref_id || '');
  if (!LINK_KINDS.includes(kind) || !refId) {
    return res.status(400).json({ error: 'kind и ref_id обязательны' });
  }
  let rows;
  try {
    rows = db.prepare(`
      SELECT s.id, s.title, s.cat, s.enabled
        FROM lab_sim_links l JOIN lab_sims s ON s.id = l.sim_id
       WHERE l.kind = ? AND l.ref_id = ?
       ORDER BY s.sort_order, s.id
    `).all(kind, refId);
  } catch (e) {
    return res.json({ sims: [], needs_migration: true });
  }
  const legacyDisabled = readLegacyDisabledIds();
  const sims = rows
    .map(r => ({ id: r.id, title: r.title, cat: r.cat, enabled: !!r.enabled && !legacyDisabled.has(r.id) }))
    .filter(s => s.enabled); // наружу отдаём только доступные
  res.json({ sims });
});

/* GET /api/lab/links/all?kind=textbook
   → { byRef: { <ref_id>: [{id,title,cat}] } } — пакетный обратный поиск для всех
   ref_id данного типа за один запрос (избегаем N+1 на странице каталога учебников).
   Отдаёт только включённые симуляции. */
router.get('/links/all', (req, res) => {
  const kind = String(req.query.kind || '');
  if (!LINK_KINDS.includes(kind)) {
    return res.status(400).json({ error: 'неверный kind' });
  }
  let rows;
  try {
    rows = db.prepare(`
      SELECT l.ref_id, s.id, s.title, s.cat, s.enabled, s.sort_order
        FROM lab_sim_links l JOIN lab_sims s ON s.id = l.sim_id
       WHERE l.kind = ?
       ORDER BY s.sort_order, s.id
    `).all(kind);
  } catch (e) {
    return res.json({ byRef: {}, needs_migration: true });
  }
  const legacyDisabled = readLegacyDisabledIds();
  const byRef = {};
  for (const r of rows) {
    if (!r.enabled || legacyDisabled.has(r.id)) continue;
    (byRef[r.ref_id] || (byRef[r.ref_id] = [])).push({ id: r.id, title: r.title, cat: r.cat });
  }
  res.json({ byRef });
});

/* ── admin: управление связями ─────────────────────────────────────────── */

/* POST /api/lab/sims/:id/links  body: { kind, ref_id, label? } */
router.post('/sims/:id/links', requireRole('admin'), (req, res) => {
  const simId = String(req.params.id || '');
  if (!db.prepare('SELECT 1 FROM lab_sims WHERE id = ?').get(simId)) {
    return res.status(404).json({ error: 'симуляция не найдена' });
  }
  const b = req.body || {};
  const kind = String(b.kind || '');
  const refId = String(b.ref_id || '').trim();
  if (!LINK_KINDS.includes(kind)) return res.status(400).json({ error: 'неверный kind' });
  if (!refId) return res.status(400).json({ error: 'ref_id обязателен' });

  // Валидация существования цели (мягкая — kmap/question произвольны).
  if (kind === 'textbook' && !db.prepare('SELECT 1 FROM textbooks WHERE slug = ?').get(refId)) {
    return res.status(404).json({ error: 'учебник не найден: ' + refId });
  }
  if (kind === 'topic') {
    const tid = Number(refId);
    if (!Number.isInteger(tid) || !db.prepare('SELECT 1 FROM topics WHERE id = ?').get(tid)) {
      return res.status(404).json({ error: 'тема не найдена: ' + refId });
    }
  }

  const label = b.label != null ? String(b.label).trim().slice(0, 200) || null : null;
  try {
    const info = db.prepare(
      'INSERT INTO lab_sim_links (sim_id, kind, ref_id, label, created_by) VALUES (?, ?, ?, ?, ?)'
    ).run(simId, kind, refId, label, req.user.id);
    const created = db.prepare('SELECT id, sim_id, kind, ref_id, label FROM lab_sim_links WHERE id = ?')
      .get(info.lastInsertRowid);
    res.json({ ok: true, link: decorateLink(created) });
  } catch (e) {
    if (/UNIQUE/i.test(e.message)) return res.status(409).json({ error: 'такая связь уже есть' });
    throw e;
  }
});

/* DELETE /api/lab/sims/:id/links/:linkId */
router.delete('/sims/:id/links/:linkId', requireRole('admin'), (req, res) => {
  const simId = String(req.params.id || '');
  const linkId = Number(req.params.linkId);
  if (!Number.isInteger(linkId)) return res.status(400).json({ error: 'неверный linkId' });
  const info = db.prepare('DELETE FROM lab_sim_links WHERE id = ? AND sim_id = ?').run(linkId, simId);
  if (!info.changes) return res.status(404).json({ error: 'связь не найдена' });
  res.json({ ok: true });
});

module.exports = router;
