import { Injectable, Logger } from '@nestjs/common';

export interface RoomParticipant {
  socketId: string;
  id: string;
  name: string;
  initials: string;
  role: 'auditor' | 'client' | 'observer';
  joinedAt: number;
  leftAt?: number;
}

export interface RoomState {
  roomId: string;
  auditId: string;
  createdAt: number;
  participants: Map<string, RoomParticipant>;
  /** everyone who ever joined, for the attendance record */
  attendance: RoomParticipant[];
}

/**
 * In-memory room registry.
 *
 * IMPORTANT: this only works with a single server instance. If you ever run
 * more than one NestJS process behind a load balancer, two people in the same
 * audit could land on different instances and never see each other.
 * The fix is the socket.io Redis adapter — swap this class for Redis-backed
 * storage at that point. Fine as-is for now.
 */
@Injectable()
export class RoomRegistry {
  private readonly log = new Logger(RoomRegistry.name);
  private rooms = new Map<string, RoomState>();

  getOrCreate(roomId: string, auditId: string): RoomState {
    let room = this.rooms.get(roomId);
    if (!room) {
      room = {
        roomId,
        auditId,
        createdAt: Date.now(),
        participants: new Map(),
        attendance: [],
      };
      this.rooms.set(roomId, room);
      this.log.log(`room created: ${roomId}`);
    }
    return room;
  }

  get(roomId: string): RoomState | undefined {
    return this.rooms.get(roomId);
  }

  addParticipant(roomId: string, p: RoomParticipant): RoomState | undefined {
    const room = this.rooms.get(roomId);
    if (!room) return undefined;
    room.participants.set(p.socketId, p);
    room.attendance.push(p);
    return room;
  }

  removeParticipant(roomId: string, socketId: string): RoomParticipant | undefined {
    const room = this.rooms.get(roomId);
    if (!room) return undefined;

    const p = room.participants.get(socketId);
    if (!p) return undefined;

    room.participants.delete(socketId);

    // stamp the leave time on the attendance record
    const record = room.attendance.find(
      (a) => a.socketId === socketId && !a.leftAt,
    );
    if (record) record.leftAt = Date.now();

    // keep the room for a while after the last person leaves, so a
    // reconnect within a few minutes rejoins the same room
    if (room.participants.size === 0) {
      setTimeout(() => {
        const still = this.rooms.get(roomId);
        if (still && still.participants.size === 0) {
          this.rooms.delete(roomId);
          this.log.log(`room disposed: ${roomId}`);
        }
      }, 5 * 60 * 1000);
    }

    return p;
  }

  /** Find which room a socket belongs to, for disconnect handling */
  findRoomBySocket(socketId: string): RoomState | undefined {
    for (const room of this.rooms.values()) {
      if (room.participants.has(socketId)) return room;
    }
    return undefined;
  }

  listParticipants(roomId: string): Omit<RoomParticipant, 'socketId'>[] {
    const room = this.rooms.get(roomId);
    if (!room) return [];
    return [...room.participants.values()].map(({ socketId, ...rest }) => rest);
  }

  /** Attendance summary for the evidence pack */
  attendanceReport(roomId: string) {
    const room = this.rooms.get(roomId);
    if (!room) return null;
    const now = Date.now();
    return {
      roomId: room.roomId,
      auditId: room.auditId,
      startedAt: room.createdAt,
      endedAt: now,
      durationMs: now - room.createdAt,
      participants: room.attendance.map((a) => ({
        id: a.id,
        name: a.name,
        role: a.role,
        joinedAt: a.joinedAt,
        leftAt: a.leftAt ?? now,
        durationMs: (a.leftAt ?? now) - a.joinedAt,
      })),
    };
  }
}
