import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { logger } from "../lib/logger.js";
import { dispatchWebhooks } from "../lib/webhook-dispatcher.js";

const { sendSlackNotification } = vi.hoisted(() => ({
  sendSlackNotification: vi.fn(),
}));

vi.mock("../lib/slack-notify.js", () => ({
  sendSlackNotification,
}));

vi.mock("../lib/logger.js", () => ({
  logger: {
    error: vi.fn(),
    warn: vi.fn(),
    info: vi.fn(),
    debug: vi.fn(),
  },
}));

describe("webhook dispatcher logging", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  afterEach(() => {
    vi.unstubAllGlobals();
  });

  it("logs dispatcher-level failures with event context", async () => {
    const db = {
      webhook: {
        findMany: vi.fn().mockRejectedValue(new Error("database unavailable")),
      },
    };

    dispatchWebhooks(db, "estimate.submitted", { id: "estimate_1" });

    await vi.waitFor(() => {
      expect(logger.error).toHaveBeenCalledWith(
        {
          err: expect.any(Error),
          event: "estimate.submitted",
        },
        "Failed to dispatch webhooks",
      );
    });
  });

  it("logs webhook delivery failures with webhook metadata", async () => {
    sendSlackNotification.mockRejectedValueOnce(new Error("slack down"));

    const db = {
      webhook: {
        findMany: vi.fn().mockResolvedValue([
          {
            id: "wh_1",
            name: "Ops Slack",
            url: "https://hooks.slack.com/services/test",
            secret: null,
            events: ["project.created"],
          },
        ]),
      },
    };

    dispatchWebhooks(db, "project.created", { id: "project_1", name: "Project One" });

    await vi.waitFor(() => {
      expect(logger.warn).toHaveBeenCalledWith(
        {
          err: expect.any(Error),
          event: "project.created",
          webhookId: "wh_1",
          webhookName: "Ops Slack",
          webhookUrl: "https://hooks.slack.com/services/test",
        },
        "Webhook delivery failed",
      );
    });
  });

  it("treats non-2xx HTTP webhook responses as delivery failures", async () => {
    const fetchMock = vi.fn().mockResolvedValue({
      ok: false,
      status: 500,
    });
    vi.stubGlobal("fetch", fetchMock);

    const db = {
      webhook: {
        findMany: vi.fn().mockResolvedValue([
          {
            id: "wh_http_1",
            name: "Project Webhook",
            url: "https://example.com/webhooks/project",
            secret: "secret",
            events: ["project.created"],
          },
        ]),
      },
    };

    dispatchWebhooks(db, "project.created", { id: "project_1", name: "Project One" });

    await vi.waitFor(() => {
      expect(logger.warn).toHaveBeenCalledWith(
        {
          err: expect.any(Error),
          event: "project.created",
          webhookId: "wh_http_1",
          webhookName: "Project Webhook",
          webhookUrl: "https://example.com/webhooks/project",
        },
        "Webhook delivery failed",
      );
    });

    expect(fetchMock).toHaveBeenCalledTimes(1);
  });
});
