/**
 * routes/admin.js — Admin panel routes
 * Connecticut Elderly Services Training Platform
 *
 * Admin authentication: cPanel MariaDB/MySQL via lib/mysqldb.js
 *   - ALL admin user lookups, password verification, and password changes
 *     go through MySQL. There are NO fallbacks and NO default passwords.
 *   - If MySQL is not configured the login page shows a clear error.
 *
 * Learner / agency data: SQLite via lib/db.js — unchanged.
 */

const express    = require('express');
const router     = express.Router();
const crypto     = require('crypto');
const rateLimit  = require('express-rate-limit');
const db         = require('../lib/db');        // SQLite — learner data only
const mysqldb    = require('../lib/mysqldb');   // MySQL  — admin auth only
const version    = require('../lib/version');

// ─── Rate limiters ────────────────────────────────────────────────────────────

// Login: max 10 attempts per 15 minutes per IP
const loginLimiter = rateLimit({
  windowMs:       15 * 60 * 1000,
  max:            10,
  standardHeaders: true,
  legacyHeaders:  false,
  handler: (req, res) => {
    res.status(429).render('admin/login', {
      error: 'Too many login attempts. Please wait 15 minutes and try again.',
      title: 'Admin Login'
    });
  }
});

// Bootstrap: max 5 attempts per 15 minutes per IP
const bootstrapLimiter = rateLimit({
  windowMs:       15 * 60 * 1000,
  max:            5,
  standardHeaders: true,
  legacyHeaders:  false,
  handler: (req, res) => {
    res.status(429).render('error', {
      message: 'Too many setup attempts. Please wait 15 minutes and try again.'
    });
  }
});

// ─── CSRF helpers ─────────────────────────────────────────────────────────────
// Synchronizer Token Pattern: a random token is stored in the session on each
// GET and must be echoed back as a hidden field on every state-changing POST.

function generateCsrfToken(req) {
  if (!req.session.csrfToken) {
    req.session.csrfToken = crypto.randomBytes(32).toString('hex');
  }
  return req.session.csrfToken;
}

function requireCsrf(req, res, next) {
  const sessionToken = req.session && req.session.csrfToken;
  const bodyToken    = req.body   && req.body._csrf;

  let valid = false;
  try {
    if (sessionToken && bodyToken) {
      const a = Buffer.from(sessionToken, 'utf8');
      const b = Buffer.from(bodyToken,    'utf8');
      valid = (a.length === b.length) && crypto.timingSafeEqual(a, b);
    }
  } catch { valid = false; }

  if (!valid) {
    return res.status(403).render('error', {
      message: 'Invalid form submission — please go back and try again.'
    });
  }
  next();
}

// ─── Auth middleware ──────────────────────────────────────────────────────────

function requireAdmin(req, res, next) {
  if (!req.session.adminAuthenticated) {
    return res.redirect('/admin/login');
  }
  next();
}

// ─── GET /admin/version ───────────────────────────────────────────────────────
// Public — shows only the build ID and auth backend. No DB credentials.
// Visit this URL after uploading a new zip to confirm the new code is live.
// Once deployment is verified, you can ignore this route (it reveals nothing
// sensitive).

router.get('/version', (req, res) => {
  res.json({
    build:       version.BUILD_ID,
    authBackend: version.AUTH_BACKEND
  });
});

// ─── GET /admin/login ─────────────────────────────────────────────────────────

router.get('/login', (req, res) => {
  if (req.session.adminAuthenticated) return res.redirect('/admin/dashboard');
  res.render('admin/login', { error: null, title: 'Admin Login' });
});

// ─── POST /admin/login ─── rate-limited ───────────────────────────────────────

