import {
  SUPPORT_CHAT_WS_EVENTS,
  SUPPORT_CHAT_WS_NAMESPACE,
  SupportConversationDto,
  SupportMessageDto,
} from "@edr/types";
import { Logger } from "@nestjs/common";
import {
  OnGatewayConnection,
  WebSocketGateway,
  WebSocketServer,
} from "@nestjs/websockets";
import { Server, Socket } from "socket.io";

import { BackofficeService } from "../backoffice/backoffice.service";
import { ExternalProfileRepository } from "../companies/external-profile.repository";
import { WsAuthService } from "../notification-inbox/ws-auth.service";

/**
 * Server → client push for support chat. Clients only *listen* (no
 * `@SubscribeMessage`); the handshake is authenticated in `handleConnection`
 * (reusing the notification module's {@link WsAuthService}). Each socket joins a
 * room based on its side:
 *  - backoffice staff → the shared `backoffice` room (see every conversation).
 *  - portal users     → their `company:<companyId>` room (their tickets only).
 *
 * A message is emitted to *both* the company room and the backoffice room so the
 * customer thread, the sender's echo, and every other agent's inbox update live.
 */
@WebSocketGateway({
  namespace: SUPPORT_CHAT_WS_NAMESPACE,
  cors: { origin: true, credentials: true },
})
export class SupportChatGateway implements OnGatewayConnection {
  private readonly logger = new Logger(SupportChatGateway.name);

  private static readonly BACKOFFICE_ROOM = "backoffice";

  @WebSocketServer()
  private readonly server!: Server;

  constructor(
    private readonly wsAuth: WsAuthService,
    private readonly backoffice: BackofficeService,
    private readonly externalProfiles: ExternalProfileRepository,
  ) {}

  async handleConnection(socket: Socket): Promise<void> {
    const userId = await this.wsAuth.resolveUserId(this.extractToken(socket));
    if (!userId) {
      this.logger.debug(`Rejected support-chat handshake ${socket.id}`);
      socket.disconnect(true);
      return;
    }
    socket.data.userId = userId;

    try {
      const staffIds = await this.backoffice.getAllCurrentEmployeeUserIds();
      if (staffIds.includes(userId)) {
        await socket.join(SupportChatGateway.BACKOFFICE_ROOM);
        socket.data.side = "AGENT";
        return;
      }
    } catch (err) {
      this.logger.warn(`Staff lookup failed: ${(err as Error).message}`);
    }

    const profile = await this.externalProfiles.findByUserId(userId);
    if (profile?.companyId) {
      await socket.join(this.companyRoom(profile.companyId));
      socket.data.side = "CUSTOMER";
      socket.data.companyId = profile.companyId;
    }
  }

  /** Push a new message + updated conversation to the company and backoffice rooms. */
  emitMessage(
    companyId: string,
    conversation: SupportConversationDto,
    message: SupportMessageDto,
  ): void {
    const payload = { conversation, message };
    for (const room of this.targetRooms(companyId)) {
      const to = this.server.to(room);
      to.emit(SUPPORT_CHAT_WS_EVENTS.MESSAGE_NEW, payload);
      to.emit(SUPPORT_CHAT_WS_EVENTS.CONVERSATION_UPDATED, conversation);
    }
  }

  /** Push a conversation metadata change (e.g. status) to both rooms. */
  emitConversationUpdated(
    companyId: string,
    conversation: SupportConversationDto,
  ): void {
    for (const room of this.targetRooms(companyId)) {
      this.server
        .to(room)
        .emit(SUPPORT_CHAT_WS_EVENTS.CONVERSATION_UPDATED, conversation);
    }
  }

  private targetRooms(companyId: string): string[] {
    return [this.companyRoom(companyId), SupportChatGateway.BACKOFFICE_ROOM];
  }

  private companyRoom(companyId: string): string {
    return `company:${companyId}`;
  }

  private extractToken(socket: Socket): string | undefined {
    const authToken = socket.handshake.auth?.token as string | undefined;
    if (authToken) return authToken;

    const queryToken = socket.handshake.query?.token;
    if (typeof queryToken === "string") return queryToken;

    const header = socket.handshake.headers?.authorization;
    if (header?.startsWith("Bearer ")) return header.slice(7);

    return undefined;
  }
}
