import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { timingSafeEqual } from "crypto";

/**
 * ApiKeyGuard - Authentication guard for service-to-service communication
 *
 * Validates the X-API-Key header against the COORDINATOR_API_KEY environment variable.
 * Uses constant-time comparison to prevent timing attacks.
 *
 * Usage:
 * @UseGuards(ApiKeyGuard)
 * @Controller('coordinator')
 * export class CoordinatorIntegrationController { ... }
 */
@Injectable()
export class ApiKeyGuard implements CanActivate {
  constructor(private readonly configService: ConfigService) {}

  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest<{ headers: Record<string, string> }>();
    const providedKey = this.extractApiKeyFromHeader(request);

    if (!providedKey) {
      throw new UnauthorizedException("No API key provided");
    }

    const configuredKey = this.configService.get<string>("COORDINATOR_API_KEY");

    if (!configuredKey) {
      throw new UnauthorizedException("API key authentication not configured");
    }

    if (!this.isValidApiKey(providedKey, configuredKey)) {
      throw new UnauthorizedException("Invalid API key");
    }

    return true;
  }

  /**
   * Extract API key from X-API-Key header (case-insensitive)
   */
  private extractApiKeyFromHeader(request: {
    headers: Record<string, string>;
  }): string | undefined {
    const headers = request.headers;

    // Check common variations (lowercase, uppercase, mixed case)
    const apiKey =
      headers["x-api-key"] ?? headers["X-API-Key"] ?? headers["X-Api-Key"] ?? headers["x-api-key"];

    // Return undefined if key is empty string
    if (typeof apiKey === "string" && apiKey.trim() === "") {
      return undefined;
    }

    return apiKey;
  }

  /**
   * Validate API key using constant-time comparison to prevent timing attacks
   */
  private isValidApiKey(providedKey: string, configuredKey: string): boolean {
    try {
      // Convert strings to buffers for constant-time comparison
      const providedBuffer = Buffer.from(providedKey, "utf8");
      const configuredBuffer = Buffer.from(configuredKey, "utf8");

      // Keys must be same length for timingSafeEqual
      if (providedBuffer.length !== configuredBuffer.length) {
        return false;
      }

      return timingSafeEqual(providedBuffer, configuredBuffer);
    } catch {
      // If comparison fails for any reason, reject
      return false;
    }
  }
}
