const config = require('./config');        // validates .env, fails fast on error
const logger = require('./utils/logger');  // structured logging

/* ── Schema check — fail fast if migrate hasn't been run ── */
const _bootDb = require('./db/db');
try {
  _bootDb.prepare('SELECT 1 FROM users LIMIT 1').get();
  _bootDb.prepare('SELECT 1 FROM role_permissions LIMIT 1').get();
} catch (e) {
  process.stderr.write(
    '[boot] FATAL: schema not initialized.\n' +
    'Run: npm run migrate && npm run seed:permissions\n'
  );
  process.exit(1);
}

const express     = require('express');
const cors        = require('cors');
const path        = require('path');
const compression = require('compression');

const authRoutes         = require('./routes/auth');
const subjectRoutes      = require('./routes/subjects');
const sessionRoutes      = require('./routes/sessions');
const adminRoutes        = require('./routes/admin');
const questionRoutes     = require('./routes/questions');
const classRoutes        = require('./routes/classes');
const assignmentRoutes   = require('./routes/assignments');
const fileRoutes         = require('./routes/files');
const testRoutes         = require('./routes/tests');
const notificationRoutes  = require('./routes/notifications');
const permissionRoutes    = require('./routes/permissions');
const submissionRoutes    = require('./routes/submissions');
const courseRoutes        = require('./routes/courses');
const lessonRoutes        = require('./routes/lessons');
const gamificationRoutes  = require('./routes/gamification');
const shopRoutes          = require('./routes/shop');
const templateRoutes      = require('./routes/templates');
const bookmarkRoutes      = require('./routes/bookmarks');
const searchRoutes        = require('./routes/search');
const flashcardRoutes     = require('./routes/flashcards');
const settingsRoutes      = require('./routes/settings');
const analyticsRoutes     = require('./routes/analytics');
const liveRoutes          = require('./routes/live');
const classroomRoutes      = require('./routes/classroom');
const guestClassroomRoutes = require('./routes/guestClassroom');
const gamesRoutes          = require('./routes/games');
const knowledgeMapRoutes  = require('./routes/knowledgeMap');
const petRoutes           = require('./routes/pet');
const collectionRoutes    = require('./routes/collection');
const redBookRoutes       = require('./routes/red-book');
const parentRoutes        = require('./routes/parent');
const exam9Routes         = require('./routes/exam9');
const examPrepRoutes      = require('./routes/exam-prep');
const textbookRoutes      = require('./routes/textbooks');
const teacherStudentsRoutes = require('./routes/teacherStudents');

const { requestId, errorHandler } = require('./middleware/errorHandler');

const app    = express();
const PORT   = config.PORT;
const isProd = config.isProd;

/* ── Gzip compression (skip SSE streams — compression buffers chunks and breaks real-time) ── */
app.use(compression({
  filter: (req, res) => {
    if (req.path.includes('/stream') || req.headers.accept === 'text/event-stream') return false;
    return compression.filter(req, res);
  },
}));

/* ── Request ID — attach before everything else ── */
app.use(requestId);

/* ── Security headers (no helmet dep needed) ── */
app.use((_req, res, next) => {
  res.setHeader('X-Content-Type-Options', 'nosniff');
  res.setHeader('X-Frame-Options', 'SAMEORIGIN');
  res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
  res.setHeader('X-XSS-Protection', '1; mode=block');
  // classroom.html needs microphone + camera for WebRTC
  const isClassroom = _req.path === '/classroom' || _req.path === '/classroom.html';
  res.setHeader('Permissions-Policy',
    isClassroom
      ? 'camera=(self), microphone=(self), geolocation=()'
      : 'camera=(), microphone=(), geolocation=()'
  );
  res.setHeader('Content-Security-Policy',
    "default-src 'self'; " +
    "script-src 'self' 'unsafe-inline' https://unpkg.com https://cdn.jsdelivr.net; " +
    "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://cdn.jsdelivr.net; " +
    "font-src 'self' https://fonts.gstatic.com https://cdn.jsdelivr.net; " +
    "img-src 'self' data: blob: https:; " +
    "connect-src 'self' https://cdn.jsdelivr.net https://stun.l.google.com; " +
    "frame-src 'self' https://www.youtube.com https://rutube.ru https://player.vimeo.com; " +
    "frame-ancestors 'self'" +
    (isProd ? "; upgrade-insecure-requests" : "")
  );
  if (isProd) res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
  next();
});

