/**
 * Capability Guard
 *
 * Enforces capability-based authorization for federation operations.
 * Fail-closed security model: denies access unless capability is explicitly granted.
 */

import {
  Injectable,
  CanActivate,
  ExecutionContext,
  UnauthorizedException,
  ForbiddenException,
} from "@nestjs/common";
import { Reflector } from "@nestjs/core";
import { FederationConnectionStatus } from "@prisma/client";
import type { Request } from "express";
import type { FederationCapabilities } from "../types/instance.types";
import { ConnectionService } from "../connection.service";
import { FederationAuditService } from "../audit.service";

/**
 * Metadata key for required capability
 */
const REQUIRED_CAPABILITY_KEY = "federation:requiredCapability";

/**
 * Guard that enforces capability-based authorization
 *
 * Security properties:
 * - Fail-closed: Denies access if capability is undefined or not explicitly true
 * - Connection validation: Verifies connection exists and is CONNECTED
 * - Audit logging: Logs all capability denials for security monitoring
 */
@Injectable()
export class CapabilityGuard implements CanActivate {
  constructor(
    private readonly reflector: Reflector,
    private readonly connectionService: ConnectionService,
    private readonly auditService: FederationAuditService
  ) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    // Check if capability requirement is set on endpoint
    const requiredCapability = this.reflector.get<keyof FederationCapabilities>(
      REQUIRED_CAPABILITY_KEY,
      context.getHandler()
    );

    // If no capability required, allow access
    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
    if (!requiredCapability) {
      return true;
    }

    const request = context.switchToHttp().getRequest<Request>();

    // Extract connection ID from request
    const connectionId = this.extractConnectionId(request);
    if (!connectionId) {
      throw new UnauthorizedException("Federation connection not identified");
    }

    // Load connection
    const connection = await this.connectionService.getConnectionById(connectionId);
    if (!connection) {
      throw new UnauthorizedException("Invalid federation connection");
    }

    // Verify connection is active
    if (connection.status !== FederationConnectionStatus.ACTIVE) {
      throw new ForbiddenException("Connection is not active");
    }

    // Check if capability is granted (fail-closed: must be explicitly true)
    const capabilities = connection.remoteCapabilities as unknown as FederationCapabilities;
    const hasCapability = capabilities[requiredCapability] === true;

    if (!hasCapability) {
      // Log capability denial for security monitoring
      this.auditService.logCapabilityDenied(
        connection.remoteInstanceId,
        requiredCapability,
        request.url
      );

      throw new ForbiddenException(`Operation requires capability: ${requiredCapability}`);
    }

    return true;
  }

  /**
   * Extract connection ID from request
   * Checks body, headers, and params in order
   */
  private extractConnectionId(request: Request): string | undefined {
    // Check body first (most common for POST/PATCH)
    const body = request.body as { connectionId?: string } | undefined;
    if (body?.connectionId) {
      return body.connectionId;
    }

    // Check headers (for federated requests)
    const headerConnectionId = request.headers["x-federation-connection-id"];
    if (headerConnectionId) {
      return Array.isArray(headerConnectionId) ? headerConnectionId[0] : headerConnectionId;
    }

    // Check route params (for GET/DELETE operations)
    const params = request.params as { connectionId?: string } | undefined;
    if (params?.connectionId) {
      return params.connectionId;
    }

    return undefined;
  }
}

/**
 * Decorator to mark endpoints that require specific federation capabilities
 *
 * @example
 * ```typescript
 * @Post("execute-command")
 * @RequireCapability("supportsCommand")
 * async executeCommand(@Body() dto: ExecuteCommandDto) {
 *   // Only reachable if remote instance has supportsCommand = true
 * }
 * ```
 */
export const RequireCapability = (capability: keyof FederationCapabilities) =>
  Reflector.createDecorator<keyof FederationCapabilities>({
    key: REQUIRED_CAPABILITY_KEY,
    transform: () => capability,
  });
