import {
  WebSocketGateway as WSGateway,
  WebSocketServer,
  OnGatewayConnection,
  OnGatewayDisconnect,
} from "@nestjs/websockets";
import { Logger } from "@nestjs/common";
import { Server, Socket } from "socket.io";

interface AuthenticatedSocket extends Socket {
  data: {
    userId?: string;
    workspaceId?: string;
  };
}

interface Task {
  id: string;
  workspaceId: string;
  [key: string]: unknown;
}

interface Event {
  id: string;
  workspaceId: string;
  [key: string]: unknown;
}

interface Project {
  id: string;
  workspaceId: string;
  [key: string]: unknown;
}

/**
 * @description WebSocket Gateway for real-time updates. Handles workspace-scoped rooms for broadcasting events.
 */
@WSGateway({
  cors: {
    origin: process.env.WEB_URL ?? "http://localhost:3000",
    credentials: true,
  },
})
export class WebSocketGateway implements OnGatewayConnection, OnGatewayDisconnect {
  @WebSocketServer()
  server!: Server;

  private readonly logger = new Logger(WebSocketGateway.name);

  /**
   * @description Handle client connection by authenticating and joining the workspace-specific room.
   * @param client - The authenticated socket client containing userId and workspaceId in data.
   * @returns Promise that resolves when the client is joined to the workspace room or disconnected.
   */
  async handleConnection(client: Socket): Promise<void> {
    const authenticatedClient = client as AuthenticatedSocket;
    const { userId, workspaceId } = authenticatedClient.data;

    if (!userId || !workspaceId) {
      this.logger.warn(`Client ${authenticatedClient.id} connected without authentication`);
      authenticatedClient.disconnect();
      return;
    }

    const room = this.getWorkspaceRoom(workspaceId);
    await authenticatedClient.join(room);

    this.logger.log(`Client ${authenticatedClient.id} joined room ${room}`);
  }

  /**
   * @description Handle client disconnect by leaving the workspace room.
   * @param client - The socket client containing workspaceId in data.
   * @returns void
   */
  handleDisconnect(client: Socket): void {
    const authenticatedClient = client as AuthenticatedSocket;
    const { workspaceId } = authenticatedClient.data;

    if (workspaceId) {
      const room = this.getWorkspaceRoom(workspaceId);
      void authenticatedClient.leave(room);
      this.logger.log(`Client ${authenticatedClient.id} left room ${room}`);
    }
  }

  /**
   * @description Emit task:created event to all clients in the workspace room.
   * @param workspaceId - The workspace identifier for the room to broadcast to.
   * @param task - The task object that was created.
   * @returns void
   */
  emitTaskCreated(workspaceId: string, task: Task): void {
    const room = this.getWorkspaceRoom(workspaceId);
    this.server.to(room).emit("task:created", task);
    this.logger.debug(`Emitted task:created to ${room}`);
  }

  /**
   * @description Emit task:updated event to all clients in the workspace room.
   * @param workspaceId - The workspace identifier for the room to broadcast to.
   * @param task - The task object that was updated.
   * @returns void
   */
  emitTaskUpdated(workspaceId: string, task: Task): void {
    const room = this.getWorkspaceRoom(workspaceId);
    this.server.to(room).emit("task:updated", task);
    this.logger.debug(`Emitted task:updated to ${room}`);
  }

  /**
   * @description Emit task:deleted event to all clients in the workspace room.
   * @param workspaceId - The workspace identifier for the room to broadcast to.
   * @param taskId - The ID of the task that was deleted.
   * @returns void
   */
  emitTaskDeleted(workspaceId: string, taskId: string): void {
    const room = this.getWorkspaceRoom(workspaceId);
    this.server.to(room).emit("task:deleted", { id: taskId });
    this.logger.debug(`Emitted task:deleted to ${room}`);
  }

  /**
   * @description Emit event:created event to all clients in the workspace room.
   * @param workspaceId - The workspace identifier for the room to broadcast to.
   * @param event - The event object that was created.
   * @returns void
   */
  emitEventCreated(workspaceId: string, event: Event): void {
    const room = this.getWorkspaceRoom(workspaceId);
    this.server.to(room).emit("event:created", event);
    this.logger.debug(`Emitted event:created to ${room}`);
  }

  /**
   * @description Emit event:updated event to all clients in the workspace room.
   * @param workspaceId - The workspace identifier for the room to broadcast to.
   * @param event - The event object that was updated.
   * @returns void
   */
  emitEventUpdated(workspaceId: string, event: Event): void {
    const room = this.getWorkspaceRoom(workspaceId);
    this.server.to(room).emit("event:updated", event);
    this.logger.debug(`Emitted event:updated to ${room}`);
  }

  /**
   * @description Emit event:deleted event to all clients in the workspace room.
   * @param workspaceId - The workspace identifier for the room to broadcast to.
   * @param eventId - The ID of the event that was deleted.
   * @returns void
   */
  emitEventDeleted(workspaceId: string, eventId: string): void {
    const room = this.getWorkspaceRoom(workspaceId);
    this.server.to(room).emit("event:deleted", { id: eventId });
    this.logger.debug(`Emitted event:deleted to ${room}`);
  }

  /**
   * @description Emit project:created event to all clients in the workspace room.
   * @param workspaceId - The workspace identifier for the room to broadcast to.
   * @param project - The project object that was created.
   * @returns void
   */
  emitProjectCreated(workspaceId: string, project: Project): void {
    const room = this.getWorkspaceRoom(workspaceId);
    this.server.to(room).emit("project:created", project);
    this.logger.debug(`Emitted project:created to ${room}`);
  }

  /**
   * @description Emit project:updated event to all clients in the workspace room.
   * @param workspaceId - The workspace identifier for the room to broadcast to.
   * @param project - The project object that was updated.
   * @returns void
   */
  emitProjectUpdated(workspaceId: string, project: Project): void {
    const room = this.getWorkspaceRoom(workspaceId);
    this.server.to(room).emit("project:updated", project);
    this.logger.debug(`Emitted project:updated to ${room}`);
  }

  /**
   * @description Emit project:deleted event to all clients in the workspace room.
   * @param workspaceId - The workspace identifier for the room to broadcast to.
   * @param projectId - The ID of the project that was deleted.
   * @returns void
   */
  emitProjectDeleted(workspaceId: string, projectId: string): void {
    const room = this.getWorkspaceRoom(workspaceId);
    this.server.to(room).emit("project:deleted", { id: projectId });
    this.logger.debug(`Emitted project:deleted to ${room}`);
  }

  /**
   * Emit cron:executed event when a scheduled command fires
   */
  emitCronExecuted(
    workspaceId: string,
    data: { scheduleId: string; command: string; executedAt: Date }
  ): void {
    const room = this.getWorkspaceRoom(workspaceId);
    this.server.to(room).emit("cron:executed", data);
    this.logger.debug(`Emitted cron:executed to ${room}`);
  }

  /**
   * Get workspace room name
   */
  private getWorkspaceRoom(workspaceId: string): string {
    return `workspace:${workspaceId}`;
  }
}
