import { describe, it, expect, vi, beforeEach } from 'vitest';
import { BrowserManager } from './manager.js';

// Use vi.hoisted() so these are available inside the hoisted vi.mock() call
const {
  mockGoto, mockTitle, mockUrl, mockClose, mockIsClosed,
  mockSetDefaultTimeout, mockPage, mockNewPage, mockPages,
  mockBrowserClose,
} = vi.hoisted(() => {
  const mockGoto = vi.fn().mockResolvedValue(undefined);
  const mockTitle = vi.fn().mockResolvedValue('Test Page');
  const mockUrl = vi.fn().mockReturnValue('https://example.com');
  const mockClose = vi.fn().mockResolvedValue(undefined);
  const mockIsClosed = vi.fn().mockReturnValue(false);
  const mockSetDefaultTimeout = vi.fn();

  const mockPage = {
    goto: mockGoto,
    title: mockTitle,
    url: mockUrl,
    close: mockClose,
    isClosed: mockIsClosed,
    setDefaultTimeout: mockSetDefaultTimeout,
  };

  const mockNewPage = vi.fn().mockResolvedValue(mockPage);
  const mockPages = vi.fn().mockResolvedValue([mockPage]);
  const mockBrowserClose = vi.fn().mockResolvedValue(undefined);

  return {
    mockGoto, mockTitle, mockUrl, mockClose, mockIsClosed,
    mockSetDefaultTimeout, mockPage, mockNewPage, mockPages,
    mockBrowserClose,
  };
});

vi.mock('puppeteer-core', () => ({
  default: {
    launch: vi.fn().mockResolvedValue({
      connected: true,
      newPage: mockNewPage,
      pages: mockPages,
      close: mockBrowserClose,
    }),
    connect: vi.fn().mockResolvedValue({
      connected: true,
      newPage: mockNewPage,
      pages: mockPages,
      close: mockBrowserClose,
    }),
  },
}));

describe('BrowserManager', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    mockPages.mockResolvedValue([mockPage]);
    mockIsClosed.mockReturnValue(false);
  });

  it('gets the default page via WebSocket endpoint', async () => {
    const manager = new BrowserManager({ wsEndpoint: 'ws://localhost:9222' });
    const page = await manager.getPage();
    expect(page).toBeDefined();
    expect(page.goto).toBeDefined();
  });

  it('reuses existing pages', async () => {
    const manager = new BrowserManager({ wsEndpoint: 'ws://localhost:9222' });
    const page1 = await manager.getPage();
    const page2 = await manager.getPage();
    expect(page1).toBe(page2);
  });

  it('creates a new page when existing one is closed', async () => {
    const manager = new BrowserManager({ wsEndpoint: 'ws://localhost:9222' });
    await manager.getPage();
    mockIsClosed.mockReturnValue(true);
    const page2 = await manager.getPage();
    expect(page2).toBeDefined();
  });

  it('enforces max pages limit', async () => {
    const manager = new BrowserManager({ wsEndpoint: 'ws://localhost:9222', maxPages: 1 });
    // The default page counts as one
    await manager.getPage();
    await expect(manager.newPage('second')).rejects.toThrow('Maximum concurrent pages');
  });

  it('shuts down cleanly', async () => {
    const manager = new BrowserManager({ wsEndpoint: 'ws://localhost:9222' });
    await manager.getPage();
    await manager.shutdown();
    expect(mockBrowserClose).toHaveBeenCalled();
  });

  it('closes a specific page', async () => {
    const manager = new BrowserManager({ wsEndpoint: 'ws://localhost:9222' });
    await manager.getPage('test-page');
    await manager.closePage('test-page');
    expect(mockClose).toHaveBeenCalled();
  });

  it('reports availability correctly', () => {
    // Without a valid path, isAvailable should return false
    expect(BrowserManager.isAvailable('/nonexistent/path')).toBe(false);
  });
});