/* ── Request logger ── */
app.use((req, res, next) => {
  const start = Date.now();
  res.on('finish', () => {
    const ms     = Date.now() - start;
    const status = res.statusCode;
    const level  = status >= 500 ? 'error' : status >= 400 ? 'warn' : 'info';
    logger[level](`${req.method} ${req.path}`, {
      status, ms, requestId: req.requestId,
      userId: req.user?.id,
    });
  });
  next();
});

/* ── CORS ── */
app.set('trust proxy', 1);
const allowedOrigins = [process.env.CLIENT_ORIGIN].filter(Boolean);
app.use(cors({
  origin: (origin, cb) => {
    if (!origin) return cb(null, !isProd); // dev: allow, prod: block
    cb(null, allowedOrigins.includes(origin) ? origin : false);
  },
  credentials: true
}));

/* ── Body parser with size limit ── */
app.use(express.json({ limit: '1mb' }));

/* ── Global API rate limit ── */
const rateLimit = require('./middleware/rateLimit');
const { requireFeature } = require('./middleware/features');
// Classroom real-time endpoints (cursor, stroke-preview) fire ~10/s per user — higher limit
app.use('/api/classroom', rateLimit({ windowMs: 60_000, max: 6000, message: 'Слишком много запросов' }));
app.use('/api', rateLimit({ windowMs: 60_000, max: 600, message: 'Слишком много запросов, подождите минуту' }));

/* ── Routes ── */
app.use('/api/auth',          authRoutes);
app.use('/api/subjects',      subjectRoutes);
app.use('/api/sessions',      sessionRoutes);
app.use('/api/admin',         adminRoutes);
app.use('/api/questions',     questionRoutes);
app.use('/api/classes',       classRoutes);
app.use('/api/assignments',   assignmentRoutes);
app.use('/api/files',         fileRoutes);
app.use('/api/tests',         testRoutes);
app.use('/api/notifications', notificationRoutes);
app.use('/api/permissions',  permissionRoutes);
app.use('/api/submissions',  submissionRoutes);
app.use('/api/courses',       courseRoutes);
app.use('/api/lessons',       lessonRoutes);
app.use('/api/gamification',  gamificationRoutes);
app.use('/api/shop',          shopRoutes);
app.use('/api/templates',     templateRoutes);
app.use('/api/bookmarks',    bookmarkRoutes);
app.use('/api/search',       searchRoutes);
app.use('/api/flashcards',   requireFeature('flashcards'), flashcardRoutes);
app.use('/api/settings',    settingsRoutes);
app.use('/api/preferences', require('./routes/preferences'));
app.use('/api/avatar',     require('./routes/avatar'));
app.use('/api/analytics',   analyticsRoutes);
app.use('/api/live',              liveRoutes);
app.use('/api/classroom/guest',   guestClassroomRoutes); // public — MUST be before /api/classroom
app.use('/api/classroom',         classroomRoutes);
app.use('/api/games',        gamesRoutes);
app.use('/api/knowledge-map', requireFeature('knowledge_map'), knowledgeMapRoutes);
app.use('/api/pet',          requireFeature('pet'),           petRoutes);
app.use('/api/collection',   requireFeature('collection'),    collectionRoutes);
app.use('/api/red-book',     requireFeature('red_book'),      redBookRoutes);
app.use('/api/biochem',      requireFeature('biochem'),       require('./routes/biochem'));
app.use('/api/parent',       parentRoutes);
app.use('/api/exam9',        exam9Routes);
app.use('/api/exam-prep',    examPrepRoutes);
app.use('/api/textbooks',    textbookRoutes);
app.use('/api/teacher-students', teacherStudentsRoutes);

/* ── Public features endpoint (merges global + per-class for authenticated students) ── */
const _featDb = require('./db/db');
const _stmtGlobalFeats      = _featDb.prepare("SELECT key, value FROM app_settings WHERE key LIKE 'feature_%'");
const _stmtClassFeats        = _featDb.prepare(
  'SELECT c.features FROM classes c JOIN class_members cm ON cm.class_id = c.id WHERE cm.user_id = ?'
);
const _stmtFreeStudentFeats  = _featDb.prepare("SELECT value FROM app_settings WHERE key = 'free_student_features'");
const _jwtLib = require('jsonwebtoken');

