/**
 * Notifications controller — inbox + unread-count + mark-read behavior.
 *
 * Covers the wired paths:
 *   - GET  /api/notifications (inbox + unread filter)
 *   - GET  /api/notifications/unread-count
 *   - POST /api/notifications/:id/read
 *   - POST /api/notifications/read-all
 *   - notifyMessageRecipients() helper used by the messages controller
 */
const express = require('express');
const request = require('supertest');
const bcrypt = require('bcryptjs');
const Database = require('better-sqlite3');
const path = require('path');
const os = require('os');
const { jwtSecret: JWT_SECRET } = require('../src/config');

function makeDb() {
  const tmp = path.join(os.tmpdir(), `notifications-${Date.now()}.db`);
  const db = new Database(tmp);
  db.pragma('foreign_keys = ON');
  db.exec(`
    CREATE TABLE users (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      uid TEXT UNIQUE,
      email TEXT,
      password TEXT,
      role TEXT,
      first_name TEXT,
      last_name TEXT,
      is_active INTEGER DEFAULT 1,
      is_deleted INTEGER DEFAULT 0
    );
    CREATE TABLE notifications (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      uid TEXT UNIQUE,
      recipient_id INTEGER NOT NULL REFERENCES users(id),
      type TEXT NOT NULL CHECK (type IN ('in_app','email','sms')),
      subject TEXT,
      body TEXT,
      related_resource TEXT,
      status TEXT NOT NULL DEFAULT 'pending'
        CHECK (status IN ('pending','sent','failed','read')),
      sent_at DATETIME,
      read_at DATETIME,
      error_message TEXT,
      metadata TEXT,
      created_at DATETIME DEFAULT (datetime('now','localtime')),
      updated_at DATETIME DEFAULT (datetime('now','localtime')),
      last_synced_at DATETIME,
      sync_status TEXT DEFAULT 'pending',
      is_deleted INTEGER DEFAULT 0
    );
  `);
  const hash = bcrypt.hashSync('secret123', 4);
  const alice = db.prepare(`INSERT INTO users (uid,email,password,role,first_name,last_name) VALUES (?,?,?,?,?,?)`)
    .run('u-alice', 'alice@x', hash, 'teacher', 'Alice', 'A');
  const bob = db.prepare(`INSERT INTO users (uid,email,password,role,first_name,last_name) VALUES (?,?,?,?,?,?)`)
    .run('u-bob', 'bob@x', hash, 'teacher', 'Bob', 'B');
  return { db, aliceId: alice.lastInsertRowid, bobId: bob.lastInsertRowid };
}

function buildApp(db) {
  // Load the controller fresh against our test DB. The module holds a
  // cached Database singleton internally, so we swap `process.env.DB_PATH`
  // before requiring it.
  process.env.DB_PATH = db.name;
  // Clear require cache so the controller re-initializes against the
  // swapped DB path.
  const ctrlPath = require.resolve('../src/controllers/notifications.controller');
  delete require.cache[ctrlPath];
  const controller = require('../src/controllers/notifications.controller');

  const app = express();
  app.use(express.json());
  app.use('/api/notifications', controller);
  return app;
}

function token(id, role) {
  const jwt = require('jsonwebtoken');
  return jwt.sign({ id, email: 'x@x', role }, JWT_SECRET, { expiresIn: '5m' });
}