router.post('/login', loginLimiter, async (req, res) => {
  const { username, password } = req.body;

  if (!username || !password) {
    return res.render('admin/login', {
      error: 'Username and password are required.',
      title: 'Admin Login'
    });
  }

  const GENERIC_ERROR = 'Invalid username or password.';

  // Explicitly block the legacy default credentials before touching the database.
  // Returns the same generic error so timing and response are indistinguishable
  // from any other failed login — even if this row still exists in the DB.
  if (
    username.trim().toLowerCase() === 'admin' &&
    password === 'admin2024'
  ) {
    return res.render('admin/login', { error: GENERIC_ERROR, title: 'Admin Login' });
  }

  try {
    const admin = await mysqldb.verifyAdminLogin(username.trim().toLowerCase(), password);

    if (!admin) {
      return res.render('admin/login', { error: GENERIC_ERROR, title: 'Admin Login' });
    }

    // Regenerate session ID before writing credentials (prevents session fixation)
    req.session.regenerate(regenErr => {
      if (regenErr) {
        console.error('[admin] Session regenerate error:', regenErr);
        return res.render('admin/login', {
          error: 'Session error — please try again.',
          title: 'Admin Login'
        });
      }

      req.session.adminAuthenticated = true;
      req.session.adminUserId        = admin.id;
      req.session.adminUsername      = admin.username;

      req.session.save(saveErr => {
        if (saveErr) {
          console.error('[admin] Session save error:', saveErr);
          return res.render('admin/login', {
            error: 'Session error — please try again.',
            title: 'Admin Login'
          });
        }
        return res.redirect('/admin/dashboard');
      });
    });

  } catch (err) {
    console.error('[admin] Login error:', err);
    const mysqlErr = err.code === 'ECONNREFUSED' || err.code === 'ER_ACCESS_DENIED_ERROR'
      || String(err).includes('connect');
    res.render('admin/login', {
      error: mysqlErr
        ? 'Cannot connect to the admin database. Check DB environment variables in cPanel.'
        : 'An error occurred. Please try again.',
      title: 'Admin Login'
    });
  }
});

// ─── GET /admin/logout ────────────────────────────────────────────────────────

router.get('/logout', (req, res) => {
  req.session.destroy(() => res.redirect('/admin/login'));
});

// ─── GET /admin/dashboard ─────────────────────────────────────────────────────

router.get('/dashboard', requireAdmin, async (req, res) => {
  try {
    const [stats, learners, agencies] = await Promise.all([
      db.getLearnerStats(),
      db.getAllLearners(),
      db.getAllAgencies()
    ]);
    res.render('admin/dashboard', {
      stats,
      learners,
      agencies,
      adminUsername: req.session.adminUsername,
      csrfToken:    generateCsrfToken(req),
      title: 'Admin Dashboard'
    });
  } catch (err) {
    console.error('[admin] Dashboard error:', err);
    res.status(500).render('error', { message: 'Could not load dashboard. Please try again.' });
  }
});

// ─── GET /admin/change-password ───────────────────────────────────────────────

router.get('/change-password', requireAdmin, (req, res) => {
  res.render('admin/change-password', {
    error: null, success: null, title: 'Change Password',
    csrfToken: generateCsrfToken(req)
  });
});

// ─── POST /admin/change-password ─── CSRF protected ──────────────────────────

router.post('/change-password', requireAdmin, requireCsrf, async (req, res) => {
  const { currentPassword, newPassword, confirmPassword } = req.body;
  const render = (error, success) =>
    res.render('admin/change-password', {
      error, success, title: 'Change Password',
      csrfToken: generateCsrfToken(req)
    });

  if (!currentPassword || !newPassword || !confirmPassword) {
    return render('All fields are required.', null);
  }
  if (newPassword.length < 8) {
    return render('New password must be at least 8 characters.', null);
  }
  if (newPassword !== confirmPassword) {
    return render('New passwords do not match.', null);
  }

  try {
    const admin = await mysqldb.verifyAdminLogin(
      req.session.adminUsername,
      currentPassword
    );
    if (!admin) {
      return render('Current password is incorrect.', null);
    }
    await mysqldb.changeAdminPassword(req.session.adminUserId, newPassword);
    render(null, 'Password updated successfully. Use your new password next time you log in.');
  } catch (err) {
    console.error('[admin] Change password error:', err);
    render('An error occurred. Please try again.', null);
  }
});

// ─── Bootstrap: one-time first admin creation ─────────────────────────────────
//
// GET/POST /admin/bootstrap?token=YOUR_ADMIN_SETUP_TOKEN
//
// Guards on every request:
//   1. ADMIN_SETUP_TOKEN must be set in cPanel environment variables.
//   2. The ?token= value must match ADMIN_SETUP_TOKEN (timing-safe compare).
//   3. No admin user may already exist in MySQL (auto-closes after first use).
//   4. CSRF token must match session (POST only).
//
// ──────────────────────────────────────────────────────────────────────────────

