const db = require('../db/db');
const { stripTags } = require('../utils/sanitize');
const { audit } = require('../utils/audit');

/* ── Prepared statements ──────────────────────────────────────────────── */
const stmts = {
  totalUsers: db.prepare("SELECT COUNT(*) AS n FROM users WHERE role = 'student'"),
  totalTests: db.prepare("SELECT COUNT(*) AS n FROM test_sessions WHERE status = 'completed'"),
  avgScore:   db.prepare(
    "SELECT ROUND(AVG(CAST(score AS REAL) / total * 100), 1) AS avg FROM test_sessions WHERE status = 'completed'"
  ),
  bySubject:  db.prepare(`
    SELECT s.name, s.slug,
           COUNT(ts.id)                                             AS tests,
           ROUND(AVG(CAST(ts.score AS REAL) / ts.total * 100), 1)  AS avg_pct
    FROM test_sessions ts
    JOIN subjects s ON s.id = ts.subject_id
    WHERE ts.status = 'completed'
    GROUP BY s.id ORDER BY s.id
  `),
};

/* ── GET /api/admin/stats ─────────────────────────────────────────────── */
function getStats(_req, res) {
  res.json({
    totalUsers: stmts.totalUsers.get().n,
    totalTests: stmts.totalTests.get().n,
    avgScore:   stmts.avgScore.get().avg,
    bySubject:  stmts.bySubject.all(),
  });
}

/* ── Overview (Phase 3 dashboard) — prepared statements ───────────────── */
const overviewStmts = {
  newUsers24h:   db.prepare("SELECT COUNT(*) AS n FROM users WHERE created_at >= datetime('now', '-24 hours')"),
  newSessions24h: db.prepare("SELECT COUNT(*) AS n FROM test_sessions WHERE started_at >= datetime('now', '-24 hours')"),
  activeUsers24h: db.prepare("SELECT COUNT(*) AS n FROM users WHERE last_login IS NOT NULL AND last_login >= datetime('now', '-24 hours')"),
  abandonedSessions24h: db.prepare("SELECT COUNT(*) AS n FROM test_sessions WHERE started_at >= datetime('now', '-24 hours') AND status = 'abandoned'"),
  classesTotal: db.prepare('SELECT COUNT(*) AS n FROM classes'),
  stuckSessions: db.prepare(`
    SELECT ts.id, u.name AS user_name, s.name AS subject_name, ts.started_at
    FROM test_sessions ts
    JOIN users u ON u.id = ts.user_id
    LEFT JOIN subjects s ON s.id = ts.subject_id
    WHERE ts.status = 'in_progress'
      AND ts.started_at < datetime('now', '-1 hour')
    ORDER BY ts.started_at
    LIMIT 5
  `),
  // No banned_at column — fall back to audit log for recent bans (last 7 days)
  bannedThisWeek: db.prepare(`
    SELECT u.id, u.name, u.email, al.created_at AS banned_at
    FROM admin_audit_log al
    JOIN users u ON u.id = CAST(SUBSTR(al.target, 6) AS INTEGER)
    WHERE al.action = 'user.ban'
      AND al.created_at >= datetime('now', '-7 days')
      AND u.is_banned = 1
    GROUP BY u.id
    ORDER BY al.created_at DESC
    LIMIT 10
  `),
  topSessions24h: db.prepare(`
    SELECT ts.id, u.name AS user_name, s.name AS subject_name,
           ts.score, ts.total,
           ROUND(CAST(ts.score AS REAL) / ts.total * 100, 1) AS percent,
           ts.finished_at
    FROM test_sessions ts
    JOIN users u    ON u.id = ts.user_id
    LEFT JOIN subjects s ON s.id = ts.subject_id
    WHERE ts.status = 'completed'
      AND ts.finished_at >= datetime('now', '-24 hours')
      AND ts.total > 0
    ORDER BY (CAST(ts.score AS REAL) / ts.total) DESC, ts.finished_at DESC
    LIMIT 5
  `),
  worstSessions24h: db.prepare(`
    SELECT ts.id, u.name AS user_name, s.name AS subject_name,
           ts.score, ts.total,
           ROUND(CAST(ts.score AS REAL) / ts.total * 100, 1) AS percent,
           ts.finished_at
    FROM test_sessions ts
    JOIN users u    ON u.id = ts.user_id
    LEFT JOIN subjects s ON s.id = ts.subject_id
    WHERE ts.status = 'completed'
      AND ts.finished_at >= datetime('now', '-24 hours')
      AND ts.total > 0
    ORDER BY (CAST(ts.score AS REAL) / ts.total) ASC, ts.finished_at DESC
    LIMIT 5
  `),
  sparkUsers7d: db.prepare(`
    SELECT date(created_at) AS d, COUNT(*) AS n FROM users
    WHERE created_at >= date('now', '-6 days')
    GROUP BY d ORDER BY d
  `),
  sparkSessions7d: db.prepare(`
    SELECT date(started_at) AS d, COUNT(*) AS n FROM test_sessions
    WHERE started_at >= date('now', '-6 days')
    GROUP BY d ORDER BY d
  `),
  sparkActiveUsers7d: db.prepare(`
    SELECT date(last_login) AS d, COUNT(DISTINCT id) AS n FROM users
    WHERE last_login IS NOT NULL AND last_login >= date('now', '-6 days')
    GROUP BY d ORDER BY d
  `),
  inventory: db.prepare(`
    SELECT
      (SELECT COUNT(*) FROM questions) AS questions,
      (SELECT COUNT(*) FROM tests)     AS tests,
      (SELECT COUNT(*) FROM courses)   AS courses,
      (SELECT COUNT(*) FROM classes)   AS classes
  `),
  sessionsBySubject24h: db.prepare(`
    SELECT s.slug, s.name, COUNT(*) AS n FROM test_sessions ts
    JOIN subjects s ON s.id = ts.subject_id
    WHERE ts.started_at >= datetime('now', '-24 hours')
    GROUP BY s.id ORDER BY n DESC
  `),
};