describe('Notifications controller', () => {
  let app;
  let db;
  let aliceToken;
  let bobToken;
  let aliceId;
  let bobId;

  beforeAll(() => {
    const built = makeDb();
    db = built.db;
    aliceId = built.aliceId;
    bobId = built.bobId;
    app = buildApp(db);
    aliceToken = token(aliceId, 'teacher');
    bobToken = token(bobId, 'teacher');
  });

  afterAll(() => {
    db.close();
  });

  test('GET /api/notifications returns 401 without a token', async () => {
    const res = await request(app).get('/api/notifications');
    expect(res.status).toBe(401);
  });

  test('GET /api/notifications/unread-count starts at 0', async () => {
    const res = await request(app)
      .get('/api/notifications/unread-count')
      .set('Authorization', `Bearer ${aliceToken}`);
    expect(res.status).toBe(200);
    expect(res.body.count).toBe(0);
  });

  test('notifyMessageRecipients writes one row per recipient and skips the sender', async () => {
    const { notifyMessageRecipients } = require('../src/controllers/notifications.controller');
    notifyMessageRecipients({
      recipients: [aliceId, bobId, aliceId], // alice appears twice (dedupe by id logic in messages.controller, here we just write per id)
      sender_id: aliceId,
      subject: 'Hi from Alice',
      body: 'Hello',
      kind: 'message',
      related_resource: 'message:42',
    });
    // Helper writes per recipient; dedupe is handled by the caller in
    // production. Two rows here (alice + bob) — but the helper skips
    // the sender, so only one row lands in bob's inbox.
    const bobInbox = await request(app)
      .get('/api/notifications')
      .set('Authorization', `Bearer ${bobToken}`);
    expect(bobInbox.body.length).toBe(1);
    expect(bobInbox.body[0].subject).toBe('Hi from Alice');
    expect(bobInbox.body[0].related_resource).toBe('message:42');

    const aliceInbox = await request(app)
      .get('/api/notifications')
      .set('Authorization', `Bearer ${aliceToken}`);
    expect(aliceInbox.body.length).toBe(0);
  });

  test('GET /api/notifications/unread-count reflects new messages', async () => {
    const res = await request(app)
      .get('/api/notifications/unread-count')
      .set('Authorization', `Bearer ${bobToken}`);
    expect(res.body.count).toBe(1);
  });

  test('GET /api/notifications?unread=true filters to unread only', async () => {
    const res = await request(app)
      .get('/api/notifications?unread=true')
      .set('Authorization', `Bearer ${bobToken}`);
    expect(res.status).toBe(200);
    expect(res.body.length).toBe(1);
  });

  test('POST /api/notifications/:id/read flips the row and returns changed=true', async () => {
    const inbox = await request(app)
      .get('/api/notifications')
      .set('Authorization', `Bearer ${bobToken}`);
    const id = inbox.body[0].id;

    const res = await request(app)
      .post(`/api/notifications/${id}/read`)
      .set('Authorization', `Bearer ${bobToken}`);
    expect(res.status).toBe(200);
    expect(res.body.changed).toBe(true);

    const after = await request(app)
      .get('/api/notifications/unread-count')
      .set('Authorization', `Bearer ${bobToken}`);
    expect(after.body.count).toBe(0);

    const second = await request(app)
      .post(`/api/notifications/${id}/read`)
      .set('Authorization', `Bearer ${bobToken}`);
    expect(second.body.changed).toBe(false);
  });

  test('POST /api/notifications/:id/read returns 400 for an invalid id', async () => {
    const res = await request(app)
      .post('/api/notifications/not-a-number/read')
      .set('Authorization', `Bearer ${bobToken}`);
    expect(res.status).toBe(400);
  });

  test('POST /api/notifications/read-all marks every row for the caller', async () => {
    // Add two new rows to Bob's inbox
    const { notifyMessageRecipients } = require('../src/controllers/notifications.controller');
    notifyMessageRecipients({
      recipients: [bobId],
      sender_id: aliceId,
      subject: 'second',
      body: '',
      kind: 'message',
    });
    notifyMessageRecipients({
      recipients: [bobId],
      sender_id: aliceId,
      subject: 'third',
      body: '',
      kind: 'message',
    });

    const before = await request(app)
      .get('/api/notifications/unread-count')
      .set('Authorization', `Bearer ${bobToken}`);
    expect(before.body.count).toBe(2);

    const res = await request(app)
      .post('/api/notifications/read-all')
      .set('Authorization', `Bearer ${bobToken}`);
    expect(res.status).toBe(200);
    expect(res.body.changed).toBe(2);

    const after = await request(app)
      .get('/api/notifications/unread-count')
      .set('Authorization', `Bearer ${bobToken}`);
    expect(after.body.count).toBe(0);
  });

  test('mark-read only affects the caller\'s notifications', async () => {
    const { notifyMessageRecipients } = require('../src/controllers/notifications.controller');
    notifyMessageRecipients({
      recipients: [aliceId],
      sender_id: bobId,
      subject: 'for alice',
      body: '',
    });

    const aliceInbox = await request(app)
      .get('/api/notifications')
      .set('Authorization', `Bearer ${aliceToken}`);
    const id = aliceInbox.body[0].id;

    // Bob should not be able to mark Alice's notification read.
    const res = await request(app)
      .post(`/api/notifications/${id}/read`)
      .set('Authorization', `Bearer ${bobToken}`);
    expect(res.body.changed).toBe(false);

    const still = await request(app)
      .get('/api/notifications/unread-count')
      .set('Authorization', `Bearer ${aliceToken}`);
    expect(still.body.count).toBe(1);
  });
});