/**
 * test/takedownNotes.test.js
 *
 * Operator notes on a takedown (UX #10). Distinct from audit_log.
 * Tests:
 *  - Model CRUD: create, list, update, delete, countForTakedown
 *  - Author + edited timestamps propagate
 *  - getById returns the joined author name + email
 *  - Deletion is idempotent on missing id
 *  - countForTakedown matches listForTakedown length
 *  - notes-counts batch API correctly maps ids → counts
 */

'use strict';

const assert = require('assert');
const { setupFreshDb, teardown } = require('./_setup');
const User     = require('../models/user');
const Case     = require('../models/case');
const Takedown = require('../models/takedown');
const Notes    = require('../models/takedownNote');
const { db }   = require('../config/db');

function makeUserAndCase() {
  const u = User.create({
    email: 'notes-' + Math.random().toString(36).slice(2, 10) + '@ncii.test',
    name: 'Notes Test',
    password: 'password1234long-enough',
    plan: 'pro',
  });
  const c = Case.create({
    user_id: u.id, victim_name: 'Notes Victim', reference_url: 'https://example.com', context: 'unit test',
  });
  const td = Takedown.create({
    case_id: c.id, user_id: u.id, url: 'https://example.com/leak', domain: 'example.com',
    contact_email: 'abuse@example.com', status: 'sent',
  });
  return { user: u, case: c, takedown: td };
}

const tests = [
  {
    name: 'Notes.create + listForTakedown returns rows in DESC order',
    fn: () => {
      setupFreshDb();
      try {
        const { user, takedown } = makeUserAndCase();
        const n1 = Notes.create({ takedown_id: takedown.id, user_id: user.id, body: 'first note' });
        // Wait at least 5ms so created_at differs (ms timestamp).
        const sleep = (ms) => new Promise(r => setTimeout(r, ms));
        return sleep(5).then(() => {
          const n2 = Notes.create({ takedown_id: takedown.id, user_id: user.id, body: 'second note' });
          const list = Notes.listForTakedown(takedown.id);
          assert.strictEqual(list.length, 2, 'two notes');
          assert.strictEqual(list[0].id, n2.id, 'most recent first');
          assert.strictEqual(list[1].id, n1.id, 'older second');
          assert.strictEqual(list[0].body, 'second note');
          assert.strictEqual(list[0].author_email, user.email, 'author email joined');
          assert.strictEqual(list[0].author_name, user.name, 'author name joined');
        });
      } finally { teardown(); }
    },
  },
  {
    name: 'Notes.update overwrites body and bumps updated_at',
    fn: () => {
      setupFreshDb();
      try {
        const { user, takedown } = makeUserAndCase();
        const n = Notes.create({ takedown_id: takedown.id, user_id: user.id, body: 'original' });
        const sleep = (ms) => new Promise(r => setTimeout(r, ms));
        return sleep(5).then(() => {
          const updated = Notes.update(n.id, 'edited text');
          assert.strictEqual(updated.body, 'edited text');
          assert.ok(updated.updated_at >= n.created_at, 'updated_at moved forward');
          assert.strictEqual(updated.created_at, n.created_at, 'created_at unchanged');
        });
      } finally { teardown(); }
    },
  },
  {
    name: 'Notes.remove deletes the row',
    fn: () => {
      setupFreshDb();
      try {
        const { user, takedown } = makeUserAndCase();
        const n = Notes.create({ takedown_id: takedown.id, user_id: user.id, body: 'to be deleted' });
        Notes.remove(n.id);
        assert.strictEqual(Notes.getById(n.id), null, 'row gone after remove');
        // idempotent on missing id
        const info = Notes.remove(999999);
        assert.strictEqual(info.changes, 0, 'no-op on missing');
      } finally { teardown(); }
    },
  },
  {
    name: 'Notes.countForTakedown matches list length',
    fn: () => {
      setupFreshDb();
      try {
        const { user, takedown } = makeUserAndCase();
        assert.strictEqual(Notes.countForTakedown(takedown.id), 0);
        Notes.create({ takedown_id: takedown.id, user_id: user.id, body: 'one' });
        Notes.create({ takedown_id: takedown.id, user_id: user.id, body: 'two' });
        Notes.create({ takedown_id: takedown.id, user_id: user.id, body: 'three' });
        assert.strictEqual(Notes.countForTakedown(takedown.id), 3);
        assert.strictEqual(Notes.listForTakedown(takedown.id).length, 3);
      } finally { teardown(); }
    },
  },
  {
    name: 'notes-counts batch query maps ids → counts (used by dashboard)',
    fn: () => {
      setupFreshDb();
      try {
        const { user, takedown: t1 } = makeUserAndCase();
        // Make a second takedown on a different domain so the dedup
        // (case_id, domain, contact_email) doesn't collapse them.
        const t2 = Takedown.create({
          case_id: t1.case_id, user_id: user.id, url: 'https://other.com/leak', domain: 'other.com',
          contact_email: 'abuse@other.com', status: 'sent',
        });
        Notes.create({ takedown_id: t1.id, user_id: user.id, body: 'a' });
        Notes.create({ takedown_id: t1.id, user_id: user.id, body: 'b' });
        Notes.create({ takedown_id: t2.id, user_id: user.id, body: 'c' });
        const rows = db.prepare(`
          SELECT takedown_id, COUNT(*) AS n
            FROM takedown_notes WHERE takedown_id IN (?, ?)
           GROUP BY takedown_id
        `).all(t1.id, t2.id);
        const counts = {};
        for (const r of rows) counts[r.takedown_id] = r.n;
        assert.strictEqual(counts[t1.id], 2);
        assert.strictEqual(counts[t2.id], 1);
      } finally { teardown(); }
    },
  },
  {
    name: 'Foreign-key cascade: deleting the takedown removes its notes',
    fn: () => {
      setupFreshDb();
      try {
        const { user, takedown } = makeUserAndCase();
        Notes.create({ takedown_id: takedown.id, user_id: user.id, body: 'will orphan' });
        assert.strictEqual(Notes.countForTakedown(takedown.id), 1);
        // Cascade via ON DELETE CASCADE on takedown_notes.takedown_id
        db.prepare('DELETE FROM takedowns WHERE id = ?').run(takedown.id);
        assert.strictEqual(Notes.countForTakedown(takedown.id), 0, 'cascade kicked in');
      } finally { teardown(); }
    },
  },
];

module.exports = tests;