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 { apiMock, useSessionMock } = vi.hoisted(() => ({
  apiMock: vi.fn(),
  useSessionMock: vi.fn(),
}));

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

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

import { AdminPage } from './admin';
import { AdminGuard } from '@/spa/guards';

const userFixtures = {
  users: [
    {
      id: 'u-admin',
      name: 'Ada Admin',
      email: 'ada@example.test',
      role: 'admin',
      banned: false,
      banReason: null,
      createdAt: '2026-08-01T00:00:00.000Z',
      updatedAt: '2026-08-01T00:00:00.000Z',
    },
    {
      id: 'u-member',
      name: 'Mel Member',
      email: 'mel@example.test',
      role: 'member',
      banned: true,
      banReason: 'spam',
      createdAt: '2026-08-02T00:00:00.000Z',
      updatedAt: '2026-08-02T00:00:00.000Z',
    },
  ],
  total: 2,
};

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();
  useSessionMock.mockReset();
});

async function renderAdminRoute(): Promise<void> {
  const routes: RouteObject[] = [
    {
      element: <AdminGuard />,
      children: [{ path: '/admin', element: <AdminPage /> }],
    },
    { path: '/', element: <div>home page</div> },
    { path: '/login', element: <div>login page</div> },
  ];
  const router = createMemoryRouter(routes, { initialEntries: ['/admin'] });
  container = document.createElement('div');
  document.body.append(container);
  root = createRoot(container);

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

function sessionWithRole(role: string | undefined): { data: unknown; isPending: boolean } {
  return {
    data: { user: { id: 'u-1', name: 'Test', email: 't@example.test', role } },
    isPending: false,
  };
}

describe('AdminGuard', () => {
  it('redirects unauthenticated visitors to /login', async () => {
    useSessionMock.mockReturnValue({ data: null, isPending: false });

    await renderAdminRoute();

    expect(container.textContent).toContain('login page');
    expect(apiMock).not.toHaveBeenCalled();
  });

  it('redirects non-admin users to /', async () => {
    useSessionMock.mockReturnValue(sessionWithRole('member'));

    await renderAdminRoute();

    expect(container.textContent).toContain('home page');
    expect(apiMock).not.toHaveBeenCalled();
  });

  it('renders the admin page for admin users', async () => {
    useSessionMock.mockReturnValue(sessionWithRole('admin'));
    apiMock.mockResolvedValueOnce(userFixtures);

    await renderAdminRoute();

    expect(container.textContent).toContain('Admin Panel');
  });
});

describe('AdminPage users tab', () => {
  it('lists users with role and ban status after load', async () => {
    useSessionMock.mockReturnValue(sessionWithRole('admin'));
    apiMock.mockResolvedValueOnce(userFixtures);

    await renderAdminRoute();

    expect(apiMock).toHaveBeenCalledWith('/api/admin/users');
    expect(container.textContent).toContain('Ada Admin');
    expect(container.textContent).toContain('Mel Member');
    expect(container.textContent).toContain('Banned');
    expect(container.textContent).toContain('2 user(s)');
  });

  it('shows the load error with a retry control', async () => {
    useSessionMock.mockReturnValue(sessionWithRole('admin'));
    apiMock.mockRejectedValueOnce(new Error('gateway unavailable'));

    await renderAdminRoute();

    expect(container.textContent).toContain('gateway unavailable');

    apiMock.mockResolvedValueOnce(userFixtures);
    const retry = [...container.querySelectorAll('button')].find((b) =>
      b.textContent?.includes('Retry'),
    );
    expect(retry).toBeTruthy();
    await act(async () => {
      retry?.dispatchEvent(new MouseEvent('click', { bubbles: true }));
    });

    expect(container.textContent).toContain('Ada Admin');
  });

  it('posts to the ban endpoint and reloads on Ban', async () => {
    useSessionMock.mockReturnValue(sessionWithRole('admin'));
    apiMock.mockResolvedValue(userFixtures);

    await renderAdminRoute();

    const banButton = [...container.querySelectorAll('button')].find(
      (b) => b.textContent === 'Ban',
    );
    expect(banButton).toBeTruthy();
    await act(async () => {
      banButton?.dispatchEvent(new MouseEvent('click', { bubbles: true }));
    });

    expect(apiMock).toHaveBeenCalledWith('/api/admin/users/u-admin/ban', { method: 'POST' });
  });
});

describe('AdminPage health tab', () => {
  it('loads health status when the tab is opened', async () => {
    useSessionMock.mockReturnValue(sessionWithRole('admin'));
    apiMock.mockResolvedValueOnce(userFixtures).mockResolvedValueOnce({
      status: 'ok',
      database: { status: 'ok', latencyMs: 3 },
      cache: { status: 'ok', latencyMs: 1 },
      agentPool: { activeSessions: 2 },
      providers: [{ id: 'ollama', name: 'Ollama', available: true, modelCount: 4 }],
      checkedAt: '2026-08-26T00:00:00.000Z',
    });

    await renderAdminRoute();

    const healthTab = [...container.querySelectorAll('button')].find((b) =>
      b.textContent?.includes('System Health'),
    );
    await act(async () => {
      healthTab?.dispatchEvent(new MouseEvent('click', { bubbles: true }));
    });

    expect(apiMock).toHaveBeenCalledWith('/api/admin/health');
    expect(container.textContent).toContain('Database (PostgreSQL)');
    expect(container.textContent).toContain('Active sessions: 2');
    expect(container.textContent).toContain('4 models');
  });
});
