import * as React from 'react';
import { createElement } from 'react';
import { createRoot, type Root } from 'react-dom/client';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import useFreshReplies from '../use-fresh-replies';

(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
const act = (React as { act?: (cb: () => void | Promise<void>) => void | Promise<void> }).act as (cb: () => void | Promise<void>) => void | Promise<void>;

type TestComment = {
  cid?: string;
  content?: string;
  index?: number;
  number?: number;
  pendingApproval?: boolean;
  communityAddress?: string;
};

const testState = vi.hoisted(() => ({
  accountComments: [] as TestComment[],
  accountCommentsCalls: [] as Array<{ commentIndices?: number[] } | undefined>,
  replies: [] as TestComment[],
}));

vi.mock('@bitsocial/bitsocial-react-hooks', () => ({
  useAccountComments: (options?: { commentIndices?: number[] }) => {
    testState.accountCommentsCalls.push(options);

    if (!options?.commentIndices?.length) {
      return {
        accountComments: testState.accountComments,
      };
    }

    const normalizedCommentIndices = options.commentIndices.filter((commentIndex) => Number.isInteger(commentIndex) && commentIndex >= 0);

    return {
      accountComments: normalizedCommentIndices
        .map((commentIndex) => testState.accountComments.find((accountComment) => accountComment.index === commentIndex))
        .filter(Boolean),
    };
  },
}));

let container: HTMLDivElement;
let latestValue: ReturnType<typeof useFreshReplies>;
let root: Root;

const HookHarness = () => {
  latestValue = useFreshReplies(testState.replies as never);
  return null;
};

const renderHook = () => {
  act(() => {
    root.render(createElement(HookHarness));
  });
};

describe('useFreshReplies', () => {
  beforeEach(() => {
    testState.accountComments = [];
    testState.accountCommentsCalls = [];
    testState.replies = [];

    container = document.createElement('div');
    document.body.appendChild(container);
    root = createRoot(container);
  });

  afterEach(() => {
    act(() => root.unmount());
    container.remove();
  });

  it('replaces stale indexed replies with the latest account comment objects', () => {
    testState.replies = [
      {
        cid: 'reply-cid',
        content: 'stale reply',
        index: 3,
        number: undefined,
        communityAddress: 'music.eth',
      },
      {
        cid: 'network-reply-cid',
        content: 'network reply',
        communityAddress: 'music.eth',
      },
    ];
    testState.accountComments = [
      {
        cid: 'reply-cid',
        content: 'fresh reply',
        index: 3,
        number: 27,
        communityAddress: 'music.eth',
      },
    ];

    renderHook();

    expect(latestValue[0]).toBe(testState.accountComments[0] as never);
    expect(latestValue[0]?.number).toBe(27);
    expect(latestValue[1]).toBe(testState.replies[1] as never);
    expect(testState.accountCommentsCalls).toContainEqual({ commentIndices: [3] });

    testState.accountComments = [
      {
        ...testState.accountComments[0],
        number: 28,
      },
    ];

    renderHook();

    expect(latestValue[0]?.number).toBe(28);
  });

  it('dedupes retried local replies when a stale reply and fresh reply share index 0', () => {
    testState.replies = [
      {
        content: 'stale failed reply',
        index: 0,
        communityAddress: 'music.eth',
      },
      {
        content: 'duplicate stale failed reply',
        index: 0,
        communityAddress: 'music.eth',
      },
    ];
    testState.accountComments = [
      {
        content: 'retried pending reply',
        index: 0,
        number: 99,
        communityAddress: 'music.eth',
      },
    ];

    renderHook();

    expect(latestValue).toHaveLength(1);
    expect(latestValue[0]).toBe(testState.accountComments[0] as never);
    expect(testState.accountCommentsCalls).toContainEqual({ commentIndices: [0] });
  });

  it('orders replies by final post number after a pending reply is approved', () => {
    testState.replies = [
      {
        cid: 'reply-8',
        index: 8,
        number: 8,
        communityAddress: 'music.eth',
      },
      {
        cid: 'reply-12',
        index: 12,
        number: 12,
        communityAddress: 'music.eth',
      },
      {
        cid: 'pending-reply',
        index: 11,
        number: undefined,
        pendingApproval: true,
        communityAddress: 'music.eth',
      },
    ];
    testState.accountComments = [
      {
        cid: 'reply-11',
        index: 11,
        number: 11,
        communityAddress: 'music.eth',
      },
    ];

    renderHook();

    expect(latestValue.map((reply) => reply.cid)).toEqual(['reply-8', 'reply-11', 'reply-12']);
    expect(testState.accountCommentsCalls).toContainEqual({ commentIndices: [8, 12, 11] });
  });

  it('orders already-numbered replies when an approved queued reply was appended', () => {
    testState.replies = [
      {
        cid: 'reply-8',
        number: 8,
        communityAddress: 'music.eth',
      },
      {
        cid: 'reply-12',
        number: 12,
        communityAddress: 'music.eth',
      },
      {
        cid: 'reply-11',
        number: 11,
        communityAddress: 'music.eth',
      },
    ];

    renderHook();

    expect(latestValue.map((reply) => reply.cid)).toEqual(['reply-8', 'reply-11', 'reply-12']);
  });
});
