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 { AuthGuard, GuestGuard } from './guards';

interface RenderedRouter {
  container: HTMLDivElement;
  router: ReturnType<typeof createMemoryRouter>;
}

const mountedRoots: Root[] = [];

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

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

async function renderRouter(
  routeObjects: RouteObject[],
  initialEntry: string,
): Promise<RenderedRouter> {
  const container = document.createElement('div');
  document.body.append(container);
  const router = createMemoryRouter(routeObjects, { initialEntries: [initialEntry] });
  const root = createRoot(container);
  mountedRoots.push(root);

  await act(async () => {
    root.render(<RouterProvider router={router} />);
  });

  return { container, router };
}

afterEach(async () => {
  for (const root of mountedRoots.splice(0)) {
    await act(async () => {
      root.unmount();
    });
  }
  document.body.replaceChildren();
  useSessionMock.mockReset();
});

const guestRoutes: RouteObject[] = [
  {
    path: '/login',
    element: <GuestGuard />,
    children: [{ index: true, element: <p>Guest page</p> }],
  },
  { path: '/chat', element: <p>Chat page</p> },
];

const authenticatedRoutes: RouteObject[] = [
  {
    path: '/chat',
    element: <AuthGuard />,
    children: [{ index: true, element: <p>Private page</p> }],
  },
  { path: '/login', element: <p>Login page</p> },
];

describe('GuestGuard', () => {
  it('renders the guest outlet while session lookup is pending', async () => {
    useSessionMock.mockReturnValue({ data: null, isPending: true });

    const view = await renderRouter(guestRoutes, '/login');

    expect(view.container.textContent).toContain('Guest page');
    expect(view.router.state.location.pathname).toBe('/login');
  });

  it('renders the guest outlet when no session exists', async () => {
    useSessionMock.mockReturnValue({ data: null, isPending: false });

    const view = await renderRouter(guestRoutes, '/login');

    expect(view.container.textContent).toContain('Guest page');
    expect(view.router.state.location.pathname).toBe('/login');
  });

  it('redirects an authenticated session to chat', async () => {
    useSessionMock.mockReturnValue({ data: { user: { id: 'user-1' } }, isPending: false });

    const view = await renderRouter(guestRoutes, '/login');

    expect(view.container.textContent).toContain('Chat page');
    expect(view.router.state.location.pathname).toBe('/chat');
  });
});

describe('AuthGuard', () => {
  it('renders the existing loading treatment while session lookup is pending', async () => {
    useSessionMock.mockReturnValue({ data: null, isPending: true });

    const view = await renderRouter(authenticatedRoutes, '/chat');

    expect(view.container.textContent).toContain('Loading...');
    expect(view.router.state.location.pathname).toBe('/chat');
  });

  it('redirects an unauthenticated visitor to login', async () => {
    useSessionMock.mockReturnValue({ data: null, isPending: false });

    const view = await renderRouter(authenticatedRoutes, '/chat');

    expect(view.container.textContent).toContain('Login page');
    expect(view.router.state.location.pathname).toBe('/login');
  });

  it('renders the authenticated outlet when a session exists', async () => {
    useSessionMock.mockReturnValue({ data: { user: { id: 'user-1' } }, isPending: false });

    const view = await renderRouter(authenticatedRoutes, '/chat');

    expect(view.container.textContent).toContain('Private page');
    expect(view.router.state.location.pathname).toBe('/chat');
  });
});
