import { test } from 'node:test';
import assert from 'node:assert/strict';
import { MatrixClient, MatrixEvent } from 'matrix-js-sdk';
import { buildForwardContent } from './forwardContent';

// Pure content builder buildForwardContent: refuses undecryptable events, forwards
// the latest edit (`m.new_content`), and strips reply fallbacks + `m.relates_to`.
// MatrixClient / MatrixEvent are mocked minimally. getEditedEvent reads edits off
// `timelineSet.relations.getChildEventsForEvent(...).getRelations()`, so the base
// client returns no child edits and the edit test injects one.

const SENDER = '@me:example.org';

type EventOptions = {
  content?: Record<string, unknown>;
  type?: string;
  id?: string;
  roomId?: string;
  decryptionFailure?: boolean;
  ts?: number;
};

const makeEvent = (options: EventOptions = {}): MatrixEvent => {
  const {
    content = {},
    type = 'm.room.message',
    id = '$evt:example.org',
    roomId = '!room:example.org',
    decryptionFailure = false,
    ts = 0,
  } = options;

  return {
    getContent: () => content,
    getType: () => type,
    getId: () => id,
    getRoomId: () => roomId,
    getSender: () => SENDER,
    getTs: () => ts,
    isDecryptionFailure: () => decryptionFailure,
  } as unknown as MatrixEvent;
};

// Base client: the timeline reports no `m.replace` edits, so the original content
// is forwarded unchanged.
const makeClient = (): MatrixClient =>
  ({
    getRoom: () => ({
      getUnfilteredTimelineSet: () => ({
        relations: {
          getChildEventsForEvent: () => null,
        },
      }),
    }),
  }) as unknown as MatrixClient;

test('plain text forwards the body and strips m.relates_to', () => {
  const mx = makeClient();
  const mEvent = makeEvent({
    content: {
      msgtype: 'm.text',
      body: 'hello world',
      'm.relates_to': { rel_type: 'm.thread', event_id: '$root:example.org' },
    },
  });

  const content = buildForwardContent(mx, mEvent);

  assert.ok(content);
  assert.equal(content.body, 'hello world');
  assert.equal(content.msgtype, 'm.text');
  assert.equal(content['m.relates_to'], undefined);
});

test('reply quote is stripped from body and formatted_body', () => {
  const mx = makeClient();
  const mEvent = makeEvent({
    content: {
      msgtype: 'm.text',
      body: '> <@alice:example.org> original\n\nmy reply',
      format: 'org.matrix.custom.html',
      formatted_body: '<mx-reply><blockquote>original</blockquote></mx-reply>my reply',
      'm.relates_to': { 'm.in_reply_to': { event_id: '$root:example.org' } },
    },
  });

  const content = buildForwardContent(mx, mEvent);

  assert.ok(content);
  assert.equal(content.body, 'my reply');
  assert.equal(content.formatted_body, 'my reply');
  assert.equal(content['m.relates_to'], undefined);
});

test('decryption failure returns undefined', () => {
  const mx = makeClient();
  const mEvent = makeEvent({
    content: { msgtype: 'm.bad.encrypted' },
    decryptionFailure: true,
  });

  assert.equal(buildForwardContent(mx, mEvent), undefined);
});

test('edited message forwards m.new_content', () => {
  const mEvent = makeEvent({
    content: {
      msgtype: 'm.text',
      body: 'original body',
      'm.relates_to': { rel_type: 'm.thread', event_id: '$root:example.org' },
    },
  });

  // The latest `m.replace` edit carries the new content under `m.new_content`.
  const editEvent = makeEvent({
    content: { 'm.new_content': { msgtype: 'm.text', body: 'edited body' } },
    ts: 100,
  });
  const mx = {
    getRoom: () => ({
      getUnfilteredTimelineSet: () => ({
        relations: {
          getChildEventsForEvent: () => ({
            getRelations: () => [editEvent],
          }),
        },
      }),
    }),
  } as unknown as MatrixClient;

  const content = buildForwardContent(mx, mEvent);

  assert.ok(content);
  assert.equal(content.body, 'edited body');
  assert.equal(content.msgtype, 'm.text');
  assert.equal(content['m.new_content'], undefined);
  assert.equal(content['m.relates_to'], undefined);
});
