import {
  Body,
  Controller,
  ForbiddenException,
  Headers,
  HttpCode,
  HttpException,
  HttpStatus,
  Logger,
  NotFoundException,
  Post,
  Req,
  UnauthorizedException,
} from '@nestjs/common';
import type { RawBodyRequest } from '@nestjs/common';
import type { Request } from 'express';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { Public } from '@common/decorators/public.decorator';
import { SkipAssertAccess } from '@common/decorators/skip-assert-access.decorator';
import {
  GiteaWebhookService,
  type GiteaPushPayload,
} from './services/webhook.service';

/**
 * Gitea webhook 入口（07-api §4.4）
 *
 * - 端点：POST /api/v1/internal-apps/webhook/gitea
 * - 鉴权：HMAC-SHA256（X-Gitea-Signature），由 service 层校验
 * - **不走 Entra session**：Gitea 不能持员工 token；用单一 webhook secret + org 白名单
 *
 * 安全分层 + 解析逻辑全在 GiteaWebhookService；本 controller 只负责：
 * 1. 取 raw body（已由 NestFactory({ rawBody: true }) 启用）
 * 2. 调 verifySignature
 * 3. 透传 outcome 到 HTTP 响应
 */
@ApiTags('Internal App Platform')
@Controller('internal-apps/webhook')
export class GiteaWebhookController {
  private readonly logger = new Logger(GiteaWebhookController.name);

  constructor(private readonly webhookSvc: GiteaWebhookService) {}

  @Public()
  @Post('gitea')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: 'Gitea push webhook → 触发部署' })
  @SkipAssertAccess('webhook 无 user 上下文，鉴权走 HMAC + org 白名单；非员工资源访问')
  async handleGitea(
    @Req() req: RawBodyRequest<Request>,
    @Headers('x-gitea-signature') signature: string | undefined,
    @Body() body: GiteaPushPayload,
  ) {
    const rawBody = req.rawBody?.toString('utf8') ?? '';
    const valid = this.webhookSvc.verifySignature(rawBody, signature);
    if (!valid) {
      this.logger.warn(`webhook 签名校验失败 sig=${signature?.slice(0, 12) ?? 'missing'}...`);
      // 必须真返 401，否则 Gitea 看到 2xx 不重试，调用方也无从感知
      throw new UnauthorizedException({
        code: 'invalid_signature',
        message: 'HMAC 校验失败',
      });
    }

    const outcome = await this.webhookSvc.handlePush(body);

    if (outcome.ok) {
      // 直接 return；全局 TransformInterceptor 会包 {success,data}。
      // 不要手工再包一层，否则双包装坑见 .learnings/2026-05-14-transform-interceptor-double-wrap.md
      return outcome;
    }
    // 真 HTTP 状态码 + 标准 error 体
    const errBody = { code: outcome.code, message: outcome.message };
    switch (outcome.httpStatus) {
      case 403:
        throw new ForbiddenException(errBody);
      case 404:
        throw new NotFoundException(errBody);
      default:
        throw new HttpException(errBody, outcome.httpStatus);
    }
  }
}
