/**
 * Enrollments Controller
 * Handles Student Class Enrollments with 4-state workflow
 *   request_status: pending | approved | rejected | withdrawn
 *
 * Endpoints:
 *   GET    /api/enrollments                       list (filtered; students see own only)
 *   POST   /api/enrollments                       student self-enrol -> pending
 *                                               admin/teacher force-enrol -> approved
 *   GET    /api/enrollments/pending               admin/teacher only
 *   POST   /api/enrollments/:id/approve           admin/teacher only
 *   POST   /api/enrollments/:id/reject            admin/teacher only (sets rejection_reason)
 *   POST   /api/enrollments/:id/withdraw          student (own row only) or admin/teacher
 *   POST   /api/enrollments/:id/transfer          admin/teacher only (Phase 1 PR 3)
 *   DELETE /api/enrollments/:id                   admin-only soft delete (preserved)
 */

const express = require('express');
const router = express.Router();
const { v4: uuidv4 } = require('uuid');
const AuditService = require('../services/AuditService');

const dbPath = process.env.DB_PATH || require('path').join(__dirname, '../../data/school.db');
const Database = require('better-sqlite3');
let db;

const getDb = () => {
  if (!db) {
    db = new Database(dbPath);
    db.pragma('foreign_keys = ON');
  }
  return db;
};

// 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' });
  }
};

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

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

// ============ LIST ENROLLMENTS ============

/**
 * @openapi
 * /api/enrollments:
 *   get:
 *     summary: List class enrollments
 *     description: |
 *       Returns class enrollments, filterable by `student_id`,
 *       `class_id`, `status`, and `request_status`. Students only see
 *       their own enrollments; staff see all.
 *     tags:
 *       - Enrollments
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: query
 *         name: student_id
 *         schema: { type: integer }
 *       - in: query
 *         name: class_id
 *         schema: { type: integer }
 *       - in: query
 *         name: status
 *         schema: { type: string }
 *       - in: query
 *         name: request_status
 *         schema: { type: string }
 *     responses:
 *       200:
 *         description: Enrollment list
 *         content:
 *           application/json:
 *             schema:
 *               type: array
 *               items: { type: object }
 *       401: { description: Unauthorized }
 *       500: { description: Server error }
 *   post:
 *     summary: Enrol a student in a class
 *     description: |
 *       Creates a new `enrollments` row. A student self-enrolling
 *       produces a `pending` request that requires admin/teacher
 *       approval; staff (admin, systems_admin, principal, teacher)
 *       force-enrol produce an immediately `approved` row. Idempotent
 *       on duplicate `(student_id, class_id)`.
 *     tags:
 *       - Enrollments
 *     security:
 *       - bearerAuth: []
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             required:
 *               - class_id
 *             properties:
 *               class_id: { type: integer }
 *               student_id:
 *                 type: integer
 *                 description: Staff-only override; defaults to caller's id for students
 *               roll_number: { type: string }
 *               academic_year: { type: string }
 *               section: { type: string }
 *     responses:
 *       201:
 *         description: Created enrollment
 *         content:
 *           application/json:
 *             schema: { type: object }
 *       400: { description: Validation error }
 *       403: { description: Students may only enrol themselves }
 *       404: { description: Class or student not found }
 *       500: { description: Server error }
 */
