import { Injectable, Logger, UnauthorizedException } from '@nestjs/common';
import { createHmac, timingSafeEqual } from 'node:crypto';
import { PrismaService } from '../../../prisma/prisma.service';

/**
 * HmacVerifier — webhook push 签名验证。
 *
 * 协议:
 *   Header X-PAC-Host-Id: <uuid>         识别宿主,查 secret
 *   Header X-PAC-Timestamp: <unix-sec>   防重放,容忍 ±5min skew
 *   Header X-PAC-Signature: <hex>        hex(HMAC-SHA256(timestamp + "." + rawBody, secret))
 *
 * **secret 存储**:Host.pushSecretHashes(String[])。
 *   - 数组形式支持密钥轮换:遍历每个 secret 算 HMAC,任一命中即通过
 *   - 索引 0 = current,后置 = grace period 旧 key
 *   - 字段名带 "Hashes" 是历史命名,**实际存储 raw secret 字符串**(HMAC 必须双方都持有原文)
 *   - 生产环境应该在 application layer 加密存储(KMS / Vault),dev 明文 OK
 */
@Injectable()
export class HmacVerifier {
  private readonly logger = new Logger(HmacVerifier.name);
  private readonly maxSkewSeconds = 300; // 5min

  constructor(private readonly prisma: PrismaService) {}

  /**
   * 验签 + 重放检查。失败抛 UnauthorizedException。
   * 成功返回 host(已 lookup 出来,调用方复用)。
   */
  async verify(input: {
    hostIdHeader: string | undefined;
    timestampHeader: string | undefined;
    signatureHeader: string | undefined;
    rawBody: string;
  }): Promise<{ hostId: string; hostName: string }> {
    const { hostIdHeader, timestampHeader, signatureHeader, rawBody } = input;

    if (!hostIdHeader) throw new UnauthorizedException('缺 X-PAC-Host-Id header');
    if (!timestampHeader) throw new UnauthorizedException('缺 X-PAC-Timestamp header');
    if (!signatureHeader) throw new UnauthorizedException('缺 X-PAC-Signature header');

    // 时间窗校验
    const tsSec = Number(timestampHeader);
    if (!Number.isFinite(tsSec)) throw new UnauthorizedException('X-PAC-Timestamp 不是数字');
    const nowSec = Math.floor(Date.now() / 1000);
    const skew = Math.abs(nowSec - tsSec);
    if (skew > this.maxSkewSeconds) {
      throw new UnauthorizedException(`时间戳偏差 ${skew}s 超过 ${this.maxSkewSeconds}s,可能重放`);
    }

    // 查 host
    const host = await this.prisma.host.findUnique({
      where: { id: hostIdHeader },
      select: {
        id: true,
        name: true,
        active: true,
        pushSecretHashes: true,
      },
    });
    if (!host) throw new UnauthorizedException(`unknown host_id=${hostIdHeader}`);
    if (!host.active) throw new UnauthorizedException(`host=${host.name} 已停用`);
    if (host.pushSecretHashes.length === 0) {
      throw new UnauthorizedException(`host=${host.name} 未配置 push secret`);
    }

    // 遍历所有 secret(支持 key rotation),任一命中通过
    const signed = `${timestampHeader}.${rawBody}`;
    const expectedBuf = hexToBuf(signatureHeader);
    if (!expectedBuf) throw new UnauthorizedException('signature 不是合法 hex');

    let hit = false;
    for (const secret of host.pushSecretHashes) {
      const computed = createHmac('sha256', secret).update(signed).digest();
      if (computed.length === expectedBuf.length && timingSafeEqual(computed, expectedBuf)) {
        hit = true;
        break;
      }
    }
    if (!hit) {
      this.logger.warn(`HMAC mismatch host=${host.name}(${host.id})`);
      throw new UnauthorizedException('签名不匹配');
    }

    // tenant(集团)由 push 事件 / pull 数据自带,不在验签层推断。
    return { hostId: host.id, hostName: host.name };
  }

  /**
   * 给开发 / 测试 / CLI 用的签名生成工具(对外应该宿主自己实现)。
   * 不要在生产代码里调,只用于本地模拟 push。
   */
  static sign(secret: string, timestamp: number, rawBody: string): string {
    return createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex');
  }
}

function hexToBuf(hex: string): Buffer | null {
  if (!/^[0-9a-fA-F]+$/.test(hex) || hex.length % 2 !== 0) return null;
  return Buffer.from(hex, 'hex');
}
