import { Body, Controller, Headers, Logger, Post, Req, type RawBodyRequest } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { createHmac, timingSafeEqual } from "node:crypto";
import type { Request } from "express";
import { SkipCsrf } from "../common/decorators/skip-csrf.decorator";
import { GiteaPrWebhookDto } from "./dto/gitea-pr-webhook.dto";
import { GatekeeperService } from "./gatekeeper.service";

@Controller("gatekeeper/webhook")
export class GatekeeperController {
  private readonly logger = new Logger(GatekeeperController.name);

  constructor(
    private readonly gatekeeperService: GatekeeperService,
    private readonly configService: ConfigService
  ) {}

  @SkipCsrf()
  @Post("gitea")
  handleWebhook(
    @Req() req: RawBodyRequest<Request>,
    @Body() body: GiteaPrWebhookDto,
    @Headers("x-gitea-signature") signature: string | undefined
  ): Promise<{ ok: boolean }> {
    const secret = this.configService.get<string>("GITEA_WEBHOOK_SECRET");

    if (secret && !this.isValidSignature(this.getRequestBody(req, body), signature, secret)) {
      this.logger.warn("Received invalid Gitea webhook signature");
      return Promise.resolve({ ok: true });
    }

    if (!secret) {
      this.logger.warn("GITEA_WEBHOOK_SECRET is not configured; accepting Gitea webhook");
    }

    void this.gatekeeperService.handlePrEvent(body).catch((error: unknown) => {
      const message = error instanceof Error ? error.message : String(error);
      this.logger.error(`Failed to process Gitea PR webhook: ${message}`);
    });

    return Promise.resolve({ ok: true });
  }

  private getRequestBody(req: RawBodyRequest<Request>, body: GiteaPrWebhookDto): Buffer {
    if (Buffer.isBuffer(req.rawBody)) {
      return req.rawBody;
    }

    return Buffer.from(JSON.stringify(body));
  }

  private isValidSignature(body: Buffer, signature: string | undefined, secret: string): boolean {
    if (!signature) {
      return false;
    }

    const expected = createHmac("sha256", secret).update(body).digest("hex");
    const actual = Buffer.from(signature);
    const expectedBuffer = Buffer.from(expected);

    if (actual.length !== expectedBuffer.length) {
      return false;
    }

    return timingSafeEqual(actual, expectedBuffer);
  }
}
