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

import {
  browserOpenTab,
  browserSnapshot,
  browserStatus,
  browserTabs,
} from "./client.js";
import {
  browserAct,
  browserArmDialog,
  browserArmFileChooser,
  browserConsoleMessages,
  browserNavigate,
  browserPdfSave,
  browserScreenshotAction,
} from "./client-actions.js";

describe("browser client", () => {
  afterEach(() => {
    vi.unstubAllGlobals();
  });

  it("wraps connection failures with a gateway hint", async () => {
    const refused = Object.assign(new Error("connect ECONNREFUSED 127.0.0.1"), {
      code: "ECONNREFUSED",
    });
    const fetchFailed = Object.assign(new TypeError("fetch failed"), {
      cause: refused,
    });

    vi.stubGlobal("fetch", vi.fn().mockRejectedValue(fetchFailed));

    await expect(browserStatus("http://127.0.0.1:18791")).rejects.toThrow(
      /Start .*gateway/i,
    );
  });

  it("adds useful timeout messaging for abort-like failures", async () => {
    vi.stubGlobal("fetch", vi.fn().mockRejectedValue(new Error("aborted")));
    await expect(browserStatus("http://127.0.0.1:18791")).rejects.toThrow(
      /timed out/i,
    );
  });

  it("surfaces non-2xx responses with body text", async () => {
    vi.stubGlobal(
      "fetch",
      vi.fn().mockResolvedValue({
        ok: false,
        status: 409,
        text: async () => "conflict",
      } as unknown as Response),
    );

    await expect(
      browserSnapshot("http://127.0.0.1:18791", { format: "aria", limit: 1 }),
    ).rejects.toThrow(/409: conflict/i);
  });

  it("uses the expected endpoints + methods for common calls", async () => {
    const calls: Array<{ url: string; init?: RequestInit }> = [];

    vi.stubGlobal(
      "fetch",
      vi.fn(async (url: string, init?: RequestInit) => {
        calls.push({ url, init });
        if (url.endsWith("/tabs") && (!init || init.method === undefined)) {
          return {
            ok: true,
            json: async () => ({
              running: true,
              tabs: [{ targetId: "t1", title: "T", url: "https://x" }],
            }),
          } as unknown as Response;
        }
        if (url.endsWith("/tabs/open")) {
          return {
            ok: true,
            json: async () => ({
              targetId: "t2",
              title: "N",
              url: "https://y",
            }),
          } as unknown as Response;
        }
        if (url.endsWith("/navigate")) {
          return {
            ok: true,
            json: async () => ({
              ok: true,
              targetId: "t1",
              url: "https://y",
            }),
          } as unknown as Response;
        }
        if (url.endsWith("/act")) {
          return {
            ok: true,
            json: async () => ({
              ok: true,
              targetId: "t1",
              url: "https://x",
              result: 1,
            }),
          } as unknown as Response;
        }
        if (url.endsWith("/hooks/file-chooser")) {
          return {
            ok: true,
            json: async () => ({ ok: true }),
          } as unknown as Response;
        }
        if (url.endsWith("/hooks/dialog")) {
          return {
            ok: true,
            json: async () => ({ ok: true }),
          } as unknown as Response;
        }
        if (url.includes("/console?")) {
          return {
            ok: true,
            json: async () => ({
              ok: true,
              targetId: "t1",
              messages: [],
            }),
          } as unknown as Response;
        }
        if (url.endsWith("/pdf")) {
          return {
            ok: true,
            json: async () => ({
              ok: true,
              path: "/tmp/a.pdf",
              targetId: "t1",
              url: "https://x",
            }),
          } as unknown as Response;
        }
        if (url.endsWith("/screenshot")) {
          return {
            ok: true,
            json: async () => ({
              ok: true,
              path: "/tmp/a.png",
              targetId: "t1",
              url: "https://x",
            }),
          } as unknown as Response;
        }
        if (url.includes("/snapshot?")) {
          return {
            ok: true,
            json: async () => ({
              ok: true,
              format: "aria",
              targetId: "t1",
              url: "https://x",
              nodes: [],
            }),
          } as unknown as Response;
        }
        return {
          ok: true,
          json: async () => ({
            enabled: true,
            controlUrl: "http://127.0.0.1:18791",
            running: true,
            pid: 1,
            cdpPort: 18792,
            chosenBrowser: "chrome",
            userDataDir: "/tmp",
            color: "#FF4500",
            headless: false,
            attachOnly: false,
          }),
        } as unknown as Response;
      }),
    );

    await expect(
      browserStatus("http://127.0.0.1:18791"),
    ).resolves.toMatchObject({
      running: true,
      cdpPort: 18792,
    });

    await expect(browserTabs("http://127.0.0.1:18791")).resolves.toHaveLength(
      1,
    );
    await expect(
      browserOpenTab("http://127.0.0.1:18791", "https://example.com"),
    ).resolves.toMatchObject({ targetId: "t2" });

    await expect(
      browserSnapshot("http://127.0.0.1:18791", { format: "aria", limit: 1 }),
    ).resolves.toMatchObject({ ok: true, format: "aria" });

    await expect(
      browserNavigate("http://127.0.0.1:18791", { url: "https://example.com" }),
    ).resolves.toMatchObject({ ok: true, targetId: "t1" });
    await expect(
      browserAct("http://127.0.0.1:18791", { kind: "click", ref: "1" }),
    ).resolves.toMatchObject({ ok: true, targetId: "t1" });
    await expect(
      browserArmFileChooser("http://127.0.0.1:18791", {
        paths: ["/tmp/a.txt"],
      }),
    ).resolves.toMatchObject({ ok: true });
    await expect(
      browserArmDialog("http://127.0.0.1:18791", { accept: true }),
    ).resolves.toMatchObject({ ok: true });
    await expect(
      browserConsoleMessages("http://127.0.0.1:18791", { level: "error" }),
    ).resolves.toMatchObject({ ok: true, targetId: "t1" });
    await expect(
      browserPdfSave("http://127.0.0.1:18791"),
    ).resolves.toMatchObject({ ok: true, path: "/tmp/a.pdf" });
    await expect(
      browserScreenshotAction("http://127.0.0.1:18791", { fullPage: true }),
    ).resolves.toMatchObject({ ok: true, path: "/tmp/a.png" });

    expect(calls.some((c) => c.url.endsWith("/tabs"))).toBe(true);
    const open = calls.find((c) => c.url.endsWith("/tabs/open"));
    expect(open?.init?.method).toBe("POST");

    const screenshot = calls.find((c) => c.url.endsWith("/screenshot"));
    expect(screenshot?.init?.method).toBe("POST");
  });
});
