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';
import { projectFixtures } from './page-fixtures';

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

vi.mock('@/lib/api', () => ({
  api: apiMock,
}));

import { ProjectsPage } from './projects';

interface Deferred<T> {
  promise: Promise<T>;
  resolve: (value: T) => void;
  reject: (reason?: unknown) => void;
}

function createDeferred<T>(): Deferred<T> {
  let resolve!: (value: T) => void;
  let reject!: (reason?: unknown) => void;
  const promise = new Promise<T>((res, rej) => {
    resolve = res;
    reject = rej;
  });
  return { promise, resolve, reject };
}

let root: Root | null = null;
let container: HTMLDivElement;

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

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

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

async function renderProjectsPage(): Promise<ReturnType<typeof createMemoryRouter>> {
  const routes: RouteObject[] = [
    { path: '/projects', element: <ProjectsPage /> },
    { path: '/projects/:id', element: <p>Project detail target</p> },
  ];

  const router = createMemoryRouter(routes, { initialEntries: ['/projects'] });
  container = document.createElement('div');
  document.body.append(container);
  root = createRoot(container);

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

  return router;
}

describe('ProjectsPage', () => {
  it('shows a visible loading state while the project request is in flight', async () => {
    const deferred = createDeferred<typeof projectFixtures>();
    apiMock.mockReturnValueOnce(deferred.promise);

    await renderProjectsPage();

    expect(container.textContent).toContain('Loading projects...');

    await act(async () => {
      deferred.resolve(projectFixtures);
      await deferred.promise;
    });
  });

  it('renders project cards from the API and navigates to a project detail route on click', async () => {
    apiMock.mockResolvedValueOnce(projectFixtures);

    const router = await renderProjectsPage();

    expect(apiMock).toHaveBeenCalledWith('/api/projects');
    expect(container.textContent).toContain('Mosaic Stack');
    expect(container.textContent).toContain('Agent Runtime');

    const button = [...container.querySelectorAll('button')].find((candidate) =>
      candidate.textContent?.includes('Mosaic Stack'),
    );
    expect(button).toBeTruthy();

    await act(async () => {
      button?.dispatchEvent(new MouseEvent('click', { bubbles: true }));
    });

    expect(router.state.location.pathname).toBe('/projects/project-1');
    expect(container.textContent).toContain('Project detail target');
  });

  it('renders the empty state when the API returns no projects', async () => {
    apiMock.mockResolvedValueOnce([]);

    await renderProjectsPage();

    expect(container.textContent).toContain('No projects yet');
    expect(container.textContent).toContain(
      'Projects will appear here when created via the gateway API',
    );
  });

  it('renders a visible alert when the projects request fails', async () => {
    apiMock.mockRejectedValueOnce(new Error('Projects are unavailable'));

    await renderProjectsPage();

    const alert = container.querySelector('[role="alert"]');
    expect(alert).toBeTruthy();
    expect(alert?.textContent).toContain('Projects are unavailable');
  });
});