/* ── GET /api/admin/overview ──────────────────────────────────────────── */
function getOverview(_req, res) {
  try {
    res.json({
      newUsers24h:       overviewStmts.newUsers24h.get().n,
      newSessions24h:    overviewStmts.newSessions24h.get().n,
      activeUsers24h:    overviewStmts.activeUsers24h.get().n,
      classesTotal:          overviewStmts.classesTotal.get().n,
      abandonedSessions24h:  overviewStmts.abandonedSessions24h.get().n,
      stuckSessions:         overviewStmts.stuckSessions.all(),
      bannedThisWeek:        overviewStmts.bannedThisWeek.all(),
      topSessions24h:        overviewStmts.topSessions24h.all(),
      worstSessions24h:      overviewStmts.worstSessions24h.all(),
      inventory:             overviewStmts.inventory.get(),
      sessionsBySubject24h:  overviewStmts.sessionsBySubject24h.all(),
      sparks: {
        users:    overviewStmts.sparkUsers7d.all(),
        sessions: overviewStmts.sparkSessions7d.all(),
        active:   overviewStmts.sparkActiveUsers7d.all(),
      },
    });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
}

/* ── Global search (Phase 4 command palette) — prepared statements ────── */
const searchStmts = {
  users:   db.prepare(`
    SELECT id, name, email, role
    FROM users
    WHERE name LIKE ? OR email LIKE ?
    ORDER BY (CASE WHEN name LIKE ? THEN 0 ELSE 1 END), id DESC
    LIMIT 5
  `),
  tests:   db.prepare(`
    SELECT id, title AS name, subject_slug
    FROM tests
    WHERE title LIKE ?
    ORDER BY id DESC
    LIMIT 3
  `),
  classes: db.prepare(`
    SELECT id, name, invite_code AS code
    FROM classes
    WHERE name LIKE ? OR invite_code LIKE ?
    ORDER BY id DESC
    LIMIT 3
  `),
};

/* ── GET /api/admin/search?q=X ────────────────────────────────────────── */
function globalSearch(req, res) {
  const q = (req.query.q || '').trim();
  if (q.length < 2) {
    return res.json({ users: [], tests: [], classes: [] });
  }
  try {
    const like = `%${q}%`;
    const prefix = `${q}%`;
    res.json({
      users:   searchStmts.users.all(like, like, prefix),
      tests:   searchStmts.tests.all(like),
      classes: searchStmts.classes.all(like, like),
    });
  } catch (err) {
    console.error('[admin.search]', err.message);
    res.status(500).json({ error: 'Search failed' });
  }
}

/* ── GET /api/admin/users?page=1&limit=50&role=student&q=name ─────────── */
function getUsers(req, res) {
  const limit  = Math.min(200, Math.max(1, Number(req.query.limit) || 50));
  const cursor = Number(req.query.cursor) || 0;
  const role   = req.query.role;
  const search = req.query.q?.trim();

  let where = 'WHERE 1=1';
  const args = [];
  if (role)   { where += ' AND u.role = ?';                         args.push(role); }
  if (search) { where += ' AND (u.name LIKE ? OR u.email LIKE ?)';  args.push(`%${search}%`, `%${search}%`); }

  // Cursor-based pagination
  if (cursor) {
    const cursorWhere = where + ' AND u.id < ?';
    const cursorArgs = [...args, cursor];
    const users = db.prepare(`
      SELECT u.id, u.name, u.email, u.role, u.created_at, u.last_login, u.is_banned,
             COUNT(ts.id)                                              AS tests_count,
             ROUND(AVG(CAST(ts.score AS REAL) / ts.total * 100), 1)   AS avg_pct
      FROM users u
      LEFT JOIN test_sessions ts ON ts.user_id = u.id AND ts.status = 'completed'
      ${cursorWhere}
      GROUP BY u.id
      ORDER BY u.id DESC
      LIMIT ?
    `).all(...cursorArgs, limit);
    const nextCursor = users.length === limit ? users[users.length - 1].id : null;
    return res.json({ users, nextCursor, limit });
  }

  // Offset-based (legacy)
  const page   = Math.max(1, Number(req.query.page) || 1);
  const offset = (page - 1) * limit;
  const { total } = db.prepare(`SELECT COUNT(*) AS total FROM users u ${where}`).get(...args);

  const users = db.prepare(`
    SELECT u.id, u.name, u.email, u.role, u.created_at, u.last_login, u.is_banned,
           COUNT(ts.id)                                              AS tests_count,
           ROUND(AVG(CAST(ts.score AS REAL) / ts.total * 100), 1)   AS avg_pct
    FROM users u
    LEFT JOIN test_sessions ts ON ts.user_id = u.id AND ts.status = 'completed'
    ${where}
    GROUP BY u.id
    ORDER BY u.created_at DESC
    LIMIT ? OFFSET ?
  `).all(...args, limit, offset);

  res.json({ users, total, page, limit });
}

/* ── PATCH /api/admin/users/:id/role ─────────────────────────────────── */
function updateRole(req, res) {
  const { role } = req.body;
  const allowed = ['student', 'teacher', 'admin', 'free_student'];
  if (!allowed.includes(role))
    return res.status(400).json({ error: `role must be one of: ${allowed.join(', ')}` });

  const user = db.prepare('SELECT id FROM users WHERE id = ?').get(req.params.id);
  if (!user) return res.status(404).json({ error: 'User not found' });

  if (Number(req.params.id) === req.user.id)
    return res.status(400).json({ error: 'Cannot change your own role' });

  const oldRole = db.prepare('SELECT role FROM users WHERE id = ?').get(req.params.id)?.role;
  db.prepare('UPDATE users SET role = ?, token_version = token_version + 1 WHERE id = ?').run(role, req.params.id);
  audit(req, 'user.role_change', `user:${req.params.id}`, `${oldRole} -> ${role}`);
  res.json({ id: Number(req.params.id), role });
}

/* ── GET /api/admin/users/:id/sessions ───────────────────────────────── */
function getUserSessions(req, res) {
  const user = db.prepare('SELECT id, name, email, role, is_banned FROM users WHERE id = ?').get(req.params.id);
  if (!user) return res.status(404).json({ error: 'User not found' });

  const sessions = db.prepare(`
    SELECT ts.id, ts.mode, ts.score, ts.total, ts.status,
           ts.started_at, ts.finished_at,
           s.name AS subject_name, s.slug AS subject_slug
    FROM test_sessions ts
    LEFT JOIN subjects s ON s.id = ts.subject_id
    WHERE ts.user_id = ?
    ORDER BY ts.started_at DESC
    LIMIT 100
  `).all(req.params.id);

  res.json({ user, sessions });
}

/* ── GET /api/admin/sessions ─────────────────────────────────────────── */
function getAllSessions(req, res) {
  const { subject, user_id } = req.query;
  const limit  = Math.min(500, Math.max(1, Number(req.query.limit) || 200));
  const offset = Math.max(0, Number(req.query.offset) || 0);

  const where = ['ts.status = \'completed\''];
  const params = [];

  if (subject)  { where.push('s.slug = ?');       params.push(subject); }
  if (user_id)  { where.push('ts.user_id = ?');   params.push(Number(user_id)); }

  params.push(limit, offset);

  const sessions = db.prepare(`
    SELECT ts.id, ts.mode, ts.score, ts.total, ts.status,
           ts.started_at, ts.finished_at,
           s.name AS subject_name, s.slug AS subject_slug,
           u.id AS user_id, u.name AS user_name, u.email AS user_email,
           ROUND(CAST(ts.score AS REAL) / ts.total * 100) AS percent,
           CAST((julianday(ts.finished_at) - julianday(ts.started_at)) * 86400 AS INTEGER) AS duration_sec
    FROM test_sessions ts
    LEFT JOIN subjects s ON s.id = ts.subject_id
    JOIN users u ON u.id = ts.user_id
    WHERE ${where.join(' AND ')}
    ORDER BY ts.started_at DESC
    LIMIT ? OFFSET ?
  `).all(...params);

  res.json(sessions);
}

/* ── GET /api/admin/sessions/:id ─────────────────────────────────────── */
function getSessionDetail(req, res) {
  const session = db.prepare(`
    SELECT ts.id, ts.mode, ts.score, ts.total, ts.status,
           ts.started_at, ts.finished_at,
           s.name AS subject_name, s.slug AS subject_slug,
           u.id AS user_id, u.name AS user_name, u.email AS user_email,
           CAST((julianday(ts.finished_at) - julianday(ts.started_at)) * 86400 AS INTEGER) AS duration_sec
    FROM test_sessions ts
    LEFT JOIN subjects s ON s.id = ts.subject_id
    JOIN users u ON u.id = ts.user_id
    WHERE ts.id = ?
  `).get(req.params.id);

  if (!session) return res.status(404).json({ error: 'Session not found' });

  const questions = db.prepare(`
    SELECT q.id, q.text, q.explanation, q.difficulty,
           ua.chosen_option_id, ua.is_correct, ua.time_spent_sec,
           sq.order_index
    FROM session_questions sq
    JOIN questions q ON q.id = sq.question_id
    LEFT JOIN user_answers ua ON ua.session_id = sq.session_id AND ua.question_id = q.id
    WHERE sq.session_id = ?
    ORDER BY sq.order_index
  `).all(req.params.id);

  if (questions.length) {
    const ids = questions.map(q => q.id);
    const allOptions = db.prepare(
      `SELECT id, question_id, text, is_correct FROM options WHERE question_id IN (${ids.map(() => '?').join(',')}) ORDER BY order_index`
    ).all(...ids);

    const byQ = {};
    for (const o of allOptions) {
      (byQ[o.question_id] ||= []).push(o);
    }
    session.questions = questions.map(q => ({ ...q, options: byQ[q.id] || [] }));
  } else {
    session.questions = [];
  }

  res.json(session);
}

/* ── DELETE /api/admin/sessions/:id ──────────────────────────────────── */
const _deleteSessionTx = db.transaction((sid) => {
  // assignment_sessions references test_sessions with ON DELETE SET NULL,
  // but we explicitly null it so the assignment slot stays usable.
  db.prepare('UPDATE assignment_sessions SET session_id = NULL WHERE session_id = ?').run(sid);
  // user_answers / session_questions cascade via ON DELETE CASCADE,
  // but delete explicitly for visibility and to mirror clearUserSessions().
  db.prepare('DELETE FROM user_answers      WHERE session_id = ?').run(sid);
  db.prepare('DELETE FROM session_questions WHERE session_id = ?').run(sid);
  db.prepare('DELETE FROM test_sessions     WHERE id = ?').run(sid);
});

function deleteSession(req, res, next) {
  const sid = Number(req.params.id);
  if (!Number.isInteger(sid) || sid <= 0)
    return res.status(400).json({ error: 'Invalid session id' });
  try {
    const sess = db.prepare('SELECT id, user_id, mode FROM test_sessions WHERE id = ?').get(sid);
    if (!sess) return res.status(404).json({ error: 'Session not found' });
    _deleteSessionTx(sid);
    audit(req, 'session.delete', `session:${sid}`, `user:${sess.user_id} mode:${sess.mode}`);
    res.json({ ok: true });
  } catch (err) {
    next(err);
  }
}

/* ── DELETE /api/admin/users/:id/sessions ────────────────────────────── */
function clearUserSessions(req, res, next) {
  const uid = Number(req.params.id);
  try {
    const user = db.prepare('SELECT id FROM users WHERE id = ?').get(uid);
    if (!user) return res.status(404).json({ error: 'User not found' });

    const sessions = db.prepare('SELECT id FROM test_sessions WHERE user_id = ?').all(uid);
    const n = sessions.length;

    if (n > 0) {
      const stmtNullAsgn  = db.prepare('UPDATE assignment_sessions SET session_id = NULL WHERE session_id = ?');
      const stmtDelAns    = db.prepare('DELETE FROM user_answers      WHERE session_id = ?');
      const stmtDelSQ     = db.prepare('DELETE FROM session_questions WHERE session_id = ?');
      const stmtDelSess   = db.prepare('DELETE FROM test_sessions     WHERE id = ?');
      db.transaction(() => {
        for (const { id } of sessions) {
          stmtNullAsgn.run(id);
          stmtDelAns.run(id);
          stmtDelSQ.run(id);
          stmtDelSess.run(id);
        }
      })();
    }

    audit(req, 'user.clear_sessions', `user:${uid}`, `${n} sessions deleted`);
    res.json({ ok: true, deleted: n });
  } catch (err) {
    next(err);
  }
}

/* ── PATCH /api/admin/users/:id ──────────────────────────────────────── */
const bcrypt = require('bcryptjs');
const { BCRYPT_ROUNDS } = require('../config');

async function updateUser(req, res, next) {
  try {
    const { name, email, password } = req.body;
    const uid = Number(req.params.id);
    const user = db.prepare('SELECT id FROM users WHERE id = ?').get(uid);
    if (!user) return res.status(404).json({ error: 'User not found' });
    if (uid === req.user.id)
      return res.status(400).json({ error: 'Cannot edit your own data here' });
    if (name !== undefined && !name?.trim())
      return res.status(400).json({ error: 'Name cannot be empty' });
    if (email !== undefined) {
      if (!email?.trim() || !/\S+@\S+\.\S+/.test(email.trim()))
        return res.status(400).json({ error: 'Invalid email format' });
      const existing = db.prepare('SELECT id FROM users WHERE email = ? AND id != ?').get(email.trim().toLowerCase(), uid);
      if (existing) return res.status(400).json({ error: 'Email already in use' });
    }
    if (password !== undefined && password.length < 6)
      return res.status(400).json({ error: 'Password must be at least 6 characters' });

    const fields = [];
    const vals = [];
    if (name     !== undefined) { fields.push('name = ?');          vals.push(stripTags(name.trim())); }
    if (email    !== undefined) { fields.push('email = ?');         vals.push(email.trim().toLowerCase()); }
    if (password !== undefined) {
      const hash = await bcrypt.hash(password, BCRYPT_ROUNDS);
      fields.push('password_hash = ?', 'token_version = token_version + 1');
      vals.push(hash);
    }
    if (!fields.length) return res.status(400).json({ error: 'Nothing to update' });
    vals.push(uid);
    db.prepare(`UPDATE users SET ${fields.join(', ')} WHERE id = ?`).run(...vals);
    const changed = [];
    if (name !== undefined) changed.push('name');
    if (email !== undefined) changed.push('email');
    if (password !== undefined) changed.push('password');
    audit(req, 'user.edit', `user:${uid}`, changed.join(', '));
    res.json({ ok: true });
  } catch (err) {
    next(err);
  }
}

/* ── PATCH /api/admin/users/:id/ban  { banned: true|false } ──────────── */
function banUser(req, res) {
  const uid = Number(req.params.id);
  if (uid === req.user.id) return res.status(400).json({ error: 'Нельзя заблокировать себя' });
  const target = db.prepare('SELECT id, role FROM users WHERE id = ?').get(uid);
  if (!target) return res.status(404).json({ error: 'User not found' });
  if (target.role === 'admin') return res.status(400).json({ error: 'Нельзя заблокировать администратора' });
  const banned = req.body.banned ? 1 : 0;
  db.prepare('UPDATE users SET is_banned = ?, token_version = token_version + 1 WHERE id = ?').run(banned, uid);
  audit(req, banned ? 'user.ban' : 'user.unban', `user:${uid}`, target.role);
  res.json({ ok: true, banned: !!banned });
}

/* ── DELETE /api/admin/users/:id ─────────────────────────────────────── */
const _deleteUserTx = db.transaction((uid) => {
  // Tables with REFERENCES users(id) WITHOUT ON DELETE CASCADE:
  db.prepare('DELETE FROM questions WHERE created_by = ?').run(uid);
  db.prepare('DELETE FROM assignments WHERE created_by = ?').run(uid);
  // The rest cascades via ON DELETE CASCADE, but explicitly clean large tables:
  db.prepare('DELETE FROM notifications WHERE user_id = ?').run(uid);
  db.prepare('DELETE FROM test_sessions WHERE user_id = ?').run(uid);
  db.prepare('DELETE FROM users WHERE id = ?').run(uid);
});

function deleteUser(req, res) {
  const uid = Number(req.params.id);
  if (uid === req.user.id) return res.status(400).json({ error: 'Нельзя удалить себя' });
  const target = db.prepare('SELECT id, role FROM users WHERE id = ?').get(uid);
  if (!target) return res.status(404).json({ error: 'User not found' });
  if (target.role === 'admin') return res.status(400).json({ error: 'Нельзя удалить администратора' });
  const uname = db.prepare('SELECT name, email FROM users WHERE id = ?').get(uid);
  _deleteUserTx(uid);
  audit(req, 'user.delete', `user:${uid}`, `${uname?.name} (${uname?.email})`);
  res.json({ ok: true });
}

/* ── GET /api/admin/features ─────────────────────────────────────────── */
function getFeatures(_req, res) {
  const rows = db.prepare("SELECT key, value FROM app_settings WHERE key LIKE 'feature_%'").all();
  const features = {};
  for (const r of rows) {
    const name = r.key.replace('feature_', '').replace('_enabled', '');
    features[name] = r.value === '1';
  }
  res.json(features);
}

/* ── PATCH /api/admin/features ──────────────────────────────────────── */
function updateFeatures(req, res) {
  const allowed = ['crossword', 'hangman', 'pet', 'red_book', 'collection',
                   'flashcards', 'knowledge_map', 'board', 'biochem', 'live_quiz', 'classroom',
                   'gamification'];
  const updates = req.body;
  const stmt = db.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES (?, ?)");
  const getOld = db.prepare("SELECT value FROM app_settings WHERE key = ?");
  let touchedGamification = false;
  for (const [name, enabled] of Object.entries(updates)) {
    if (!allowed.includes(name)) continue;
    const settingKey = `feature_${name}_enabled`;
    const oldRow = getOld.get(settingKey);
    const oldVal = oldRow ? oldRow.value : null;
    const newVal = enabled ? '1' : '0';
    stmt.run(settingKey, newVal);
    audit(req, 'feature.update', `feature:${name}`, `${oldVal} -> ${newVal}`);
    if (name === 'gamification') touchedGamification = true;
  }
  // Invalidate the gamification cache so the kill-switch takes effect
  // immediately (otherwise awardXP keeps running for up to 30s).
  if (touchedGamification) {
    try {
      const { invalidateGamificationCache } = require('./gamification/_shared');
      invalidateGamificationCache();
    } catch { /* defensive — shouldn't fail */ }
  }
  res.json({ ok: true });
}

/* ── GET /api/admin/free-student-features ────────────────────────────── */
const FREE_STUDENT_MODULES = [
  'gamification', 'hangman', 'crossword', 'pet', 'red_book', 'collection',
  'lab', 'knowledge_map', 'flashcards', 'board', 'biochem', 'live_quiz',
];
function getFreeStudentFeatures(_req, res) {
  const row = db.prepare("SELECT value FROM app_settings WHERE key = 'free_student_features'").get();
  let features = {};
  if (row?.value) {
    try { features = JSON.parse(row.value); } catch {}
  }
  // Default: all enabled
  const result = {};
  for (const m of FREE_STUDENT_MODULES) {
    result[m] = features[m] !== false;
  }
  res.json(result);
}

/* ── PATCH /api/admin/free-student-features ─────────────────────────── */
function updateFreeStudentFeatures(req, res) {
  const updates = req.body;
  const row = db.prepare("SELECT value FROM app_settings WHERE key = 'free_student_features'").get();
  let current = {};
  if (row?.value) {
    try { current = JSON.parse(row.value); } catch {}
  }
  for (const [key, val] of Object.entries(updates)) {
    if (!FREE_STUDENT_MODULES.includes(key)) continue;
    current[key] = Boolean(val);
  }
  db.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('free_student_features', ?)").run(JSON.stringify(current));
  res.json({ ok: true });
}

/* ── GET /api/admin/audit-log ───────────────────────────────────────── */
function getAuditLog(req, res) {
  const limit = Math.min(500, Math.max(1, Number(req.query.limit) || 100));
  const rows = db.prepare(`
    SELECT al.*, u.name AS admin_name, u.email AS admin_email
    FROM admin_audit_log al
    LEFT JOIN users u ON u.id = al.admin_id
    ORDER BY al.created_at DESC LIMIT ?
  `).all(limit);
  res.json(rows);
}

/* ── DELETE /api/admin/audit-log ────────────────────────────────────── */
function clearAuditLog(req, res) {
  db.prepare('DELETE FROM admin_audit_log').run();
  res.json({ ok: true });
}

/* ── GET /api/admin/error-log ──────────────────────────────────────── */
function getErrorLog(req, res) {
  const limit = Math.min(500, Math.max(1, Number(req.query.limit) || 100));
  const rows = db.prepare(`
    SELECT * FROM error_log ORDER BY created_at DESC LIMIT ?
  `).all(limit);
  res.json(rows);
}

/* ── DELETE /api/admin/error-log ───────────────────────────────────── */
function clearErrorLog(req, res) {
  db.prepare('DELETE FROM error_log').run();
  res.json({ ok: true });
}

/* ── GET /api/admin/health ─────────────────────────────────────────── */
const os = require('os');
const path = require('path');
const fs = require('fs');
const { DB_PATH, UPLOADS_DIR } = require('../config');

function getHealth(_req, res) {
  const uptimeSec = process.uptime();
  let dbSizeBytes = 0;
  try { dbSizeBytes = fs.statSync(DB_PATH).size; } catch {}
  let uploadsSizeBytes = 0;
  try {
    const files = fs.readdirSync(UPLOADS_DIR);
    for (const f of files) {
      try { uploadsSizeBytes += fs.statSync(path.join(UPLOADS_DIR, f)).size; } catch {}
    }
  } catch {}

  const totalUsers   = db.prepare('SELECT COUNT(*) AS n FROM users').get().n;
  const todaySessions = db.prepare("SELECT COUNT(*) AS n FROM test_sessions WHERE started_at >= date('now')").get().n;
  const totalSessions = db.prepare('SELECT COUNT(*) AS n FROM test_sessions').get().n;
  const totalQuestions = db.prepare('SELECT COUNT(*) AS n FROM questions').get().n;
  const recentErrors  = db.prepare("SELECT COUNT(*) AS n FROM error_log WHERE created_at >= datetime('now', '-24 hours')").get().n;

  res.json({
    uptime: uptimeSec,
    memory: {
      rss: process.memoryUsage().rss,
      heapUsed: process.memoryUsage().heapUsed,
    },
    db: {
      sizeBytes: dbSizeBytes,
      totalUsers,
      totalSessions,
      todaySessions,
      totalQuestions,
    },
    uploads: {
      sizeBytes: uploadsSizeBytes,
    },
    node: process.version,
    platform: os.platform(),
    cpus: os.cpus().length,
    freeMem: os.freemem(),
    totalMem: os.totalmem(),
    recentErrors,
  });
}

/* ── Topics CRUD ─────────────────────────────────────────────────────── */
function getTopics(req, res) {
  const { subject_id } = req.query;
  let rows;
  if (subject_id) {
    rows = db.prepare(`
      SELECT t.*, s.name AS subject_name, s.slug AS subject_slug,
             (SELECT COUNT(*) FROM questions q WHERE q.topic_id = t.id) AS question_count
      FROM topics t JOIN subjects s ON s.id = t.subject_id
      WHERE t.subject_id = ? ORDER BY t.order_index, t.name
    `).all(Number(subject_id));
  } else {
    rows = db.prepare(`
      SELECT t.*, s.name AS subject_name, s.slug AS subject_slug,
             (SELECT COUNT(*) FROM questions q WHERE q.topic_id = t.id) AS question_count
      FROM topics t JOIN subjects s ON s.id = t.subject_id
      ORDER BY s.name, t.order_index, t.name
    `).all();
  }
  res.json(rows);
}

function createTopic(req, res) {
  const { subject_id, name } = req.body;
  if (!subject_id || !name?.trim())
    return res.status(400).json({ error: 'subject_id and name required' });
  const subj = db.prepare('SELECT id FROM subjects WHERE id = ?').get(Number(subject_id));
  if (!subj) return res.status(404).json({ error: 'Subject not found' });
  const maxOrder = db.prepare('SELECT MAX(order_index) AS m FROM topics WHERE subject_id = ?').get(Number(subject_id));
  const order = (maxOrder?.m || 0) + 1;
  const r = db.prepare('INSERT INTO topics (subject_id, name, order_index) VALUES (?, ?, ?)').run(Number(subject_id), stripTags(name.trim()), order);
  audit(req, 'topic.create', `topic:${r.lastInsertRowid}`, name.trim());
  res.status(201).json({ id: r.lastInsertRowid, name: name.trim(), order_index: order });
}

function updateTopic(req, res) {
  const id = Number(req.params.id);
  const topic = db.prepare('SELECT * FROM topics WHERE id = ?').get(id);
  if (!topic) return res.status(404).json({ error: 'Topic not found' });
  const { name, order_index } = req.body;
  const newName = name !== undefined ? stripTags(name.trim()) : topic.name;
  const newOrder = order_index !== undefined ? Number(order_index) : topic.order_index;
  if (!newName) return res.status(400).json({ error: 'Name cannot be empty' });
  db.prepare('UPDATE topics SET name = ?, order_index = ? WHERE id = ?').run(newName, newOrder, id);
  audit(req, 'topic.update', `topic:${id}`, newName);
  res.json({ ok: true });
}

function deleteTopic(req, res) {
  const id = Number(req.params.id);
  const topic = db.prepare('SELECT t.*, (SELECT COUNT(*) FROM questions q WHERE q.topic_id = t.id) AS qcount FROM topics t WHERE t.id = ?').get(id);
  if (!topic) return res.status(404).json({ error: 'Topic not found' });
  if (topic.qcount > 0)
    return res.status(400).json({ error: `Cannot delete topic with ${topic.qcount} questions. Reassign questions first.` });
  db.prepare('DELETE FROM topics WHERE id = ?').run(id);
  audit(req, 'topic.delete', `topic:${id}`, topic.name);
  res.json({ ok: true });
}

/* ── Broadcast notification ──────────────────────────────────────────── */
const { pushNotif } = require('../utils/notifications');

function broadcast(req, res) {
  const { message, role, link } = req.body;
  if (!message?.trim()) return res.status(400).json({ error: 'message required' });
  const msg = stripTags(message.trim()).slice(0, 500);
  const lnk = link?.trim() || null;

  let users;
  if (role && role !== 'all') {
    users = db.prepare('SELECT id FROM users WHERE role = ? AND is_banned = 0').all(role);
  } else {
    users = db.prepare('SELECT id FROM users WHERE is_banned = 0').all();
  }

  const ins = db.prepare("INSERT INTO notifications (user_id, type, message, link) VALUES (?, 'broadcast', ?, ?)");
  db.transaction(() => {
    for (const u of users) ins.run(u.id, msg, lnk);
  })();

  audit(req, 'broadcast', role || 'all', `${users.length} users: ${msg.slice(0, 80)}`);
  res.json({ ok: true, sent: users.length });
}

module.exports = {
  getStats, getOverview, globalSearch,
  getUsers, updateRole, getUserSessions, getAllSessions, getSessionDetail,
  clearUserSessions, deleteSession, updateUser, banUser, deleteUser,
  getFeatures, updateFeatures, getFreeStudentFeatures, updateFreeStudentFeatures,
  getAuditLog, clearAuditLog, getErrorLog, clearErrorLog, getHealth,
  getTopics, createTopic, updateTopic, deleteTopic,
  broadcast,
};
