import { describe, it, expect, vi, beforeEach } from "vitest";

// --- mocks ----------------------------------------------------------------

const mockPage = {
  goto: vi.fn().mockResolvedValue(undefined),
};

const mockContext = {
  newPage: vi.fn().mockResolvedValue(mockPage),
  close: vi.fn().mockResolvedValue(undefined),
};

const mockBrowser = {
  newContext: vi.fn().mockResolvedValue(mockContext),
  close: vi.fn().mockResolvedValue(undefined),
};

const mockChromium = {
  launch: vi.fn().mockResolvedValue(mockBrowser),
};

vi.mock("playwright", () => ({
  chromium: mockChromium,
}));

// Mock fs.existsSync — return true by default; individual tests override as needed.
vi.mock("fs", () => ({
  existsSync: vi.fn().mockReturnValue(true),
}));

import { existsSync } from "fs";
import { pw_launch_browser } from "./pw_launch_browser";

// --------------------------------------------------------------------------

beforeEach(() => {
  vi.clearAllMocks();
  // Reset page.goto and existsSync defaults after clearing
  mockPage.goto.mockResolvedValue(undefined);
  mockContext.newPage.mockResolvedValue(mockPage);
  mockContext.close.mockResolvedValue(undefined);
  mockBrowser.newContext.mockResolvedValue(mockContext);
  mockBrowser.close.mockResolvedValue(undefined);
  mockChromium.launch.mockResolvedValue(mockBrowser);
  vi.mocked(existsSync).mockReturnValue(true);
});

// --------------------------------------------------------------------------

describe("pw_launch_browser", () => {
  it("launch defaults", async () => {
    const handles = await pw_launch_browser();

    // chromium.launch called with defaults
    expect(mockChromium.launch).toHaveBeenCalledWith({ headless: true, slowMo: 0 });

    // context created with default viewport
    expect(mockBrowser.newContext).toHaveBeenCalledWith(
      expect.objectContaining({ viewport: { width: 1280, height: 800 } })
    );

    // no storageState in context options when not provided
    const ctxCall = mockBrowser.newContext.mock.calls[0][0] as Record<string, unknown>;
    expect(ctxCall.storageState).toBeUndefined();

    // page navigated to default baseUrl
    expect(mockPage.goto).toHaveBeenCalledWith("http://localhost:5180");

    // handles returned
    expect(handles.browser).toBe(mockBrowser);
    expect(handles.context).toBe(mockContext);
    expect(handles.page).toBe(mockPage);
    expect(typeof handles.close).toBe("function");
  });

  it("launch with storageState", async () => {
    vi.mocked(existsSync).mockReturnValue(true);

    const handles = await pw_launch_browser({
      headless: false,
      baseUrl: "http://localhost:5173",
      viewport: { width: 1920, height: 1080 },
      storageStatePath: "/tmp/auth.json",
      slowMo: 50,
    });

    expect(mockChromium.launch).toHaveBeenCalledWith({ headless: false, slowMo: 50 });
    expect(mockBrowser.newContext).toHaveBeenCalledWith(
      expect.objectContaining({
        viewport: { width: 1920, height: 1080 },
        storageState: "/tmp/auth.json",
      })
    );
    expect(mockPage.goto).toHaveBeenCalledWith("http://localhost:5173");
    expect(handles.page).toBe(mockPage);
  });

  it("close calls both", async () => {
    const handles = await pw_launch_browser();

    await handles.close();

    expect(mockContext.close).toHaveBeenCalledOnce();
    expect(mockBrowser.close).toHaveBeenCalledOnce();

    // context must close before browser (call order)
    const contextCloseOrder = mockContext.close.mock.invocationCallOrder[0];
    const browserCloseOrder = mockBrowser.close.mock.invocationCallOrder[0];
    expect(contextCloseOrder).toBeLessThan(browserCloseOrder);
  });

  it("storageState skipped when file missing", async () => {
    vi.mocked(existsSync).mockReturnValue(false);

    await pw_launch_browser({ storageStatePath: "/tmp/nonexistent.json" });

    const ctxCall = mockBrowser.newContext.mock.calls[0][0] as Record<string, unknown>;
    expect(ctxCall.storageState).toBeUndefined();
  });
});