function checkBootstrapToken(req, res) {
  const envToken = process.env.ADMIN_SETUP_TOKEN;
  if (!envToken) {
    res.status(403).render('error', {
      message: 'Bootstrap is disabled. Add ADMIN_SETUP_TOKEN to cPanel environment variables to enable it.'
    });
    return false;
  }
  const provided = (req.body && req.body.token) || req.query.token || '';
  let match = false;
  try {
    const a = Buffer.from(envToken, 'utf8');
    const b = Buffer.from(provided, 'utf8');
    if (a.length === b.length) match = crypto.timingSafeEqual(a, b);
  } catch { match = false; }

  if (!match) {
    res.status(403).render('error', { message: 'Invalid or missing setup token.' });
    return false;
  }
  return true;
}

router.get('/bootstrap', bootstrapLimiter, async (req, res) => {
  if (!checkBootstrapToken(req, res)) return;

  try {
    const existing = await mysqldb.getAnyAdminUser();
    if (existing) {
      return res.status(403).render('error', {
        message: 'An admin account already exists. Bootstrap is permanently disabled.'
      });
    }
  } catch (err) {
    console.error('[bootstrap] MySQL check error:', err);
    return res.status(500).render('error', {
      message: 'Cannot connect to the MySQL database. Check DB environment variables in cPanel.'
    });
  }

  res.render('admin/bootstrap', {
    token: req.query.token, error: null, success: null, title: 'Create First Admin',
    csrfToken: generateCsrfToken(req)
  });
});

router.post('/bootstrap', bootstrapLimiter, async (req, res) => {
  if (!checkBootstrapToken(req, res)) return;

  // CSRF check (inline — bootstrap has no requireAdmin middleware)
  const sessionToken = req.session && req.session.csrfToken;
  const bodyToken    = req.body   && req.body._csrf;
  let csrfValid = false;
  try {
    if (sessionToken && bodyToken) {
      const a = Buffer.from(sessionToken, 'utf8');
      const b = Buffer.from(bodyToken,    'utf8');
      csrfValid = (a.length === b.length) && crypto.timingSafeEqual(a, b);
    }
  } catch { csrfValid = false; }

  if (!csrfValid) {
    return res.status(403).render('error', {
      message: 'Invalid form submission — please go back and try again.'
    });
  }

  const render = (error, success) =>
    res.render('admin/bootstrap', {
      token: req.body.token, error, success, title: 'Create First Admin',
      csrfToken: generateCsrfToken(req)
    });

  try {
    const existing = await mysqldb.getAnyAdminUser();
    if (existing) {
      return res.status(403).render('error', {
        message: 'An admin account already exists. Bootstrap is permanently disabled.'
      });
    }
  } catch (err) {
    console.error('[bootstrap] MySQL check error:', err);
    return res.status(500).render('error', {
      message: 'Cannot connect to the MySQL database. Check DB environment variables in cPanel.'
    });
  }

  const { username, password, confirmPassword } = req.body;
  if (!username || !password || !confirmPassword) return render('All fields are required.', null);
  if (username.trim().length < 3) return render('Username must be at least 3 characters.', null);
  if (password.length < 8) return render('Password must be at least 8 characters.', null);
  if (password !== confirmPassword) return render('Passwords do not match.', null);

  // Block legacy default credentials from being re-created via bootstrap.
  const u = username.trim().toLowerCase();
  if (u === 'admin') {
    return render('Username "admin" is not allowed. Please choose a different username.', null);
  }
  if (password === 'admin2024') {
    return render('That password is not allowed. Please choose a stronger password.', null);
  }

  try {
    await mysqldb.addAdminUser(u, u, password, 'superadmin');
    console.log(`[bootstrap] First admin created in MySQL: "${u}"`);
    render(null,
      `Admin account "${u}" created in MySQL. ` +
      `You can now log in at /admin/login. ` +
      `Remove ADMIN_SETUP_TOKEN from cPanel environment variables to permanently disable this route.`
    );
  } catch (err) {
    console.error('[bootstrap] Create admin error:', err);
    render('Failed to create admin account. ' +
      'Check that the admin_users table exists in your MySQL database.', null);
  }
});

// ─── POST /admin/agencies/add ─── CSRF protected ─────────────────────────────

router.post('/agencies/add', requireAdmin, requireCsrf, async (req, res) => {
  const { name, code } = req.body;
  if (!name || !code) {
    return res.status(400).json({ error: 'Name and code are required.' });
  }
  try {
    await db.addAgency(name, code.toUpperCase());
    res.json({ success: true });
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});

module.exports = router;
