import type { RequestPermissionRequest } from "@agentclientprotocol/sdk";
import { describe, expect, it, vi } from "vitest";
import { resolvePermissionRequest } from "./client.js";
import { extractAttachmentsFromPrompt, extractTextFromPrompt } from "./event-mapper.js";

function makePermissionRequest(
  overrides: Partial<RequestPermissionRequest> = {},
): RequestPermissionRequest {
  const { toolCall: toolCallOverride, options: optionsOverride, ...restOverrides } = overrides;
  const base: RequestPermissionRequest = {
    sessionId: "session-1",
    toolCall: {
      toolCallId: "tool-1",
      title: "read: src/index.ts",
      status: "pending",
    },
    options: [
      { kind: "allow_once", name: "Allow once", optionId: "allow" },
      { kind: "reject_once", name: "Reject once", optionId: "reject" },
    ],
  };

  return {
    ...base,
    ...restOverrides,
    toolCall: toolCallOverride ? { ...base.toolCall, ...toolCallOverride } : base.toolCall,
    options: optionsOverride ?? base.options,
  };
}

describe("resolvePermissionRequest", () => {
  it("auto-approves safe tools without prompting", async () => {
    const prompt = vi.fn(async () => true);
    const res = await resolvePermissionRequest(makePermissionRequest(), { prompt, log: () => {} });
    expect(res).toEqual({ outcome: { outcome: "selected", optionId: "allow" } });
    expect(prompt).not.toHaveBeenCalled();
  });

  it("prompts for dangerous tool names inferred from title", async () => {
    const prompt = vi.fn(async () => true);
    const res = await resolvePermissionRequest(
      makePermissionRequest({
        toolCall: { toolCallId: "tool-2", title: "exec: uname -a", status: "pending" },
      }),
      { prompt, log: () => {} },
    );
    expect(prompt).toHaveBeenCalledTimes(1);
    expect(prompt).toHaveBeenCalledWith("exec", "exec: uname -a");
    expect(res).toEqual({ outcome: { outcome: "selected", optionId: "allow" } });
  });

  it("prompts for non-read/search tools (write)", async () => {
    const prompt = vi.fn(async () => true);
    const res = await resolvePermissionRequest(
      makePermissionRequest({
        toolCall: { toolCallId: "tool-w", title: "write: /tmp/pwn", status: "pending" },
      }),
      { prompt, log: () => {} },
    );
    expect(prompt).toHaveBeenCalledTimes(1);
    expect(prompt).toHaveBeenCalledWith("write", "write: /tmp/pwn");
    expect(res).toEqual({ outcome: { outcome: "selected", optionId: "allow" } });
  });

  it("auto-approves search without prompting", async () => {
    const prompt = vi.fn(async () => true);
    const res = await resolvePermissionRequest(
      makePermissionRequest({
        toolCall: { toolCallId: "tool-s", title: "search: foo", status: "pending" },
      }),
      { prompt, log: () => {} },
    );
    expect(res).toEqual({ outcome: { outcome: "selected", optionId: "allow" } });
    expect(prompt).not.toHaveBeenCalled();
  });

  it("prompts for fetch even when tool name is known", async () => {
    const prompt = vi.fn(async () => false);
    const res = await resolvePermissionRequest(
      makePermissionRequest({
        toolCall: { toolCallId: "tool-f", title: "fetch: https://example.com", status: "pending" },
      }),
      { prompt, log: () => {} },
    );
    expect(prompt).toHaveBeenCalledTimes(1);
    expect(res).toEqual({ outcome: { outcome: "selected", optionId: "reject" } });
  });

  it("prompts when tool name contains read/search substrings but isn't a safe kind", async () => {
    const prompt = vi.fn(async () => false);
    const res = await resolvePermissionRequest(
      makePermissionRequest({
        toolCall: { toolCallId: "tool-t", title: "thread: reply", status: "pending" },
      }),
      { prompt, log: () => {} },
    );
    expect(prompt).toHaveBeenCalledTimes(1);
    expect(res).toEqual({ outcome: { outcome: "selected", optionId: "reject" } });
  });

  it("uses allow_always and reject_always when once options are absent", async () => {
    const options: RequestPermissionRequest["options"] = [
      { kind: "allow_always", name: "Always allow", optionId: "allow-always" },
      { kind: "reject_always", name: "Always reject", optionId: "reject-always" },
    ];
    const prompt = vi.fn(async () => false);
    const res = await resolvePermissionRequest(
      makePermissionRequest({
        toolCall: { toolCallId: "tool-3", title: "gateway: reload", status: "pending" },
        options,
      }),
      { prompt, log: () => {} },
    );
    expect(res).toEqual({ outcome: { outcome: "selected", optionId: "reject-always" } });
  });

  it("prompts when tool identity is unknown and can still approve", async () => {
    const prompt = vi.fn(async () => true);
    const res = await resolvePermissionRequest(
      makePermissionRequest({
        toolCall: {
          toolCallId: "tool-4",
          title: "Modifying critical configuration file",
          status: "pending",
        },
      }),
      { prompt, log: () => {} },
    );
    expect(prompt).toHaveBeenCalledWith(undefined, "Modifying critical configuration file");
    expect(res).toEqual({ outcome: { outcome: "selected", optionId: "allow" } });
  });

  it("returns cancelled when no permission options are present", async () => {
    const prompt = vi.fn(async () => true);
    const res = await resolvePermissionRequest(makePermissionRequest({ options: [] }), {
      prompt,
      log: () => {},
    });
    expect(prompt).not.toHaveBeenCalled();
    expect(res).toEqual({ outcome: { outcome: "cancelled" } });
  });
});

describe("acp event mapper", () => {
  it("extracts text and resource blocks into prompt text", () => {
    const text = extractTextFromPrompt([
      { type: "text", text: "Hello" },
      { type: "resource", resource: { uri: "file:///tmp/spec.txt", text: "File contents" } },
      { type: "resource_link", uri: "https://example.com", name: "Spec", title: "Spec" },
      { type: "image", data: "abc", mimeType: "image/png" },
    ]);

    expect(text).toBe("Hello\nFile contents\n[Resource link (Spec)] https://example.com");
  });

  it("extracts image blocks into gateway attachments", () => {
    const attachments = extractAttachmentsFromPrompt([
      { type: "image", data: "abc", mimeType: "image/png" },
      { type: "image", data: "", mimeType: "image/png" },
      { type: "text", text: "ignored" },
    ]);

    expect(attachments).toEqual([
      {
        type: "image",
        mimeType: "image/png",
        content: "abc",
      },
    ]);
  });
});
