/**
 * Role-based access control middleware.
 *
 * One helper per common pattern. Controllers compose them with `auth`:
 *
 *   const auth = require('./shared/auth-middleware');
 *   const { requireRole, requireStaff, requireAdmin, requireOwnerOrAdmin } =
 *     require('../middleware/requireRole');
 *
 *   router.post('/', auth, requireRole('teacher', 'school_admin'), handler);
 *   router.get('/', auth, requireStaff, listHandler);
 *   router.delete('/:uid', auth, requireAdmin, deleteHandler);
 *
 * All middlewares return 401 if there is no req.user (defence-in-depth in
 * case a route is wired without `auth` upstream) and 403 if the role
 * doesn't match. The JSON shape matches what other controllers already
 * return so callers don't need to special-case errors.
 *
 * The role constants are exported as ROLE_* so callers can write
 * `requireRole(ROLE.PRINCIPAL, ROLE.SYSTEMS_ADMIN)` instead of repeating
 * string literals.
 */

const ROLE = Object.freeze({
  SYSTEMS_ADMIN:   'systems_admin',
  SCHOOL_ADMIN:    'school_admin',
  PRINCIPAL:       'principal',
  HR:              'hr',
  BURSAR:          'bursar',
  TEACHER:         'teacher',
  STUDENT:         'student',
  PARENT:          'parent',
  LIBRARIAN:       'librarian',
  CLUBS_HEAD:      'clubs_head',
  ACCOUNTANT:      'accountant',
  NURSE:           'nurse',
  DINING_STAFF:    'dining_staff',
});

const ADMIN_ROLES = [ROLE.SYSTEMS_ADMIN, ROLE.SCHOOL_ADMIN];
const ADMIN_AND_PRINCIPAL = [ROLE.SYSTEMS_ADMIN, ROLE.SCHOOL_ADMIN, ROLE.PRINCIPAL];
const STAFF_ROLES = [
  ...ADMIN_AND_PRINCIPAL,
  ROLE.HR, ROLE.BURSAR, ROLE.ACCOUNTANT, ROLE.NURSE, ROLE.DINING_STAFF,
];
const ACADEMIC_ROLES = [...ADMIN_AND_PRINCIPAL, ROLE.TEACHER, ROLE.LIBRARIAN, ROLE.CLUBS_HEAD];
const ALL_ROLES = Object.values(ROLE);

/**
 * Factory: only allow the listed roles.
 * Usage: `router.get('/admin', auth, requireRole('school_admin','systems_admin'), handler)`
 */
const requireRole = (...allowed) => {
  const allowSet = new Set(allowed);
  return (req, res, next) => {
    if (!req.user || !req.user.role) {
      return res.status(401).json({ error: 'Unauthenticated' });
    }
    if (!allowSet.has(req.user.role)) {
      return res.status(403).json({ error: 'Forbidden: insufficient role' });
    }
    return next();
  };
};

/**
 * Shorthand: any school administrator or principal. Use for routes
 * that map to View/CRUD cells for principal in SCREENS.md (e.g. /users,
 * /departments).
 */
const requireAdmin = requireRole(...ADMIN_AND_PRINCIPAL);

/**
 * Stricter variant: only systems_admin or school_admin. Use when
 * principal read access is not granted (e.g. /audit-logs, settings,
 * academic rollover execution).
 */
const requireStrictAdmin = requireRole(ROLE.SYSTEMS_ADMIN, ROLE.SCHOOL_ADMIN);

/**
 * Shorthand: any "staff" role — anyone who is not a student, parent, or
 * teacher-supervised role. Use for routes that are teacher/collab-shared.
 */
const requireStaff = requireRole(...STAFF_ROLES, ROLE.TEACHER, ROLE.LIBRARIAN, ROLE.CLUBS_HEAD);

/**
 * Allow admin or the user whose id matches `req.params.userId` / `req.user.id`.
 * Useful for "edit your own profile" routes.
 */
const requireOwnerOrAdmin = (req, res, next) => {
  if (!req.user || !req.user.role) {
    return res.status(401).json({ error: 'Unauthenticated' });
  }
  const paramId =
    parseInt(req.params.userId, 10) ||
    parseInt(req.params.id, 10) ||
    parseInt(req.params.studentId, 10);
  if (ADMIN_ROLES.includes(req.user.role)) return next();
  if (paramId && paramId === req.user.id) return next();
  if (req.user.role === ROLE.PARENT) {
    // Parents get a separate scope check via the controller; this
    // middleware only opens the gate.
    return next();
  }
  return res.status(403).json({ error: 'Forbidden: not the owner' });
};

module.exports = {
  ROLE,
  requireRole,
  requireAdmin,
  requireStrictAdmin,
  requireStaff,
  requireOwnerOrAdmin,
  ADMIN_ROLES,
  ADMIN_AND_PRINCIPAL,
  STAFF_ROLES,
  ACADEMIC_ROLES,
  ALL_ROLES,
};
