/**
 * userRoles.controller.js — auxiliary role grants, class covers, scope queries.
 *
 * Phase 1 PR 1 (2026-07-22) — auxiliary roles thin slice.
 *
 * Endpoints:
 *   GET    /api/users/:id/roles                    — list active + revoked grants
 *   POST   /api/users/:id/roles                    — grant (admin only)
 *   PUT    /api/user-roles/:id                     — update expires_at / reason
 *   DELETE /api/user-roles/:id                     — soft-revoke (admin only)
 *   GET    /api/user-roles/active-covers           — covers for a class on a date
 *
 * The JWT carries effective_roles, but every grant/revoke is also written
 * through AuditService so the action has a paper trail. Controllers do
 * not return effective_roles directly — clients should call
 * /api/auth/refresh-roles after a mutation to pick up the new set.
 */

'use strict';

const express = require('express');
const path = require('path');
const Database = require('better-sqlite3');
const { v4: uuidv4 } = require('uuid');

const dbPath = process.env.DB_PATH || path.join(__dirname, '..', '..', 'data', 'school.db');
let db;
const getDb = () => {
  if (!db) {
    db = new Database(dbPath);
    db.pragma('foreign_keys = ON');
  }
  return db;
};

const router = express.Router();

// Auth middleware
const auth = (req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1];
  if (!token) return res.status(401).json({ error: 'No token provided' });
  try {
    const jwt = require('jsonwebtoken');
    const { jwtSecret: JWT_SECRET } = require('../config');
    req.user = jwt.verify(token, JWT_SECRET);
    next();
  } catch (err) {
    res.status(401).json({ error: 'Invalid token' });
  }
};

const requireAdmin = (req, res, next) => {
  if (!['school_admin', 'systems_admin'].includes(req.user.role)) {
    return res.status(403).json({ error: 'Admin access required' });
  }
  next();
};

const AuditService = require('../services/AuditService');

const SUPPORTED_ROLES = new Set([
  'systems_admin', 'school_admin', 'teacher', 'student', 'parent', 'accountant',
  'librarian', 'nurse', 'clubs_head', 'principal', 'hr', 'bursar', 'dining_staff',
  'driver', 'groundsman', 'matron', 'boarding_master', 'security', 'janitor',
]);

// ---------- GET /api/users/:id/roles -----------------------------------------

router.get('/users/:id/roles', auth, (req, res) => {
  try {
    const userId = Number(req.params.id);
    const rows = getDb()
      .prepare(
        `SELECT ur.id, ur.uid, ur.user_id, ur.role,
                ur.scope_class_id, ur.scope_subject_id, ur.scope_cohort_id,
                ur.starts_at, ur.expires_at, ur.reason,
                ur.granted_by, ur.granted_at,
                ur.revoked_by, ur.revoked_at, ur.revoke_reason,
                c.name AS scope_class_name,
                s.name AS scope_subject_name
           FROM user_roles ur
           LEFT JOIN classes c ON c.id = ur.scope_class_id
           LEFT JOIN subjects s ON s.id = ur.scope_subject_id
           WHERE ur.user_id = ? AND ur.is_deleted = 0
           ORDER BY ur.revoked_at IS NULL DESC, ur.granted_at DESC`
      )
      .all(userId);
    res.json({ grants: rows });
  } catch (err) {
    console.error('userRoles.list error:', err);
    res.status(500).json({ error: 'Internal Server Error', message: err.message });
  }
});

// ---------- POST /api/users/:id/roles ----------------------------------------

