import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from "@nestjs/common";
import * as bcrypt from "bcrypt";
import type { Request } from "express";
import { PrismaService } from "../../prisma/prisma.service";
import type { ApiKeyAuthContext } from "./auth.types";

const API_KEY_PREFIX_LENGTH = 20; // ej. "sk_live_abc123def456"

@Injectable()
export class ApiKeyGuard implements CanActivate {
  constructor(private readonly prisma: PrismaService) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const req = context.switchToHttp().getRequest<Request>();
    const auth = req.header("authorization");
    if (!auth || !auth.startsWith("Bearer ")) {
      throw new UnauthorizedException("Missing Bearer token");
    }
    const token = auth.slice(7).trim();
    if (!/^(sk|pk)_(live|test)_[0-9a-z]+$/.test(token)) {
      throw new UnauthorizedException("Invalid API key format");
    }

    const prefix = token.slice(0, API_KEY_PREFIX_LENGTH);
    const candidate = await this.prisma.merchantApiKey.findUnique({
      where: { prefix },
      select: {
        id: true,
        merchantId: true,
        type: true,
        environment: true,
        scope: true,
        hash: true,
        revokedAt: true,
        merchant: { select: { status: true } },
      },
    });

    if (!candidate || candidate.revokedAt) {
      throw new UnauthorizedException("Invalid API key");
    }
    if (candidate.merchant.status !== "active") {
      throw new UnauthorizedException("Merchant is not active");
    }

    const matches = await bcrypt.compare(token, candidate.hash);
    if (!matches) {
      throw new UnauthorizedException("Invalid API key");
    }

    // Fire-and-forget; no bloquea la request si la actualización falla
    void this.prisma.merchantApiKey
      .update({ where: { id: candidate.id }, data: { lastUsedAt: new Date() } })
      .catch(() => undefined);

    const authContext: ApiKeyAuthContext = {
      kind: "api_key",
      apiKeyId: candidate.id,
      merchantId: candidate.merchantId,
      type: candidate.type,
      environment: candidate.environment,
      scope: candidate.scope,
    };
    req.auth = authContext;
    return true;
  }
}