app.get('/api/features', (req, res) => {
  const rows = _stmtGlobalFeats.all();
  const features = {};
  for (const r of rows) {
    const name = r.key.replace('feature_', '').replace('_enabled', '');
    features[name] = r.value === '1';
  }
  // Prevent browser caching — class features change when teacher updates settings
  res.setHeader('Cache-Control', 'no-store');

  // For authenticated students: overlay class-specific feature flags
  const token = (req.headers.authorization || '').replace('Bearer ', '');
  if (token) {
    try {
      const payload = _jwtLib.verify(token, process.env.JWT_SECRET, { algorithms: ['HS256'] });
      if (payload.role === 'student' || payload.role === 'free_student') {
        const classes = _stmtClassFeats.all(payload.id);
        if (classes.length === 0 && payload.role === 'student') {
          features._no_class = true; // regular student has no class — restrict access
        }
        for (const cls of classes) {
          if (!cls.features) continue;
          try {
            const f = JSON.parse(cls.features);
            for (const [key, val] of Object.entries(f)) {
              if (val === false) features[key] = false;
            }
          } catch (e) { console.error('[features] class JSON parse error:', e.message); }
        }
        // Apply role-level free_student restrictions (set by admin)
        if (payload.role === 'free_student') {
          const fsRow = _stmtFreeStudentFeats.get();
          if (fsRow?.value) {
            try {
              const fsFeats = JSON.parse(fsRow.value);
              for (const [key, val] of Object.entries(fsFeats)) {
                if (val === false) features[key] = false;
              }
            } catch (e) { console.error('[features] free_student JSON parse error:', e.message); }
          }
        }
      }
    } catch { /* invalid/expired token — anonymous features */ }
  }
  res.json(features);
});

/* ── Health check ── */
const _startTime = Date.now();
const _pkg = require('../package.json');
const _db  = require('./db/db');

/* GET /api/ice-servers — WebRTC ICE config (STUN + optional TURN from env)
   Required for clients behind symmetric NAT/CGNAT (school/corporate networks). */
app.get('/api/ice-servers', (req, res) => {
  const iceServers = [
    { urls: 'stun:stun.l.google.com:19302' },
    { urls: 'stun:stun1.l.google.com:19302' },
  ];
  if (config.TURN_URL && config.TURN_USER && config.TURN_PASS) {
    iceServers.push({
      urls: config.TURN_URL,
      username: config.TURN_USER,
      credential: config.TURN_PASS,
    });
  }
  res.json({ iceServers });
});

app.get('/api/health', (req, res) => {
  let dbStatus = 'ok';
  let dbLatencyMs = null;
  try {
    const t0 = Date.now();
    _db.prepare('SELECT 1').get();
    dbLatencyMs = Date.now() - t0;
  } catch {
    dbStatus = 'error';
  }

  const status = dbStatus === 'ok' ? 'ok' : 'degraded';
  const httpStatus = status === 'ok' ? 200 : 503;

  // Minimal public response — no version/runtime details
  const pub = { status, timestamp: new Date().toISOString() };

  // Detailed response only for authenticated admins
  const token = (req.headers.authorization || '').replace('Bearer ', '');
  if (token) {
    try {
      const jwt = require('jsonwebtoken');
      const payload = jwt.verify(token, process.env.JWT_SECRET, { algorithms: ['HS256'] });
      const u = _db.prepare('SELECT role FROM users WHERE id = ?').get(payload.id);
      if (u?.role === 'admin') {
        const uptimeSec = Math.floor((Date.now() - _startTime) / 1000);
        return res.status(httpStatus).json({
          ...pub,
          version: _pkg.version,
          uptime: { seconds: uptimeSec, human: _fmtUptime(uptimeSec) },
          db: { status: dbStatus, latency_ms: dbLatencyMs },
          node: process.version,
          env: process.env.NODE_ENV || 'development',
        });
      }
    } catch { /* invalid token → fall through to public response */ }
  }

  res.status(httpStatus).json(pub);
});

function _fmtUptime(s) {
  const d = Math.floor(s / 86400);
  const h = Math.floor((s % 86400) / 3600);
  const m = Math.floor((s % 3600) / 60);
  const sec = s % 60;
  if (d > 0) return `${d}d ${h}h ${m}m`;
  if (h > 0) return `${h}h ${m}m ${sec}s`;
  if (m > 0) return `${m}m ${sec}s`;
  return `${sec}s`;
}

/* ── Static frontend ── */
const frontendDir = path.join(__dirname, '../../frontend');
const jsDir       = path.join(__dirname, '../../js');
const staticCache = isProd ? { maxAge: '7d' } : { setHeaders: (res) => res.setHeader('Cache-Control', 'no-store') };
app.use('/js',      express.static(jsDir, staticCache));
app.use('/css',     express.static(path.join(frontendDir, 'css'), staticCache));
app.use('/img',     express.static(path.join(frontendDir, 'img'), staticCache));
app.use('/avatars', express.static(path.join(__dirname, '../uploads/avatars'), { maxAge: '1d' }));

