import type { ButtonInteraction, ComponentData, StringSelectMenuInteraction } from "@buape/carbon";
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../../config/config.js";
import { createAgentComponentButton, createAgentSelectMenu } from "./agent-components.js";

const readAllowFromStoreMock = vi.hoisted(() => vi.fn());
const upsertPairingRequestMock = vi.hoisted(() => vi.fn());
const enqueueSystemEventMock = vi.hoisted(() => vi.fn());

vi.mock("../../pairing/pairing-store.js", () => ({
  readChannelAllowFromStore: (...args: unknown[]) => readAllowFromStoreMock(...args),
  upsertChannelPairingRequest: (...args: unknown[]) => upsertPairingRequestMock(...args),
}));

vi.mock("../../infra/system-events.js", async (importOriginal) => {
  const actual = await importOriginal<typeof import("../../infra/system-events.js")>();
  return {
    ...actual,
    enqueueSystemEvent: (...args: unknown[]) => enqueueSystemEventMock(...args),
  };
});

const createCfg = (): OpenClawConfig => ({}) as OpenClawConfig;

const createDmButtonInteraction = (overrides: Partial<ButtonInteraction> = {}) => {
  const reply = vi.fn().mockResolvedValue(undefined);
  const interaction = {
    rawData: { channel_id: "dm-channel" },
    user: { id: "123456789", username: "Alice", discriminator: "1234" },
    reply,
    ...overrides,
  } as unknown as ButtonInteraction;
  return { interaction, reply };
};

const createDmSelectInteraction = (overrides: Partial<StringSelectMenuInteraction> = {}) => {
  const reply = vi.fn().mockResolvedValue(undefined);
  const interaction = {
    rawData: { channel_id: "dm-channel" },
    user: { id: "123456789", username: "Alice", discriminator: "1234" },
    values: ["alpha"],
    reply,
    ...overrides,
  } as unknown as StringSelectMenuInteraction;
  return { interaction, reply };
};

beforeEach(() => {
  readAllowFromStoreMock.mockReset().mockResolvedValue([]);
  upsertPairingRequestMock.mockReset().mockResolvedValue({ code: "PAIRCODE", created: true });
  enqueueSystemEventMock.mockReset();
});

describe("agent components", () => {
  it("sends pairing reply when DM sender is not allowlisted", async () => {
    const button = createAgentComponentButton({
      cfg: createCfg(),
      accountId: "default",
      dmPolicy: "pairing",
    });
    const { interaction, reply } = createDmButtonInteraction();

    await button.run(interaction, { componentId: "hello" } as ComponentData);

    expect(reply).toHaveBeenCalledTimes(1);
    expect(reply.mock.calls[0]?.[0]?.content).toContain("Pairing code: PAIRCODE");
    expect(enqueueSystemEventMock).not.toHaveBeenCalled();
  });

  it("allows DM interactions when pairing store allowlist matches", async () => {
    readAllowFromStoreMock.mockResolvedValue(["123456789"]);
    const button = createAgentComponentButton({
      cfg: createCfg(),
      accountId: "default",
      dmPolicy: "allowlist",
    });
    const { interaction, reply } = createDmButtonInteraction();

    await button.run(interaction, { componentId: "hello" } as ComponentData);

    expect(reply).toHaveBeenCalledWith({ content: "✓", ephemeral: true });
    expect(enqueueSystemEventMock).toHaveBeenCalled();
  });

  it("matches tag-based allowlist entries for DM select menus", async () => {
    const select = createAgentSelectMenu({
      cfg: createCfg(),
      accountId: "default",
      dmPolicy: "allowlist",
      allowFrom: ["Alice#1234"],
    });
    const { interaction, reply } = createDmSelectInteraction();

    await select.run(interaction, { componentId: "hello" } as ComponentData);

    expect(reply).toHaveBeenCalledWith({ content: "✓", ephemeral: true });
    expect(enqueueSystemEventMock).toHaveBeenCalled();
  });
});
