import { createHmac } from "node:crypto";
import { Logger } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { Test, type TestingModule } from "@nestjs/testing";
import type { Request } from "express";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { GatekeeperController } from "./gatekeeper.controller";
import { GatekeeperService } from "./gatekeeper.service";

describe("GatekeeperController", () => {
  let controller: GatekeeperController;

  const gatekeeperService = {
    handlePrEvent: vi.fn(),
  };

  const configService = {
    get: vi.fn(),
  };

  const payload = {
    type: "pull_request",
    action: "labeled",
    label: { name: "auto-merge" },
    repository: { full_name: "mosaic/stack" },
    sender: { login: "jason" },
    pull_request: {
      number: 7,
      body: "ready",
      base: { ref: "main" },
      head: { sha: "abcdef1234567890" },
      labels: [{ name: "auto-merge" }],
    },
  };

  beforeEach(async () => {
    vi.clearAllMocks();
    configService.get.mockImplementation((key: string) => {
      if (key === "GITEA_WEBHOOK_SECRET") {
        return "secret";
      }

      return undefined;
    });
    gatekeeperService.handlePrEvent.mockResolvedValue(undefined);

    const module: TestingModule = await Test.createTestingModule({
      controllers: [GatekeeperController],
      providers: [
        { provide: GatekeeperService, useValue: gatekeeperService },
        { provide: ConfigService, useValue: configService },
      ],
    }).compile();

    controller = module.get(GatekeeperController);
  });

  it("accepts a valid signature and schedules processing", async () => {
    const signature = createHmac("sha256", "secret").update(JSON.stringify(payload)).digest("hex");

    await expect(
      controller.handleWebhook(
        { rawBody: Buffer.from(JSON.stringify(payload)) } as Request,
        payload,
        signature
      )
    ).resolves.toEqual({ ok: true });

    expect(gatekeeperService.handlePrEvent).toHaveBeenCalledWith(payload);
  });

  it("ignores invalid signatures", async () => {
    const warnSpy = vi.spyOn(Logger.prototype, "warn").mockImplementation(() => undefined);

    await expect(controller.handleWebhook({} as Request, payload, "bad")).resolves.toEqual({
      ok: true,
    });

    expect(gatekeeperService.handlePrEvent).not.toHaveBeenCalled();
    expect(warnSpy).toHaveBeenCalledWith(
      expect.stringContaining("invalid Gitea webhook signature")
    );
  });

  it("accepts requests without validation when no secret is configured", async () => {
    const warnSpy = vi.spyOn(Logger.prototype, "warn").mockImplementation(() => undefined);
    configService.get.mockReturnValue(undefined);

    await expect(controller.handleWebhook({} as Request, payload, "")).resolves.toEqual({
      ok: true,
    });

    expect(gatekeeperService.handlePrEvent).toHaveBeenCalledWith(payload);
    expect(warnSpy).toHaveBeenCalledWith(
      expect.stringContaining("GITEA_WEBHOOK_SECRET is not configured")
    );
  });
});
