/**
 * Regression test for GET /api/messages/conversations.
 *
 * Guards against orphaned direct-message rows crashing the client.
 * Seeded automation DMs that were sent without a recipient collapsed the
 * per-conversation partition to NULL `other_user_id`, producing rows
 * with `other_user_name = NULL`. The client conversation filter calls
 * `c.other_user_name.toLowerCase()`, which threw on the null value and
 * prevented the Messages page from rendering for that user.
 *
 * The fix filters out rows where either side of the partition is NULL
 * and adds defensive tolerance on the client.
 */
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 buildConversationsRouter(db) {
  const app = express();
  app.use(express.json());

  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');
      req.user = jwt.verify(token, JWT_SECRET);
      next();
    } catch {
      res.status(401).json({ error: 'Invalid token' });
    }
  };

  app.get('/api/messages/conversations', auth, (req, res) => {
    const conversations = db.prepare(`
      SELECT * FROM (
        SELECT
          CAST(other_user_id AS TEXT) as other_user_id,
          other_user_name,
          last_message,
          last_message_at,
          0 as unread_count,
          last_sender_id,
          last_is_read,
          last_is_delivered,
          last_id
        FROM (
          SELECT
            CASE WHEN m.sender_id = ? THEN m.recipient_id ELSE m.sender_id END as other_user_id,
            u.first_name || ' ' || u.last_name as other_user_name,
            m.body as last_message,
            m.created_at as last_message_at,
            m.sender_id as last_sender_id,
            m.is_read as last_is_read,
            m.is_delivered as last_is_delivered,
            m.id as last_id,
            ROW_NUMBER() OVER (PARTITION BY CASE WHEN m.sender_id = ? THEN m.recipient_id ELSE m.sender_id END ORDER BY m.created_at DESC) as rn
          FROM messages m
          LEFT JOIN users u ON CASE WHEN m.sender_id = ? THEN m.recipient_id ELSE m.sender_id END = u.id
          WHERE (m.sender_id = ? OR m.recipient_id = ?)
            AND m.is_deleted = 0 AND m.group_id IS NULL
            AND m.recipient_id IS NOT NULL AND m.sender_id IS NOT NULL
        ) sub
        WHERE rn = 1 AND other_user_id IS NOT NULL
      )
      ORDER BY last_message_at DESC
    `).all(req.user.id, req.user.id, req.user.id, req.user.id, req.user.id);

    res.json(conversations);
  });

  return app;
}

function makeTempDb() {
  const tmp = path.join(os.tmpdir(), `conv-orphan-${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 messages (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      uid TEXT UNIQUE,
      sender_id INTEGER,
      recipient_id INTEGER,
      subject TEXT,
      body TEXT,
      is_read INTEGER DEFAULT 0,
      is_delivered INTEGER DEFAULT 0,
      is_announcement INTEGER DEFAULT 0,
      group_id INTEGER,
      created_at DATETIME DEFAULT (datetime('now', 'localtime')),
      updated_at DATETIME DEFAULT (datetime('now', 'localtime')),
      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');
  // Orphan DM: recipient is NULL (simulates a buggy automation row)
  db.prepare(`INSERT INTO messages (uid, sender_id, recipient_id, subject, body) VALUES (?, ?, ?, ?, ?)`)
    .run('m-orphan', alice.lastInsertRowid, null, 'Auto', 'Hello from automation');
  // Real DM
  db.prepare(`INSERT INTO messages (uid, sender_id, recipient_id, subject, body) VALUES (?, ?, ?, ?, ?)`)
    .run('m-real', alice.lastInsertRowid, bob.lastInsertRowid, 'Hi', 'Hello Bob');
  return { db, aliceId: alice.lastInsertRowid, bobId: bob.lastInsertRowid };
}

describe('GET /api/messages/conversations orphan handling', () => {
  let app;
  let token;
  let db;

  beforeAll(async () => {
    const built = makeTempDb();
    db = built.db;
    app = buildConversationsRouter(db);
    const jwt = require('jsonwebtoken');
    token = jwt.sign({ id: built.aliceId, email: 'alice@x', role: 'teacher' }, JWT_SECRET, { expiresIn: '5m' });
  });

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

  it('returns no rows with NULL other_user_id or other_user_name', async () => {
    const res = await request(app)
      .get('/api/messages/conversations')
      .set('Authorization', `Bearer ${token}`);
    expect(res.status).toBe(200);
    expect(res.body).toBeInstanceOf(Array);
    for (const conv of res.body) {
      expect(conv.other_user_id).not.toBeNull();
      expect(conv.other_user_name).not.toBeNull();
    }
  });

  it('still returns legitimate conversations after dropping the orphan', async () => {
    const res = await request(app)
      .get('/api/messages/conversations')
      .set('Authorization', `Bearer ${token}`);
    expect(res.status).toBe(200);
    expect(res.body.length).toBe(1);
    expect(res.body[0].other_user_name).toBe('Bob B');
  });
});