/**
 * Federation Controller
 *
 * API endpoints for instance identity and federation management.
 * Issue #272: Rate limiting applied to prevent DoS attacks
 */

import { Controller, Get, Post, UseGuards, Logger, Req, Body, Param, Query } from "@nestjs/common";
import { Throttle } from "@nestjs/throttler";
import { FederationService } from "./federation.service";
import { FederationAuditService } from "./audit.service";
import { ConnectionService } from "./connection.service";
import { getDefaultWorkspaceId } from "./federation.config";
import { AuthGuard } from "../auth/guards/auth.guard";
import { AdminGuard } from "../auth/guards/admin.guard";
import { WorkspaceGuard } from "../common/guards/workspace.guard";
import { SkipCsrf } from "../common/decorators/skip-csrf.decorator";
import type { PublicInstanceIdentity } from "./types/instance.types";
import type { ConnectionDetails } from "./types/connection.types";
import type { AuthenticatedRequest } from "../common/types/user.types";
import {
  InitiateConnectionDto,
  AcceptConnectionDto,
  RejectConnectionDto,
  DisconnectConnectionDto,
  IncomingConnectionRequestDto,
} from "./dto/connection.dto";
import { FederationConnectionStatus } from "@prisma/client";

@Controller("v1/federation")
export class FederationController {
  private readonly logger = new Logger(FederationController.name);

  constructor(
    private readonly federationService: FederationService,
    private readonly auditService: FederationAuditService,
    private readonly connectionService: ConnectionService
  ) {}

  /**
   * Get this instance's public identity
   * No authentication required - this is public information for federation
   * Rate limit: "long" tier (200 req/hour) - public endpoint
   * CSRF exempt: GET method (safe)
   */
  @Get("instance")
  @Throttle({ long: { limit: 200, ttl: 3600000 } })
  async getInstance(): Promise<PublicInstanceIdentity> {
    this.logger.debug("GET /api/v1/federation/instance");
    return this.federationService.getPublicIdentity();
  }

  /**
   * Regenerate instance keypair
   * Requires system administrator privileges
   * Returns public identity only (private key never exposed in API)
   * Rate limit: "medium" tier (20 req/min) - sensitive admin operation
   */
  @Post("instance/regenerate-keys")
  @UseGuards(AuthGuard, AdminGuard)
  @Throttle({ medium: { limit: 20, ttl: 60000 } })
  async regenerateKeys(@Req() req: AuthenticatedRequest): Promise<PublicInstanceIdentity> {
    if (!req.user) {
      throw new Error("User not authenticated");
    }

    this.logger.warn(`Admin user ${req.user.id} regenerating instance keypair`);

    const result = await this.federationService.regenerateKeypair();

    // Audit log for security compliance
    this.auditService.logKeypairRegeneration(req.user.id, result.instanceId);

    return result;
  }

  /**
   * Initiate a connection to a remote instance
   * Requires authentication and workspace access
   * Rate limit: "medium" tier (20 req/min) - authenticated endpoint
   */
  @Post("connections/initiate")
  @UseGuards(AuthGuard, WorkspaceGuard)
  @Throttle({ medium: { limit: 20, ttl: 60000 } })
  async initiateConnection(
    @Req() req: AuthenticatedRequest,
    @Body() dto: InitiateConnectionDto
  ): Promise<ConnectionDetails> {
    if (!req.user?.workspaceId) {
      throw new Error("Workspace ID not found in request");
    }

    this.logger.log(
      `User ${req.user.id} initiating connection to ${dto.remoteUrl} for workspace ${req.user.workspaceId}`
    );

    return this.connectionService.initiateConnection(req.user.workspaceId, dto.remoteUrl);
  }

  /**
   * Accept a pending connection
   * Requires authentication and workspace access
   * Rate limit: "medium" tier (20 req/min) - authenticated endpoint
   */
  @Post("connections/:id/accept")
  @UseGuards(AuthGuard, WorkspaceGuard)
  @Throttle({ medium: { limit: 20, ttl: 60000 } })
  async acceptConnection(
    @Req() req: AuthenticatedRequest,
    @Param("id") connectionId: string,
    @Body() dto: AcceptConnectionDto
  ): Promise<ConnectionDetails> {
    if (!req.user?.workspaceId) {
      throw new Error("Workspace ID not found in request");
    }

    this.logger.log(
      `User ${req.user.id} accepting connection ${connectionId} for workspace ${req.user.workspaceId}`
    );

    return this.connectionService.acceptConnection(
      req.user.workspaceId,
      connectionId,
      dto.metadata
    );
  }

