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 { describe, beforeEach, expect, it, vi } from "vitest";
import { GatekeeperService } from "../gatekeeper/gatekeeper.service";
import { QueueNotificationsService } from "./queue-notifications.service";
import {
  type WoodpeckerWebhookPayload,
  WoodpeckerWebhookController,
} from "./woodpecker-webhook.controller";

function signPayload(payload: WoodpeckerWebhookPayload, secret: string): string {
  return createHmac("sha256", secret).update(JSON.stringify(payload)).digest("hex");
}

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

  const mockService = {
    notifyAgentCiResult: vi.fn(),
  };
  const mockGatekeeperService = {
    handleCiEvent: vi.fn(),
  };

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

  beforeEach(async () => {
    vi.clearAllMocks();
    mockConfigService.get.mockImplementation((key: string) => {
      if (key === "WOODPECKER_WEBHOOK_SECRET") {
        return "test-secret";
      }

      return undefined;
    });

    const module: TestingModule = await Test.createTestingModule({
      controllers: [WoodpeckerWebhookController],
      providers: [
        { provide: QueueNotificationsService, useValue: mockService },
        { provide: GatekeeperService, useValue: mockGatekeeperService },
        { provide: ConfigService, useValue: mockConfigService },
      ],
    }).compile();

    controller = module.get<WoodpeckerWebhookController>(WoodpeckerWebhookController);
  });

  it("accepts a valid signature and forwards the payload to the service", async () => {
    const payload: WoodpeckerWebhookPayload = {
      branch: "feat/ms24-ci-webhook",
      status: "success",
      buildUrl: "https://ci.example/build/123",
      repo: "mosaic/stack",
      prNumber: 42,
      headSha: "abcdef1234567890",
    };
    const signature = signPayload(payload, "test-secret");
    mockService.notifyAgentCiResult.mockResolvedValue({ notified: 2 });

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

    expect(mockService.notifyAgentCiResult).toHaveBeenCalledWith(payload);
    expect(mockGatekeeperService.handleCiEvent).toHaveBeenCalledWith(
      "mosaic/stack",
      42,
      "abcdef1234567890",
      "success"
    );
  });

  it("returns ok without notifying when the signature is invalid", async () => {
    const warnSpy = vi.spyOn(Logger.prototype, "warn").mockImplementation(() => undefined);
    const payload: WoodpeckerWebhookPayload = {
      branch: "feat/ms24-ci-webhook",
      status: "failure",
      buildUrl: "https://ci.example/build/123",
      repo: "mosaic/stack",
    };

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

    expect(mockService.notifyAgentCiResult).not.toHaveBeenCalled();
    expect(warnSpy).toHaveBeenCalledWith(
      expect.stringContaining("invalid Woodpecker webhook signature")
    );
  });

  it("accepts the payload when the webhook secret is missing", async () => {
    const warnSpy = vi.spyOn(Logger.prototype, "warn").mockImplementation(() => undefined);
    const payload: WoodpeckerWebhookPayload = {
      branch: "feat/ms24-ci-webhook",
      status: "success",
      buildUrl: "https://ci.example/build/123",
      repo: "mosaic/stack",
    };
    mockConfigService.get.mockReturnValue(undefined);
    mockService.notifyAgentCiResult.mockResolvedValue({ notified: 1 });

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

    expect(mockService.notifyAgentCiResult).toHaveBeenCalledWith(payload);
    expect(warnSpy).toHaveBeenCalledWith(
      expect.stringContaining("WOODPECKER_WEBHOOK_SECRET is not configured")
    );
  });

  it("returns zero notifications when no active branch matches", async () => {
    const payload: WoodpeckerWebhookPayload = {
      branch: "feat/ms24-ci-webhook",
      status: "success",
      buildUrl: "https://ci.example/build/999",
      repo: "mosaic/stack",
    };
    mockService.notifyAgentCiResult.mockResolvedValue({ notified: 0 });

    await expect(
      controller.handleWebhook(
        { rawBody: Buffer.from(JSON.stringify(payload)) } as Request,
        payload,
        signPayload(payload, "test-secret")
      )
    ).resolves.toEqual({ ok: true, notified: 0 });
  });

  it("does not call Gatekeeper when the PR metadata is missing", async () => {
    const payload: WoodpeckerWebhookPayload = {
      branch: "feat/ms24-ci-webhook",
      status: "success",
      buildUrl: "https://ci.example/build/555",
      repo: "mosaic/stack",
    };
    mockService.notifyAgentCiResult.mockResolvedValue({ notified: 1 });

    await controller.handleWebhook(
      { rawBody: Buffer.from(JSON.stringify(payload)) } as Request,
      payload,
      signPayload(payload, "test-secret")
    );

    expect(mockGatekeeperService.handleCiEvent).not.toHaveBeenCalled();
  });
});