router.get('/', auth, (req, res) => {
  try {
    const { student_id, class_id, status, request_status } = req.query;
    let query = 'SELECT * FROM enrollments WHERE is_deleted = 0';
    const params = [];

    if (student_id) {
      query += ' AND student_id = ?';
      params.push(student_id);
    }
    if (class_id) {
      query += ' AND class_id = ?';
      params.push(class_id);
    }
    if (status) {
      query += ' AND status = ?';
      params.push(status);
    }
    if (request_status) {
      query += ' AND request_status = ?';
      params.push(request_status);
    }

    // Students only see their own enrollments
    if (req.user.role === 'student') {
      query += ' AND student_id = ?';
      params.push(req.user.id);
    }

    query += ' ORDER BY created_at DESC';
    const list = getDb().prepare(query).all(...params);
    res.json(list);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// ============ LIST PENDING ENROLLMENTS (admin/teacher) ============

/**
 * @openapi
 * /api/enrollments/pending:
 *   get:
 *     summary: List pending enrollments
 *     description: |
 *       Returns class enrollments with `request_status = 'pending'`
 *       (or NULL), joined with student and class details. Restricted
 *       to admin or teacher roles.
 *     tags:
 *       - Enrollments
 *     security:
 *       - bearerAuth: []
 *     responses:
 *       200:
 *         description: Pending enrollment list
 *         content:
 *           application/json:
 *             schema:
 *               type: array
 *               items: { type: object }
 *       401: { description: Unauthorized }
 *       403: { description: Admin or Teacher only }
 *       500: { description: Server error }
 */
router.get('/pending', auth, adminOrTeacher, (req, res) => {
  try {
    const rows = getDb().prepare(`
      SELECT e.*, u.first_name AS student_first_name, u.last_name AS student_last_name,
             u.email AS student_email,
             c.name AS class_name, c.section AS class_section
      FROM enrollments e
      JOIN users u ON e.student_id = u.id
      JOIN classes c ON e.class_id = c.id
      WHERE e.is_deleted = 0
        AND (e.request_status IS NULL OR e.request_status = 'pending')
      ORDER BY COALESCE(e.requested_at, e.created_at) DESC
    `).all();
    res.json(rows);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// ============ CREATE ENROLLMENT (self or force) ============

router.post('/', auth, (req, res) => {
  try {
    const { student_id, class_id, roll_number, academic_year, section } = req.body;
    if (!class_id) {
      return res.status(400).json({ error: 'class_id is required' });
    }

    const database = getDb();

    // Resolve student_id: students can only self-enrol; staff pick a student.
    let resolvedStudentId = student_id;
    let isSelfEnrol = false;
    if (req.user.role === 'student') {
      if (student_id && Number(student_id) !== Number(req.user.id)) {
        return res.status(403).json({ error: 'Students may only enrol themselves' });
      }
      resolvedStudentId = req.user.id;
      isSelfEnrol = true;
    } else if (!['school_admin', 'systems_admin', 'principal', 'teacher'].includes(req.user.role)) {
      return res.status(403).json({ error: 'Insufficient role to enrol students' });
    } else if (!resolvedStudentId) {
      return res.status(400).json({ error: 'student_id is required for staff-initiated enrolment' });
    }

    // Validate class
    const classExists = database.prepare('SELECT id, section FROM classes WHERE id = ? AND is_deleted = 0').get(class_id);
    if (!classExists) {
      return res.status(404).json({ error: 'Class not found' });
    }

    // Validate student exists with role=student
    const studentExists = database.prepare("SELECT id, role FROM users WHERE id = ? AND is_deleted = 0").get(resolvedStudentId);
    if (!studentExists) {
      return res.status(404).json({ error: 'Student user not found' });
    }
    if (studentExists.role !== 'student') {
      return res.status(400).json({ error: 'User is not a student' });
    }

    // Idempotency: if a row already exists for (student, class), return it.
    const existing = database.prepare(`
      SELECT * FROM enrollments
      WHERE student_id = ? AND class_id = ? AND is_deleted = 0
      ORDER BY id DESC LIMIT 1
    `).get(resolvedStudentId, class_id);

    if (existing) {
      return res.json({ ...existing, _idempotent: true });
    }

    const year = academic_year || new Date().getFullYear().toString();
    const finalSection = section || classExists.section || '';
    const uid = uuidv4();

    // Self-enrol -> pending; staff force-enrol -> approved
    const requestStatus = isSelfEnrol ? 'pending' : 'approved';
    const now = new Date().toISOString();
    const approvedBy = isSelfEnrol ? null : req.user.id;
    const approvedAt = isSelfEnrol ? null : now;
    const status = isSelfEnrol ? 'inactive' : 'active';
    const requestedAt = isSelfEnrol ? now : null;

    const result = database.prepare(`
      INSERT INTO enrollments (
        uid, student_id, class_id, roll_number, academic_year, section,
        status, request_status, requested_at, approved_by, approved_at,
        sync_status
      )
      VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending')
    `).run(
      uid, resolvedStudentId, class_id,
      roll_number || null, year, finalSection,
      status, requestStatus, requestedAt, approvedBy, approvedAt
    );

    const created = database.prepare('SELECT * FROM enrollments WHERE id = ?').get(result.lastInsertRowid);
    res.status(201).json(created);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// ============ BULK APPROVE ENROLLMENTS (admin/teacher) ============
router.post('/bulk-approve', auth, adminOrTeacher, (req, res) => {
  try {
    const { ids } = req.body || {};
    if (!Array.isArray(ids) || ids.length === 0) {
      return res.status(400).json({ error: 'ids array required' });
    }

    const database = getDb();
    const stmt = database.prepare(`
      UPDATE enrollments
      SET request_status   = 'approved',
          status           = 'active',
          approved_by      = ?,
          approved_at      = datetime('now', 'localtime'),
          rejection_reason = NULL,
          updated_at       = datetime('now', 'localtime'),
          sync_status      = 'pending'
      WHERE id = ? AND is_deleted = 0
    `);

    const transaction = database.transaction((targetIds) => {
      let count = 0;
      for (const id of targetIds) {
        const info = stmt.run(req.user.id, id);
        count += info.changes;
      }
      return count;
    });

    const count = transaction(ids);
    res.json({ message: `Successfully approved ${count} enrollment(s)`, count });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// ============ BULK REJECT ENROLLMENTS (admin/teacher) ============
router.post('/bulk-reject', auth, adminOrTeacher, (req, res) => {
  try {
    const { ids, rejection_reason } = req.body || {};
    if (!Array.isArray(ids) || ids.length === 0) {
      return res.status(400).json({ error: 'ids array required' });
    }

    const database = getDb();
    const stmt = database.prepare(`
      UPDATE enrollments
      SET request_status   = 'rejected',
          status           = 'inactive',
          rejection_reason = ?,
          approved_by      = NULL,
          approved_at      = NULL,
          updated_at       = datetime('now', 'localtime'),
          sync_status      = 'pending'
      WHERE id = ? AND is_deleted = 0
    `);

    const transaction = database.transaction((targetIds) => {
      let count = 0;
      for (const id of targetIds) {
        const info = stmt.run(rejection_reason || null, id);
        count += info.changes;
      }
      return count;
    });

    const count = transaction(ids);
    res.json({ message: `Successfully rejected ${count} enrollment(s)`, count });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// ============ APPROVE ENROLLMENT (admin/teacher) ============

/**
 * @openapi
 * /api/enrollments/{id}/approve:
 *   post:
 *     summary: Approve an enrollment
 *     description: |
 *       Approves the class enrollment: sets `request_status` to
 *       `approved`, activates the row, records the approver, and
 *       clears any rejection reason. Restricted to admin or teacher
 *       roles.
 *     tags:
 *       - Enrollments
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: id
 *         required: true
 *         schema: { type: integer }
 *     responses:
 *       200:
 *         description: Approved enrollment
 *         content:
 *           application/json:
 *             schema: { type: object }
 *       401: { description: Unauthorized }
 *       403: { description: Admin or Teacher only }
 *       404: { description: Enrollment not found }
 *       500: { description: Server error }
 */
router.post('/:id/approve', auth, adminOrTeacher, (req, res) => {
  try {
    const database = getDb();
    const existing = database.prepare('SELECT * FROM enrollments WHERE id = ? AND is_deleted = 0').get(req.params.id);
    if (!existing) {
      return res.status(404).json({ error: 'Enrollment not found' });
    }

    database.prepare(`
      UPDATE enrollments
      SET request_status   = 'approved',
          status           = 'active',
          approved_by      = ?,
          approved_at      = datetime('now', 'localtime'),
          rejection_reason = NULL,
          updated_at       = datetime('now', 'localtime'),
          sync_status      = 'pending'
      WHERE id = ?
    `).run(req.user.id, req.params.id);

    const updated = database.prepare('SELECT * FROM enrollments WHERE id = ?').get(req.params.id);
    res.json(updated);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// ============ REJECT ENROLLMENT (admin/teacher) ============

/**
 * @openapi
 * /api/enrollments/{id}/reject:
 *   post:
 *     summary: Reject an enrollment
 *     description: |
 *       Rejects the class enrollment: sets `request_status` to
 *       `rejected`, deactivates the row, clears the approver, and
 *       stores an optional rejection reason from the request body.
 *       Restricted to admin or teacher roles.
 *     tags:
 *       - Enrollments
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: id
 *         required: true
 *         schema: { type: integer }
 *     requestBody:
 *       required: false
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             properties:
 *               reason: { type: string }
 *     responses:
 *       200:
 *         description: Rejected enrollment
 *         content:
 *           application/json:
 *             schema: { type: object }
 *       401: { description: Unauthorized }
 *       403: { description: Admin or Teacher only }
 *       404: { description: Enrollment not found }
 *       500: { description: Server error }
 */
router.post('/:id/reject', auth, adminOrTeacher, (req, res) => {
  try {
    const { rejection_reason } = req.body || {};
    const database = getDb();
    const existing = database.prepare('SELECT * FROM enrollments WHERE id = ? AND is_deleted = 0').get(req.params.id);
    if (!existing) {
      return res.status(404).json({ error: 'Enrollment not found' });
    }

    database.prepare(`
      UPDATE enrollments
      SET request_status   = 'rejected',
          status           = 'inactive',
          rejection_reason = ?,
          approved_by      = NULL,
          approved_at      = NULL,
          updated_at       = datetime('now', 'localtime'),
          sync_status      = 'pending'
      WHERE id = ?
    `).run(rejection_reason || null, req.params.id);

    const updated = database.prepare('SELECT * FROM enrollments WHERE id = ?').get(req.params.id);
    res.json(updated);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// ============ WITHDRAW ENROLLMENT ============
// Student must own the row; admin/teacher can withdraw any.

/**
 * @openapi
 * /api/enrollments/{id}/withdraw:
 *   post:
 *     summary: Withdraw an enrollment
 *     description: |
 *       Withdraws the class enrollment: sets `request_status` to
 *       `withdrawn` and deactivates the row. Students may only
 *       withdraw their own enrollment; staff (admin, systems_admin,
 *       principal, teacher) may withdraw any.
 *     tags:
 *       - Enrollments
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: id
 *         required: true
 *         schema: { type: integer }
 *     requestBody:
 *       required: false
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             properties:
 *               reason: { type: string }
 *     responses:
 *       200:
 *         description: Withdrawn enrollment
 *         content:
 *           application/json:
 *             schema: { type: object }
 *       401: { description: Unauthorized }
 *       403: { description: Students may only withdraw their own enrollment }
 *       404: { description: Enrollment not found }
 *       500: { description: Server error }
 */
router.post('/:id/withdraw', auth, (req, res) => {
  try {
    const database = getDb();
    const existing = database.prepare('SELECT * FROM enrollments WHERE id = ? AND is_deleted = 0').get(req.params.id);
    if (!existing) {
      return res.status(404).json({ error: 'Enrollment not found' });
    }

    const isStaff = ['school_admin', 'systems_admin', 'principal', 'teacher'].includes(req.user.role);
    const isOwner = Number(existing.student_id) === Number(req.user.id);
    if (!isStaff && !isOwner) {
      return res.status(403).json({ error: 'Students may only withdraw their own enrollment' });
    }

    database.prepare(`
      UPDATE enrollments
      SET request_status = 'withdrawn',
          status         = 'inactive',
          updated_at     = datetime('now', 'localtime'),
          sync_status    = 'pending'
      WHERE id = ?
    `).run(req.params.id);

    const updated = database.prepare('SELECT * FROM enrollments WHERE id = ?').get(req.params.id);
    res.json(updated);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// ============ TRANSFER ENROLLMENT (admin/teacher) ============
// Closes the current row by setting status='transferred' and
// request_status='transferred' (preserves history), then creates a new
// active/approved row in the destination class. Wrapped in a
// transaction so the pair commits atomically.

/**
 * @openapi
 * /api/enrollments/{id}/transfer:
 *   post:
 *     summary: Transfer a student to another class
 *     description: |
 *       Atomically closes the current enrollment
 *       (`status='transferred'`, `request_status='transferred'`) and
 *       creates a new active/approved enrollment in `newClassId`. The
 *       transferred row keeps `is_deleted=0` so the transfer is
 *       traceable in the student's history. Restricted to admin or
 *       teacher roles.
 *     tags:
 *       - Enrollments
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: id
 *         required: true
 *         schema: { type: integer }
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             required:
 *               - newClassId
 *             properties:
 *               newClassId: { type: integer }
 *               reason: { type: string }
 *     responses:
 *       200:
 *         description: Transferred enrollment (returns the new active row)
 *       400: { description: Validation error }
 *       401: { description: Unauthorized }
 *       403: { description: Admin or teacher only }
 *       404: { description: Enrollment or destination class not found }
 *       500: { description: Server error }
 */
router.post('/:id/transfer', auth, adminOrTeacher, (req, res) => {
  try {
    const { newClassId, reason } = req.body || {};
    if (!newClassId) {
      return res.status(400).json({ error: 'newClassId is required' });
    }
    const database = getDb();

    const existing = database.prepare('SELECT * FROM enrollments WHERE id = ? AND is_deleted = 0').get(req.params.id);
    if (!existing) return res.status(404).json({ error: 'Enrollment not found' });

    const destClass = database.prepare('SELECT id, section FROM classes WHERE id = ? AND is_deleted = 0').get(newClassId);
    if (!destClass) return res.status(404).json({ error: 'Destination class not found' });
    if (Number(destClass.id) === Number(existing.class_id)) {
      return res.status(400).json({ error: 'Destination class is the same as the current class' });
    }

    // Idempotency: if there's already an active row for the same
    // student in the destination class, don't double-enrol. The caller
    // can decide whether to soft-revoke it before re-transferring.
    const existingInDest = database.prepare(`
      SELECT id FROM enrollments
      WHERE student_id = ? AND class_id = ? AND status = 'active' AND is_deleted = 0
    `).get(existing.student_id, newClassId);
    if (existingInDest) {
      return res.status(400).json({
        error: 'Student is already actively enrolled in the destination class',
        existingEnrollmentId: existingInDest.id,
      });
    }

    const year = existing.academic_year || String(new Date().getFullYear());
    const section = destClass.section || existing.section || '';
    const newUid = uuidv4();
    const now = new Date().toISOString();

    const tx = database.transaction(() => {
      database.prepare(`
        UPDATE enrollments
        SET status = 'transferred',
            request_status = 'approved',
            updated_at = datetime('now', 'localtime'),
            sync_status = 'pending'
        WHERE id = ?
      `).run(req.params.id);

      const ins = database.prepare(`
        INSERT INTO enrollments (
          uid, student_id, class_id, roll_number, academic_year, section,
          status, request_status, requested_at, approved_by, approved_at,
          sync_status
        ) VALUES (?, ?, ?, ?, ?, ?, 'active', 'approved', NULL, ?, ?, 'pending')
      `).run(
        newUid, existing.student_id, newClassId,
        existing.roll_number, year, section,
        req.user.id, now
      );
      return ins.lastInsertRowid;
    });
    const newEnrollmentId = tx();

    // Audit (fire-and-forget — same pattern as cohorts controller)
    AuditService.log(req, 'ENROLLMENT_TRANSFER', {
      entityType: 'enrollment',
      entityId: Number(req.params.id),
      fromClassId: existing.class_id,
      toClassId: newClassId,
      newEnrollmentId,
      reason: reason || null,
    }).catch(() => undefined);

    const created = database.prepare('SELECT * FROM enrollments WHERE id = ?').get(newEnrollmentId);
    res.json({ closed: existing, created });
  } catch (err) {
    console.error('transfer error:', err);
    res.status(500).json({ error: err.message });
  }
});

// ============ SOFT DELETE (admin only) ============

/**
 * @openapi
 * /api/enrollments/{id}:
 *   delete:
 *     summary: Soft-delete an enrollment
 *     description: |
 *       Soft-deletes the enrollment (`is_deleted = 1`) and marks it
 *       as `inactive`. Restricted to admin roles
 *       (`school_admin`, `systems_admin`, `principal`).
 *     tags:
 *       - Enrollments
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: id
 *         required: true
 *         schema: { type: integer }
 *     responses:
 *       200:
 *         description: Removed
 *         content:
 *           application/json:
 *             schema: { type: object }
 *       401: { description: Unauthorized }
 *       403: { description: Admin only }
 *       404: { description: Enrollment not found }
 *       500: { description: Server error }
 */
router.delete('/:id', auth, adminOnly, (req, res) => {
  try {
    const result = getDb().prepare(`
      UPDATE enrollments
      SET is_deleted = 1, status = 'inactive',
          updated_at = datetime('now', 'localtime'),
          sync_status = 'pending'
      WHERE id = ? AND is_deleted = 0
    `).run(req.params.id);

    if (result.changes === 0) {
      return res.status(404).json({ error: 'Enrollment not found' });
    }
    res.json({ success: true, message: 'Enrollment removed' });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

module.exports = router;