  /**
   * Reject a pending connection
   * Requires authentication and workspace access
   * Rate limit: "medium" tier (20 req/min) - authenticated endpoint
   */
  @Post("connections/:id/reject")
  @UseGuards(AuthGuard, WorkspaceGuard)
  @Throttle({ medium: { limit: 20, ttl: 60000 } })
  async rejectConnection(
    @Req() req: AuthenticatedRequest,
    @Param("id") connectionId: string,
    @Body() dto: RejectConnectionDto
  ): Promise<ConnectionDetails> {
    if (!req.user?.workspaceId) {
      throw new Error("Workspace ID not found in request");
    }

    this.logger.log(`User ${req.user.id} rejecting connection ${connectionId}: ${dto.reason}`);

    return this.connectionService.rejectConnection(req.user.workspaceId, connectionId, dto.reason);
  }

  /**
   * Disconnect an active connection
   * Requires authentication and workspace access
   * Rate limit: "medium" tier (20 req/min) - authenticated endpoint
   */
  @Post("connections/:id/disconnect")
  @UseGuards(AuthGuard, WorkspaceGuard)
  @Throttle({ medium: { limit: 20, ttl: 60000 } })
  async disconnectConnection(
    @Req() req: AuthenticatedRequest,
    @Param("id") connectionId: string,
    @Body() dto: DisconnectConnectionDto
  ): Promise<ConnectionDetails> {
    if (!req.user?.workspaceId) {
      throw new Error("Workspace ID not found in request");
    }

    this.logger.log(`User ${req.user.id} disconnecting connection ${connectionId}`);

    return this.connectionService.disconnect(req.user.workspaceId, connectionId, dto.reason);
  }

  /**
   * Get all connections for the workspace
   * Requires authentication and workspace access
   * Rate limit: "long" tier (200 req/hour) - read-only endpoint
   */
  @Get("connections")
  @UseGuards(AuthGuard, WorkspaceGuard)
  @Throttle({ long: { limit: 200, ttl: 3600000 } })
  async getConnections(
    @Req() req: AuthenticatedRequest,
    @Query("status") status?: FederationConnectionStatus
  ): Promise<ConnectionDetails[]> {
    if (!req.user?.workspaceId) {
      throw new Error("Workspace ID not found in request");
    }

    return this.connectionService.getConnections(req.user.workspaceId, status);
  }

  /**
   * Get a single connection
   * Requires authentication and workspace access
   * Rate limit: "long" tier (200 req/hour) - read-only endpoint
   */
  @Get("connections/:id")
  @UseGuards(AuthGuard, WorkspaceGuard)
  @Throttle({ long: { limit: 200, ttl: 3600000 } })
  async getConnection(
    @Req() req: AuthenticatedRequest,
    @Param("id") connectionId: string
  ): Promise<ConnectionDetails> {
    if (!req.user?.workspaceId) {
      throw new Error("Workspace ID not found in request");
    }

    return this.connectionService.getConnection(req.user.workspaceId, connectionId);
  }

  /**
   * Handle incoming connection request from remote instance
   * Public endpoint - no authentication required (signature-based verification)
   * Rate limit: "short" tier (3 req/sec) - CRITICAL DoS protection (Issue #272)
   * CSRF exempt: Uses signature-based authentication instead
   */
  @Post("incoming/connect")
  @SkipCsrf()
  @Throttle({ short: { limit: 3, ttl: 1000 } })
  async handleIncomingConnection(
    @Body() dto: IncomingConnectionRequestDto
  ): Promise<{ status: string; connectionId?: string }> {
    this.logger.log(`Received connection request from ${dto.instanceId}`);

    // LIMITATION: Incoming connections are created in a default workspace
    // TODO: Future enhancement - Allow configuration of which workspace handles incoming connections
    // This could be based on routing rules, instance configuration, or a dedicated federation workspace
    // Issue #338: Validate DEFAULT_WORKSPACE_ID is a valid UUID (throws if invalid/missing)
    const workspaceId = getDefaultWorkspaceId();

    const connection = await this.connectionService.handleIncomingConnectionRequest(
      workspaceId,
      dto
    );

    return {
      status: "pending",
      connectionId: connection.id,
    };
  }
}
