/**
 * Notifications Controller
 *
 * In-app inbox backed by the `notifications` table (see migration
 * 2026072100000030_notifications.js). Scope (2026-07-28):
 *
 *   - POST /api/messages writes one notification per recipient (DM, group,
 *     club, broadcast) so the bell icon has a durable unread counter.
 *   - GET  /api/notifications            — inbox (newest first, 100 max)
 *   - GET  /api/notifications/unread-count — just the badge counter
 *   - POST /api/notifications/:id/read   — mark single row read
 *   - POST /api/notifications/read-all   — mark all rows for the caller read
 *
 * Notification types:
 *   'message:received' — DM / group / club chat message
 *   'announcement'     — system broadcast
 *
 * The schema also supports 'in_app' / 'email' / 'sms' for `type` (channel);
 * for now we always write 'in_app'. Future email/SMS providers can hook
 * the same row and flip status to 'sent' when dispatched.
 */

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

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;
};

// Reusable shared auth (mirrors the inline `auth` in other controllers so
// we don't take on a new dependency shape in tests).
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 {
    res.status(401).json({ error: 'Invalid token' });
  }
};

function insertNotification(opts) {
  const { recipient_id, type = 'in_app', subject, body, related_resource, metadata } = opts;
  if (!recipient_id) return null;
  try {
    getDb()
      .prepare(`
        INSERT INTO notifications
          (uid, recipient_id, type, subject, body, related_resource, status, metadata, sent_at)
        VALUES (?, ?, ?, ?, ?, ?, 'sent', ?, datetime('now','localtime'))
      `)
      .run(uuidv4(), recipient_id, type, subject || null, body || null, related_resource || null,
        metadata ? JSON.stringify(metadata) : null);
  } catch (err) {
    // Don't let a notification write failure abort the parent action.
    console.warn('[notifications] insert failed:', err.message);
  }
}

/**
 * Write one 'message:received' notification per recipient. Used by the
 * messages controller for DMs, group sends, club sends, and announcements.
 *
 * Exports a function rather than mounting it as Express middleware so
 * other controllers can call it directly:
 *
 *   const { notifyMessageRecipients } = require('./notifications.controller');
 *   notifyMessageRecipients({ recipients: [1,2,3], sender_id: 9, subject, body, kind: 'dm', related });
 */
function notifyMessageRecipients(opts) {
  const { recipients, sender_id, subject, body, kind = 'message', related_resource } = opts;
  if (!Array.isArray(recipients) || recipients.length === 0) return;
  for (const rid of recipients) {
    if (Number(rid) === Number(sender_id)) continue; // never notify yourself
    insertNotification({
      recipient_id: rid,
      type: 'in_app',
      subject: subject || 'New message',
      body: body || '',
      related_resource: related_resource || `${kind}:${sender_id}`,
      metadata: { kind, sender_id, ...(related_resource ? { resource: related_resource } : {}) },
    });
  }
}

module.exports = router;
module.exports.notifyMessageRecipients = notifyMessageRecipients;

// =============================================================================
// HTTP routes
// =============================================================================

/**
 * @openapi
 * /api/notifications:
 *   get:
 *     summary: List notifications for the caller
 *     description: Returns the inbox rows for the authenticated user, newest first.
 *     tags: [Notifications]
 *     security: [{ bearerAuth: [] }]
 *     parameters:
 *       - in: query
 *         name: unread
 *         schema: { type: boolean }
 *         description: When true, only return rows with status != 'read'.
 *     responses:
 *       200: { description: Inbox rows }
 *       401: { description: Unauthorized }
 */
router.get('/', auth, (req, res) => {
  try {
    const unread = String(req.query.unread || '').toLowerCase() === 'true';
    let query = `
      SELECT id, uid, type, subject, body, related_resource, status, metadata, created_at, read_at
      FROM notifications
      WHERE recipient_id = ? AND is_deleted = 0
    `;
    const params = [req.user.id];
    if (unread) {
      query += ` AND status != 'read'`;
    }
    query += ` ORDER BY created_at DESC LIMIT 100`;
    const rows = getDb().prepare(query).all(...params);
    const mapped = rows.map((r) => ({
      ...r,
      metadata: r.metadata ? safeParse(r.metadata) : null,
    }));
    res.json(mapped);
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

/**
 * @openapi
 * /api/notifications/unread-count:
 *   get:
 *     summary: Unread notification count
 *     description: Lightweight counter for the bell badge.
 *     tags: [Notifications]
 *     security: [{ bearerAuth: [] }]
 *     responses:
 *       200: { description: '{ count: number }' }
 */
router.get('/unread-count', auth, (req, res) => {
  try {
    const row = getDb()
      .prepare(`
        SELECT COUNT(*) as c FROM notifications
        WHERE recipient_id = ? AND is_deleted = 0 AND status != 'read'
      `)
      .get(req.user.id);
    res.json({ count: row.c });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

/**
 * @openapi
 * /api/notifications/{id}/read:
 *   post:
 *     summary: Mark a single notification as read
 *     tags: [Notifications]
 *     security: [{ bearerAuth: [] }]
 *     parameters:
 *       - in: path
 *         name: id
 *         required: true
 *         schema: { type: integer }
 *     responses:
 *       200: { description: '{ ok: true }' }
 *       404: { description: Notification not found }
 */
router.post('/:id/read', auth, (req, res) => {
  try {
    const id = parseInt(req.params.id, 10);
    if (!id) return res.status(400).json({ error: 'Invalid id' });
    const result = getDb()
      .prepare(`
        UPDATE notifications
        SET status = 'read', read_at = datetime('now','localtime'), updated_at = datetime('now','localtime')
        WHERE id = ? AND recipient_id = ? AND is_deleted = 0 AND status != 'read'
      `)
      .run(id, req.user.id);
    if (result.changes === 0) {
      // Already read or not yours — still 200 (idempotent), but signal it.
      return res.json({ ok: true, changed: false });
    }
    res.json({ ok: true, changed: true });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

/**
 * @openapi
 * /api/notifications/read-all:
 *   post:
 *     summary: Mark every notification for the caller as read
 *     tags: [Notifications]
 *     security: [{ bearerAuth: [] }]
 *     responses:
 *       200: { description: '{ ok: true, changed: number }' }
 */
router.post('/read-all', auth, (req, res) => {
  try {
    const result = getDb()
      .prepare(`
        UPDATE notifications
        SET status = 'read', read_at = datetime('now','localtime'), updated_at = datetime('now','localtime')
        WHERE recipient_id = ? AND is_deleted = 0 AND status != 'read'
      `)
      .run(req.user.id);
    res.json({ ok: true, changed: result.changes });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

function safeParse(s) {
  try {
    return JSON.parse(s);
  } catch {
    return null;
  }
}