// Redirect legacy .html URLs → clean URLs (301)
app.use((req, res, next) => {
  if (req.path.endsWith('.html') && !req.path.startsWith('/api/')) {
    const clean = req.path.slice(0, -5);
    const qs    = req.url.slice(req.path.length); // preserve ?query
    return res.redirect(301, clean + qs);
  }
  next();
});

// Clean URL for textbooks: /textbook/<slug> → frontend/textbooks/<html_path>
// With ?embed=1 — inject CSS that hides headers/sidebars + JS-bridge for classroom sync.
const fs = require('fs');
const _textbookDb = require('./db/db');
const _stmtTextbookPath = _textbookDb.prepare('SELECT html_path FROM textbooks WHERE slug=? AND is_active=1');
const _embedCache = new Map(); // html_path → {mtime, html}

const EMBED_INJECT = `
<style id="__ls_embed_style__">
  /* Скрываем хедеры/боковые навигации в embed-режиме classroom */
  .hdr, .hdr-back, .hdr-side, .app-layout > .sidebar, .sidebar, .mob-bar,
  .topbar, .topbar-nav, .tb-back, .ls-topbar, .ls-sidebar, .sb-content > .sidebar,
  header.hdr, nav.sidebar { display: none !important; }
  .app-layout { display: block !important; }
  .sb-content { margin-left: 0 !important; width: 100% !important; }
  body { padding-top: 0 !important; margin-top: 0 !important; }
  main { padding-top: 16px !important; }
  html, body { background: #fff; }
</style>
<script id="__ls_embed_bridge__">
(function(){
  try {
    if (window.parent === window) return;
    var SLUG = __LS_SLUG__;
    // Перехват внутренних ссылок → передача наверх (учитель транслирует всем)
    document.addEventListener('click', function(e){
      var a = e.target.closest && e.target.closest('a[href]');
      if (!a) return;
      var href = a.getAttribute('href') || '';
      if (!href || href.startsWith('javascript:')) return;
      if (href.startsWith('#')) {
        // hash-навигация внутри страницы
        window.parent.postMessage({ type:'ls_tb_nav', slug:SLUG, hash:href.slice(1) }, '*');
        return;
      }
      // ссылка на другой учебник (/textbook/<slug>)
      var m = href.match(/^\\/textbook\\/([a-z0-9_-]+)/i);
      if (m) {
        e.preventDefault();
        window.parent.postMessage({ type:'ls_tb_nav', slug:m[1], hash:null }, '*');
      }
    }, true);
    // Скролл (throttled) — учитель транслирует
    var st;
    window.addEventListener('scroll', function(){
      clearTimeout(st);
      st = setTimeout(function(){
        window.parent.postMessage({ type:'ls_tb_scroll', slug:SLUG, scrollY:window.scrollY }, '*');
      }, 250);
    }, { passive:true });
    // Принимаем команды от parent (для студентов в demo-режиме)
    window.addEventListener('message', function(e){
      var d = e.data; if (!d || typeof d !== 'object') return;
      if (d.type === 'ls_tb_apply') {
        if (d.hash != null) { try { location.hash = '#' + d.hash; } catch(_){} }
        if (typeof d.scrollY === 'number') { try { window.scrollTo(0, d.scrollY); } catch(_){} }
      }
      if (d.type === 'ls_tb_lock') {
        // блокируем взаимодействие у студентов в demo
        var el = document.getElementById('__ls_embed_lock__');
        if (d.locked && !el) {
          el = document.createElement('div');
          el.id = '__ls_embed_lock__';
          el.style.cssText = 'position:fixed;inset:0;z-index:2147483647;background:transparent;cursor:not-allowed';
          document.body.appendChild(el);
        } else if (!d.locked && el) {
          el.remove();
        }
      }
    });
    // Сообщаем parent что embed готов
    window.parent.postMessage({ type:'ls_tb_ready', slug:SLUG }, '*');
  } catch(_){}
})();
</script>
`;

function _renderEmbed(filePath, slug) {
  let stat; try { stat = fs.statSync(filePath); } catch { return null; }
  const cached = _embedCache.get(filePath);
  if (cached && cached.mtime === stat.mtimeMs && cached.slug === slug) return cached.html;
  let html;
  try { html = fs.readFileSync(filePath, 'utf8'); } catch { return null; }
  const inject = EMBED_INJECT.replace('__LS_SLUG__', JSON.stringify(slug));
  if (html.includes('</head>')) html = html.replace('</head>', inject + '</head>');
  else html = inject + html;
  _embedCache.set(filePath, { mtime: stat.mtimeMs, slug, html });
  return html;
}

