import { test } from 'node:test';
import assert from 'node:assert/strict';
import { EventStatus, MatrixEvent, RelationType } from 'matrix-js-sdk';
import { getThreadSummary, isPendingThreadReply } from './threadSummaryData';

// getThreadSummary reads either the live Thread (preferred) or the
// server-aggregated `m.thread` bundle. We stub only the members it touches and
// cast through `unknown` to MatrixEvent, mirroring the light mocking used in
// the state tests.

type ThreadStub = { length: number; lastReplyTs?: number };
type BundleStub = { count: number; latestTs?: number };

const makeRootEvent = (opts: { thread?: ThreadStub; bundle?: BundleStub }): MatrixEvent => {
  const thread = opts.thread
    ? {
        length: opts.thread.length,
        lastReply: () =>
          opts.thread?.lastReplyTs === undefined
            ? null
            : ({ getTs: () => opts.thread?.lastReplyTs } as unknown as MatrixEvent),
      }
    : undefined;

  return {
    getThread: () => thread,
    getServerAggregatedRelation: (relType: string) => {
      if (relType !== RelationType.Thread || !opts.bundle) return undefined;
      return {
        count: opts.bundle.count,
        latest_event:
          opts.bundle.latestTs === undefined
            ? undefined
            : { origin_server_ts: opts.bundle.latestTs },
      };
    },
  } as unknown as MatrixEvent;
};

// ---------------------------------------------------------------------------
// getThreadSummary
// ---------------------------------------------------------------------------

test('prefers the live thread: count from length, latestTs from lastReply', () => {
  const rootEvent = makeRootEvent({
    thread: { length: 3, lastReplyTs: 1700 },
    bundle: { count: 99, latestTs: 1 },
  });
  assert.deepEqual(getThreadSummary(rootEvent), { count: 3, latestTs: 1700 });
});

test('live thread with no replies yields undefined latestTs', () => {
  const rootEvent = makeRootEvent({ thread: { length: 0 } });
  assert.deepEqual(getThreadSummary(rootEvent), { count: 0, latestTs: undefined });
});

test('falls back to the server bundle when no live thread', () => {
  const rootEvent = makeRootEvent({ bundle: { count: 5, latestTs: 1234 } });
  assert.deepEqual(getThreadSummary(rootEvent), { count: 5, latestTs: 1234 });
});

test('bundle without latest_event yields undefined latestTs', () => {
  const rootEvent = makeRootEvent({ bundle: { count: 2 } });
  assert.deepEqual(getThreadSummary(rootEvent), { count: 2, latestTs: undefined });
});

test('returns undefined when there is neither a thread nor a bundle', () => {
  const rootEvent = makeRootEvent({});
  assert.equal(getThreadSummary(rootEvent), undefined);
});

// ---------------------------------------------------------------------------
// isPendingThreadReply
// ---------------------------------------------------------------------------

const ROOT = '$root:server';

const makeReply = (opts: {
  status: EventStatus | null;
  threadRootId?: string;
  relation?: { rel_type?: string; event_id?: string } | null;
}): MatrixEvent =>
  ({
    status: opts.status,
    threadRootId: opts.threadRootId,
    getRelation: () => opts.relation ?? null,
  }) as unknown as MatrixEvent;

test('SENDING with matching threadRootId is pending', () => {
  const event = makeReply({ status: EventStatus.SENDING, threadRootId: ROOT });
  assert.equal(isPendingThreadReply(event, ROOT), true);
});

test('NOT_SENT with matching threadRootId is pending', () => {
  const event = makeReply({ status: EventStatus.NOT_SENT, threadRootId: ROOT });
  assert.equal(isPendingThreadReply(event, ROOT), true);
});

test('SENDING resolved via the m.thread relation content is pending', () => {
  const event = makeReply({
    status: EventStatus.SENDING,
    relation: { rel_type: RelationType.Thread, event_id: ROOT },
  });
  assert.equal(isPendingThreadReply(event, ROOT), true);
});

test('SENT (confirmed) event is not pending', () => {
  const event = makeReply({ status: EventStatus.SENT, threadRootId: ROOT });
  assert.equal(isPendingThreadReply(event, ROOT), false);
});

test('null status is not pending', () => {
  const event = makeReply({ status: null, threadRootId: ROOT });
  assert.equal(isPendingThreadReply(event, ROOT), false);
});

test('SENDING but for a different thread is not pending', () => {
  const event = makeReply({ status: EventStatus.SENDING, threadRootId: '$other:server' });
  assert.equal(isPendingThreadReply(event, ROOT), false);
});

test('SENDING with a non-thread relation is not pending', () => {
  const event = makeReply({
    status: EventStatus.SENDING,
    relation: { rel_type: RelationType.Reference, event_id: ROOT },
  });
  assert.equal(isPendingThreadReply(event, ROOT), false);
});

test('SENDING with no relation and no threadRootId is not pending', () => {
  const event = makeReply({ status: EventStatus.SENDING });
  assert.equal(isPendingThreadReply(event, ROOT), false);
});