router.post('/users/:id/roles', auth, requireAdmin, async (req, res) => {
  try {
    const userId = Number(req.params.id);
    const {
      role,
      scope_class_id = null,
      scope_subject_id = null,
      scope_cohort_id = null,
      starts_at = null,
      expires_at = null,
      reason = null,
    } = req.body;

    if (!role || !SUPPORTED_ROLES.has(role)) {
      return res.status(400).json({ error: `role must be one of: ${Array.from(SUPPORTED_ROLES).join(', ')}` });
    }
    const target = getDb().prepare('SELECT id, role FROM users WHERE id = ? AND is_deleted = 0').get(userId);
    if (!target) return res.status(404).json({ error: 'User not found' });

    // Validate scope FKs if present (best-effort — the FK is enforced by the DB
    // but we'd rather return a friendly error than a 500 from SQLite).
    if (scope_class_id) {
      const c = getDb().prepare('SELECT 1 FROM classes WHERE id = ? AND is_deleted = 0').get(scope_class_id);
      if (!c) return res.status(400).json({ error: `Invalid scope_class_id: ${scope_class_id}` });
    }
    if (scope_subject_id) {
      const s = getDb().prepare('SELECT 1 FROM subjects WHERE id = ? AND is_deleted = 0').get(scope_subject_id);
      if (!s) return res.status(400).json({ error: `Invalid scope_subject_id: ${scope_subject_id}` });
    }

    const uid = uuidv4();
    const result = getDb()
      .prepare(
        `INSERT INTO user_roles
           (uid, user_id, role, scope_class_id, scope_subject_id, scope_cohort_id,
            starts_at, expires_at, reason, granted_by)
           VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
      )
      .run(uid, userId, role, scope_class_id, scope_subject_id, scope_cohort_id,
           starts_at, expires_at, reason, req.user.id);

    const row = getDb().prepare('SELECT * FROM user_roles WHERE id = ?').get(result.lastInsertRowid);

    await AuditService.log(req, 'GRANT_USER_ROLE', {
      entityType: 'user_role',
      entityId: row.id,
      targetUserId: userId,
      role,
      scope_class_id,
      scope_subject_id,
      scope_cohort_id,
      starts_at,
      expires_at,
      reason,
    });

    res.status(201).json(row);
  } catch (err) {
    if (err.code === 'SQLITE_CONSTRAINT_UNIQUE' || /UNIQUE constraint failed: user_roles/.test(err.message)) {
      return res.status(409).json({
        error: 'An active grant already exists for this user, role, and scope. Revoke the existing grant first.',
      });
    }
    console.error('userRoles.grant error:', err);
    res.status(500).json({ error: 'Internal Server Error', message: err.message });
  }
});

// ---------- PUT /api/user-roles/:id ------------------------------------------

router.put('/user-roles/:id', auth, requireAdmin, async (req, res) => {
  try {
    const id = Number(req.params.id);
    const existing = getDb().prepare('SELECT * FROM user_roles WHERE id = ? AND is_deleted = 0').get(id);
    if (!existing) return res.status(404).json({ error: 'Grant not found' });
    if (existing.revoked_at) return res.status(400).json({ error: 'Cannot edit a revoked grant' });

    const { expires_at = existing.expires_at, reason = existing.reason } = req.body;
    getDb()
      .prepare(
        `UPDATE user_roles
           SET expires_at = ?, reason = ?, updated_at = datetime('now','localtime'),
               sync_status = 'pending'
           WHERE id = ?`
      )
      .run(expires_at, reason, id);
    const row = getDb().prepare('SELECT * FROM user_roles WHERE id = ?').get(id);

    await AuditService.log(req, 'UPDATE_USER_ROLE', {
      entityType: 'user_role',
      entityId: id,
      expires_at,
      reason,
    });
    res.json(row);
  } catch (err) {
    console.error('userRoles.update error:', err);
    res.status(500).json({ error: 'Internal Server Error', message: err.message });
  }
});

// ---------- DELETE /api/user-roles/:id ---------------------------------------

router.delete('/user-roles/:id', auth, requireAdmin, async (req, res) => {
  try {
    const id = Number(req.params.id);
    const existing = getDb().prepare('SELECT * FROM user_roles WHERE id = ? AND is_deleted = 0').get(id);
    if (!existing) return res.status(404).json({ error: 'Grant not found' });
    if (existing.revoked_at) return res.status(400).json({ error: 'Grant already revoked' });

    const { revoke_reason = null } = req.body || {};
    getDb()
      .prepare(
        `UPDATE user_roles
           SET revoked_at = datetime('now','localtime'),
               revoked_by = ?,
               revoke_reason = ?,
               updated_at = datetime('now','localtime'),
               sync_status = 'pending'
           WHERE id = ?`
      )
      .run(req.user.id, revoke_reason, id);

    await AuditService.log(req, 'REVOKE_USER_ROLE', {
      entityType: 'user_role',
      entityId: id,
      targetUserId: existing.user_id,
      role: existing.role,
      scope_class_id: existing.scope_class_id,
      scope_subject_id: existing.scope_subject_id,
      scope_cohort_id: existing.scope_cohort_id,
      revoke_reason,
    });
    res.json({ ok: true, id });
  } catch (err) {
    console.error('userRoles.revoke error:', err);
    res.status(500).json({ error: 'Internal Server Error', message: err.message });
  }
});

// ---------- GET /api/user-roles/active-covers -------------------------------

router.get('/user-roles/active-covers', auth, (req, res) => {
  try {
    const classId = Number(req.query.class_id);
    const date = (req.query.date || new Date().toISOString().slice(0, 10)).toString();
    if (!classId) return res.status(400).json({ error: 'class_id is required' });

    const rows = getDb()
      .prepare(
        `SELECT ur.id, ur.user_id, ur.role, ur.scope_class_id, ur.scope_subject_id,
                ur.starts_at, ur.expires_at, ur.reason, ur.granted_by, ur.granted_at,
                u.first_name, u.last_name, u.email
           FROM user_roles ur
           JOIN users u ON u.id = ur.user_id
           WHERE ur.scope_class_id = ?
             AND ur.is_deleted = 0
             AND ur.revoked_at IS NULL
             AND (ur.starts_at IS NULL OR datetime(ur.starts_at) <= datetime('now','localtime'))
             AND (ur.expires_at IS NULL OR date(ur.expires_at) >= ?)
           ORDER BY ur.granted_at DESC`
      )
      .all(classId, date);
    res.json({ class_id: classId, date, covers: rows });
  } catch (err) {
    console.error('userRoles.activeCovers error:', err);
    res.status(500).json({ error: 'Internal Server Error', message: err.message });
  }
});

module.exports = router;
