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

vi.mock("../../src/plugin-sdk/index.js", () => ({ emptyPluginConfigSchema: () => ({}) }));
vi.mock("./src/classifier.js", () => ({ classifyMessage: vi.fn() }));
vi.mock("./src/store.js", () => ({
  initSchema: vi.fn(),
  insertComplaint: vi.fn(),
  markNotified: vi.fn(),
  getPendingNotifications: vi.fn(() => []),
}));
vi.mock("./src/destinations.js", () => ({ sendAlerts: vi.fn() }));
vi.mock("./src/context.js", () => ({ fetchContext: vi.fn(() => []) }));

import plugin from "./index.js";
import { classifyMessage } from "./src/classifier.js";
import { sendAlerts } from "./src/destinations.js";
import { insertComplaint, markNotified } from "./src/store.js";

const PLUGIN_ID = "complaint-listener";

function makeApi(config: any = {}, manualStart = false) {
  let capturedSvc: any = null;
  const ledger = { all: vi.fn(() => []) };
  const api = {
    id: PLUGIN_ID,
    config: {},
    pluginConfig: {
      subscriptions: [{ kind: "channel", channel: "telegram", accountId: "default" }],
      destinations: [{ channel: "whatsapp", accountId: "default", to: "+51999" }],
      ...config,
    },
    logger: { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() },
    runtime: {
      channel: {
        whatsapp: { sendMessageWhatsApp: vi.fn() },
        telegram: { sendMessageTelegram: vi.fn() },
      },
      messageLedger: { getDb: () => ledger as any },
    },
    registerService: vi.fn((svc) => {
      capturedSvc = svc;
      if (!manualStart) svc.start({ stateDir: "/tmp", config: {}, logger: {} });
    }),
    on: vi.fn(),
    triggerStart: async () => capturedSvc.start({ stateDir: "/tmp", config: {}, logger: {} }),
  };
  return api;
}

const complaintResult = { is_complaint: true, severity: "high" as const, category: "product_defect" as const, summary: "Broken" };
const inboundEvent = {
  channel: "telegram",
  accountId: "default",
  chatId: "c1",
  content: "your product is broken",
  isGroup: false,
  senderId: "u1",
  messageId: "m1",
};

describe("complaint-listener wiring", () => {
  beforeEach(() => {
    vi.resetAllMocks();
    process.env.ANTHROPIC_API_KEY = "k";
    (insertComplaint as any).mockReturnValue(42);
    (sendAlerts as any).mockResolvedValue({ total: 1, successCount: 1, perDestination: [] });
  });

  it("registers both hooks + service", () => {
    const api = makeApi() as any;
    plugin.register(api);
    expect(api.on).toHaveBeenCalledWith("message_inbound", expect.any(Function));
    expect(api.on).toHaveBeenCalledWith("message_received", expect.any(Function));
    expect(api.registerService).toHaveBeenCalled();
  });

  it("channel sub: message_inbound matches and classifies", async () => {
    (classifyMessage as any).mockResolvedValue(complaintResult);
    const api = makeApi() as any;
    plugin.register(api);
    const inboundHandler = api.on.mock.calls.find((c: any) => c[0] === "message_inbound")[1];
    await inboundHandler(inboundEvent);
    expect(classifyMessage).toHaveBeenCalled();
    expect(insertComplaint).toHaveBeenCalled();
    expect(sendAlerts).toHaveBeenCalled();
    expect(markNotified).toHaveBeenCalledWith(expect.anything(), 42);
  });

  it("dedup: message_received does not re-process after message_inbound for same messageId", async () => {
    (classifyMessage as any).mockResolvedValue(complaintResult);
    const api = makeApi({
      subscriptions: [
        { kind: "channel", channel: "telegram", accountId: "default" },
        { kind: "agent", agentId: "panik" },
      ],
    }) as any;
    plugin.register(api);
    const inboundHandler = api.on.mock.calls.find((c: any) => c[0] === "message_inbound")[1];
    const receivedHandler = api.on.mock.calls.find((c: any) => c[0] === "message_received")[1];
    await inboundHandler(inboundEvent);
    await receivedHandler(
      { from: "u1", content: "your product is broken", messageId: "m1" },
      { channelId: "telegram", accountId: "default", chatId: "c1", agentId: "panik" },
    );
    expect(classifyMessage).toHaveBeenCalledTimes(1);
  });

  it("agent sub: message_received matches if no channel sub matched", async () => {
    (classifyMessage as any).mockResolvedValue(complaintResult);
    const api = makeApi({
      subscriptions: [{ kind: "agent", agentId: "panik" }],
    }) as any;
    plugin.register(api);
    const receivedHandler = api.on.mock.calls.find((c: any) => c[0] === "message_received")[1];
    await receivedHandler(
      { from: "u1", content: "this product is broken", messageId: "m9" },
      { channelId: "telegram", accountId: "default", chatId: "c1", agentId: "panik" },
    );
    expect(classifyMessage).toHaveBeenCalled();
  });

  it("non-complaint: no insert, no alert", async () => {
    (classifyMessage as any).mockResolvedValue({ is_complaint: false, severity: "low", category: "other", summary: "" });
    const api = makeApi() as any;
    plugin.register(api);
    const h = api.on.mock.calls.find((c: any) => c[0] === "message_inbound")[1];
    await h(inboundEvent);
    expect(insertComplaint).not.toHaveBeenCalled();
    expect(sendAlerts).not.toHaveBeenCalled();
  });

  it("all destinations fail → no markNotified", async () => {
    (classifyMessage as any).mockResolvedValue(complaintResult);
    (sendAlerts as any).mockResolvedValue({ total: 1, successCount: 0, perDestination: [] });
    const api = makeApi() as any;
    plugin.register(api);
    const h = api.on.mock.calls.find((c: any) => c[0] === "message_inbound")[1];
    await h(inboundEvent);
    expect(markNotified).not.toHaveBeenCalled();
  });

  it("ledger disabled: warns + no-ops", async () => {
    const api = makeApi() as any;
    api.runtime.messageLedger.getDb = () => null;
    plugin.register(api);
    const h = api.on.mock.calls.find((c: any) => c[0] === "message_inbound")[1];
    await h(inboundEvent);
    expect(api.logger.warn).toHaveBeenCalled();
    expect(classifyMessage).not.toHaveBeenCalled();
  });

  it("empty subscriptions: skips all classification", async () => {
    const api = makeApi({ subscriptions: [] }) as any;
    plugin.register(api);
    const h = api.on.mock.calls.find((c: any) => c[0] === "message_inbound")[1];
    await h(inboundEvent);
    expect(classifyMessage).not.toHaveBeenCalled();
  });
});
