import { test } from 'node:test';
import assert from 'node:assert/strict';
import { MatrixEvent } from 'matrix-js-sdk';
import { myMainReceiptPresent, receiptIsMine, setMarkedUnread } from './markedUnread';

// MSC2867 mark-as-unread: reading a room (our own receipt) clears the flag, so
// `receiptIsMine` must detect only OUR receipt and ignore others'. And a write
// must land on BOTH the stable `m.marked_unread` and the unstable
// `com.famedly.marked_unread` key so it round-trips across servers/clients.

const ME = '@me:server';
const OTHER = '@friend:server';

const receiptEvent = (content: object): MatrixEvent =>
  ({ getContent: () => content }) as MatrixEvent;

test('receiptIsMine: true when the receipt content carries our user id', () => {
  const event = receiptEvent({
    $abc: { 'm.read': { [ME]: { ts: 1 } } },
  });
  assert.equal(receiptIsMine(event, ME), true);
});

test('receiptIsMine: false when only another user has a receipt', () => {
  const event = receiptEvent({
    $abc: { 'm.read': { [OTHER]: { ts: 1 } } },
  });
  assert.equal(receiptIsMine(event, ME), false);
});

test('receiptIsMine: tolerates empty / malformed content', () => {
  assert.equal(receiptIsMine(receiptEvent({}), ME), false);
  assert.equal(receiptIsMine(receiptEvent({ $x: {} }), ME), false);
});

// myMainReceiptPresent gates the auto-clear to main-timeline reads, so reading a
// single thread does not wipe the whole-room marked-unread flag.
test('myMainReceiptPresent: true for an unthreaded receipt (no thread_id)', () => {
  const event = receiptEvent({ $abc: { 'm.read': { [ME]: { ts: 1 } } } });
  assert.equal(myMainReceiptPresent(event, ME), true);
});

test('myMainReceiptPresent: true for a thread_id "main" receipt', () => {
  const event = receiptEvent({ $abc: { 'm.read': { [ME]: { ts: 1, thread_id: 'main' } } } });
  assert.equal(myMainReceiptPresent(event, ME), true);
});

test('myMainReceiptPresent: false for a thread-scoped receipt', () => {
  const event = receiptEvent({
    $abc: { 'm.read': { [ME]: { ts: 1, thread_id: '$root:server' } } },
  });
  assert.equal(myMainReceiptPresent(event, ME), false);
});

test('myMainReceiptPresent: false when only another user has a main receipt', () => {
  const event = receiptEvent({ $abc: { 'm.read': { [OTHER]: { ts: 1 } } } });
  assert.equal(myMainReceiptPresent(event, ME), false);
});

test('setMarkedUnread writes both the stable and unstable keys with the flag', async () => {
  const calls: Array<{ type: string; content: unknown }> = [];
  const mx = {
    setRoomAccountData: (_roomId: string, type: string, content: unknown) => {
      calls.push({ type, content });
      return Promise.resolve();
    },
  } as any;

  await setMarkedUnread(mx, '!room:server', true);

  const types = calls.map((c) => c.type).sort();
  assert.deepEqual(types, ['com.famedly.marked_unread', 'm.marked_unread']);
  assert.ok(calls.every((c) => (c.content as { unread: boolean }).unread === true));
});

test('setMarkedUnread(false) clears both keys and does not reject if the unstable write fails', async () => {
  const seen: string[] = [];
  const mx = {
    setRoomAccountData: (_roomId: string, type: string) => {
      seen.push(type);
      // Simulate an older server rejecting the unstable key — must not reject.
      if (type === 'com.famedly.marked_unread') return Promise.reject(new Error('unknown type'));
      return Promise.resolve();
    },
  } as any;

  await assert.doesNotReject(() => setMarkedUnread(mx, '!room:server', false));
  assert.ok(seen.includes('m.marked_unread'));
});
