import { act } from 'react';
import { createRoot, type Root } from 'react-dom/client';
import { createMemoryRouter, RouterProvider, type RouteObject } from 'react-router-dom';
import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest';

const { useSessionMock } = vi.hoisted(() => ({
  useSessionMock: vi.fn(),
}));

vi.mock('@/lib/auth-client', () => ({
  useSession: useSessionMock,
}));

import { routes } from '@/routes';

beforeAll(() => {
  Object.defineProperty(globalThis, 'IS_REACT_ACT_ENVIRONMENT', {
    configurable: true,
    value: true,
  });
});

afterAll(() => {
  Reflect.deleteProperty(globalThis, 'IS_REACT_ACT_ENVIRONMENT');
});

function Boom(): never {
  throw new Error('render blew up');
}

/** Recursively clones the real exported route table, replacing only the
 * `/chat` route's `element` with `<Boom />` — every other route (including
 * the real `AuthGuard` nesting and the real `/chat` `errorElement`) is left
 * exactly as exported. This is what makes the test fail if a future change
 * removes the real route's `errorElement`, unlike a hand-built independent
 * route tree that could drift from production undetected. */
function replaceChatElementWithBoom(nodes: RouteObject[]): RouteObject[] {
  return nodes.map((node) => {
    const cloned: RouteObject = { ...node };
    if (cloned.path === '/chat') {
      cloned.element = <Boom />;
    }
    if (cloned.children) {
      cloned.children = replaceChatElementWithBoom(cloned.children);
    }
    return cloned;
  });
}

let root: Root | null;
let container: HTMLElement;

afterEach(async () => {
  await act(async () => {
    root?.unmount();
  });
  document.body.replaceChildren();
  root = null;
  useSessionMock.mockReset();
});

describe('ChatRouteErrorBoundary', () => {
  it('renders a recoverable, non-blank fallback when the /chat route element throws during render', async () => {
    useSessionMock.mockReturnValue({ data: { user: { id: 'user-1' } }, isPending: false });

    const routeObjects = replaceChatElementWithBoom(routes);
    const router = createMemoryRouter(routeObjects, { initialEntries: ['/chat'] });

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

    const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
    try {
      await act(async () => {
        root?.render(<RouterProvider router={router} />);
      });

      expect(consoleErrorSpy).toHaveBeenCalled();
    } finally {
      consoleErrorSpy.mockRestore();
    }

    expect(container.textContent).not.toBe('');
    expect(container.querySelector('[role="alert"]')).toBeTruthy();
  });
});