app.get('/textbook/:slug', (req, res, next) => {
  const row = _stmtTextbookPath.get(req.params.slug);
  if (!row) return next();
  const filePath = path.join(frontendDir, 'textbooks', row.html_path);
  if (!isProd) res.setHeader('Cache-Control', 'no-store');
  if (req.query.embed === '1') {
    const html = _renderEmbed(filePath, req.params.slug);
    if (html == null) return next();
    res.setHeader('Content-Type', 'text/html; charset=utf-8');
    return res.send(html);
  }
  res.sendFile(filePath, err => { if (err) next(); });
});

// Catalog: /textbooks → frontend/textbooks.html (explicit to avoid conflict with /textbooks/ directory)
app.get('/textbooks', (_req, res) => {
  if (!isProd) res.setHeader('Cache-Control', 'no-store');
  res.sendFile(path.join(frontendDir, 'textbooks.html'));
});

// ── Exam preparation module: /exam-prep/:examKey/{,variants,practice,topics,mock}[/...]
// All sub-pages share the same examKey segment; sub-view is the second segment.
// The HTML file is chosen by the sub-view; examKey is parsed client-side from URL.
function sendExamPrep(res, file) {
  if (!isProd) res.setHeader('Cache-Control', 'no-store');
  res.sendFile(path.join(frontendDir, file));
}
const EXAM_PREP_VIEWS = {
  variants: 'exam-prep-variants.html',
  practice: 'exam-prep-practice.html',
  topics:   'exam-prep-topics.html',
  mock:     'exam-prep-mock.html',
};
// /exam-prep → redirect to default track (math9 for now)
app.get('/exam-prep', (_req, res) => res.redirect(302, '/exam-prep/math9'));
// /exam-prep/:examKey → dashboard
app.get('/exam-prep/:examKey', (_req, res) => sendExamPrep(res, 'exam-prep.html'));
// /exam-prep/:examKey/:view → corresponding sub-page (sub-view + optional trailing segments)
app.get(['/exam-prep/:examKey/:view', '/exam-prep/:examKey/:view/*'], (req, res, next) => {
  const file = EXAM_PREP_VIEWS[req.params.view];
  if (!file) return next();
  sendExamPrep(res, file);
});

// Legacy /exam9 → new variants browser (F2 port complete)
app.get('/exam9', (_req, res) => res.redirect(301, '/exam-prep/math9/variants'));

// Serve HTML files without extension  (/dashboard → dashboard.html)
// In dev: disable cache so edits are always picked up immediately
const htmlCacheOpts = isProd ? { extensions: ['html'] } : {
  extensions: ['html'],
  setHeaders: (res, filePath) => {
    if (filePath.endsWith('.html')) res.setHeader('Cache-Control', 'no-store');
  },
};
app.use(express.static(frontendDir, htmlCacheOpts));

app.get('/', (_req, res) => res.sendFile(path.join(frontendDir, 'login.html')));
app.get('/login', (_req, res) => res.sendFile(path.join(frontendDir, 'login.html')));

/* ── Custom error pages ── */
app.get('/403', (_req, res) => res.status(403).sendFile(path.join(frontendDir, '403.html')));
app.get('/404', (_req, res) => res.status(404).sendFile(path.join(frontendDir, '404.html')));
app.get('/500', (_req, res) => res.status(500).sendFile(path.join(frontendDir, '500.html')));
/* ── Global error handler ── */
app.use(errorHandler);

app.use((_req, res) => res.status(404).sendFile(path.join(frontendDir, '404.html')));

const server = app.listen(PORT, () => logger.info(`Server running on port ${PORT}`, { env: config.NODE_ENV }));

/* ── WebSocket server for low-latency classroom events (cursor + preview) ── */
require('./ws-server').attach(server);

/* ── Graceful shutdown ── */
function shutdown(signal) {
  logger.info(`${signal} received — shutting down gracefully`);
  // Close WebSocket connections first so HTTP server can stop accepting new requests cleanly
  try { require('./ws-server').closeAll(); } catch (e) { logger.error('ws close error', { err: e.message }); }
  server.close(() => {
    try {
      const _shutDb = require('./db/db');
      _shutDb.exec('PRAGMA optimize');
      _shutDb.close();
    } catch (e) { logger.error('db close error', { err: e.message }); }
    logger.info('Server closed');
    process.exit(0);
  });
  // Force exit after 5s if connections hang
  setTimeout(() => process.exit(1), 5000).unref();
}
process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT',  () => shutdown('SIGINT'));
