import { Logger } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import {
    ConnectedSocket,
    MessageBody,
    OnGatewayConnection,
    OnGatewayDisconnect,
    OnGatewayInit,
    SubscribeMessage,
    WebSocketGateway,
    WebSocketServer,
    WsException,
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';

interface AuthenticatedSocket extends Socket {
  data: {
    userId: string;
    empresaId: string;
    username: string;
    isSupervisor: boolean;
  };
}

@WebSocketGateway({
  cors: { origin: '*' },
  namespace: '/pos',
  transports: ['websocket', 'polling'],
})
export class PosGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {
  @WebSocketServer()
  server: Server;

  private readonly logger = new Logger(PosGateway.name);
  // empresa_id -> Set<socket_id> de supervisores conectados
  private supervisorRooms = new Map<string, Set<string>>();
  // empresa_id -> Set<socket_id> de cajeros conectados
  private cajeroRooms = new Map<string, Set<string>>();

  constructor(
    private readonly jwtService: JwtService,
  ) {}

  afterInit() {
    this.logger.log('POS WebSocket Gateway inicializado', 'PosGateway');
  }

  async handleConnection(client: AuthenticatedSocket) {
    try {
      const token = client.handshake.auth?.token || client.handshake.headers?.authorization?.replace('Bearer ', '');

      if (!token) {
        throw new WsException('Token requerido');
      }

      const payload = await this.jwtService.verifyAsync(token, {
        secret: process.env.JWT_SECRET,
      });

      client.data = {
        userId: payload.sub,
        empresaId: payload.empresa_id,
        username: payload.username,
        isSupervisor: payload.is_superadmin || false,
      };

      // Unirse a la sala de la empresa
      const empresaRoom = `empresa:${client.data.empresaId}`;
      client.join(empresaRoom);

      this.logger.log(
        `WS conectado: ${client.data.username} (${client.id}) → empresa ${client.data.empresaId}`,
        'PosGateway',
      );
    } catch (error) {
      this.logger.warn(`WS conexión rechazada: ${error.message}`, 'PosGateway');
      client.emit('error', { message: 'Autenticación fallida' });
      client.disconnect();
    }
  }

  handleDisconnect(client: AuthenticatedSocket) {
    if (!client.data?.empresaId) return;

    // Limpiar de salas de supervisor
    const supSet = this.supervisorRooms.get(client.data.empresaId);
    if (supSet) {
      supSet.delete(client.id);
      if (supSet.size === 0) this.supervisorRooms.delete(client.data.empresaId);
    }

    // Limpiar de salas de cajero
    const cajSet = this.cajeroRooms.get(client.data.empresaId);
    if (cajSet) {
      cajSet.delete(client.id);
      if (cajSet.size === 0) this.cajeroRooms.delete(client.data.empresaId);
    }

    this.logger.log(`WS desconectado: ${client.data.username} (${client.id})`, 'PosGateway');
  }

  // ==================== REGISTRO DE ROLES ====================

  @SubscribeMessage('registrar:supervisor')
  handleRegistrarSupervisor(@ConnectedSocket() client: AuthenticatedSocket) {
    const empresaId = client.data.empresaId;
    if (!this.supervisorRooms.has(empresaId)) {
      this.supervisorRooms.set(empresaId, new Set());
    }
    this.supervisorRooms.get(empresaId).add(client.id);
    client.join(`supervisores:${empresaId}`);

    this.logger.log(`Supervisor registrado: ${client.data.username} → empresa ${empresaId}`, 'PosGateway');
    return { event: 'registrado', data: { rol: 'supervisor' } };
  }

  @SubscribeMessage('registrar:cajero')
  handleRegistrarCajero(
    @ConnectedSocket() client: AuthenticatedSocket,
    @MessageBody() data: { caja_id: string; sesion_id?: string },
  ) {
    const empresaId = client.data.empresaId;
    if (!this.cajeroRooms.has(empresaId)) {
      this.cajeroRooms.set(empresaId, new Set());
    }
    this.cajeroRooms.get(empresaId).add(client.id);
    client.join(`cajeros:${empresaId}`);

    if (data?.caja_id) {
      client.join(`caja:${data.caja_id}`);
    }
    if (data?.sesion_id) {
      client.join(`sesion:${data.sesion_id}`);
    }

    this.logger.log(`Cajero registrado: ${client.data.username} → caja ${data?.caja_id}`, 'PosGateway');
    return { event: 'registrado', data: { rol: 'cajero' } };
  }

  // ==================== MÉTODOS PÚBLICOS (llamados desde servicios) ====================

  /**
   * Notifica a supervisores de una empresa que hay una solicitud de autorización pendiente
   */
  notificarSolicitudAutorizacion(
    empresaId: string,
    autorizacion: {
      id: string;
      tipo: string;
      solicitante: { id: string; username: string; nombres?: string };
      motivo?: string;
      sesion_caja_id?: string;
      contexto?: any;
      expires_at: Date;
    },
  ) {
    this.server.to(`supervisores:${empresaId}`).emit('autorizacion:solicitud', autorizacion);

    this.logger.log(
      `Notificación enviada a supervisores: autorización ${autorizacion.id} (${autorizacion.tipo})`,
      'PosGateway',
    );
  }

  /**
   * Notifica al cajero que su solicitud fue aprobada
   */
  notificarAutorizacionAprobada(
    empresaId: string,
    data: {
      autorizacion_id: string;
      tipo: string;
      token?: string;
      supervisor: { id: string; username: string; nombres?: string };
      sesion_caja_id?: string;
    },
  ) {
    this.server.to(`empresa:${empresaId}`).emit('autorizacion:aprobada', data);

    // Si hay sesión, notificar directamente a la caja
    if (data.sesion_caja_id) {
      this.server.to(`sesion:${data.sesion_caja_id}`).emit('autorizacion:aprobada', data);
    }
  }

  /**
   * Notifica al cajero que su solicitud fue rechazada
   */
  notificarAutorizacionRechazada(
    empresaId: string,
    data: {
      autorizacion_id: string;
      tipo: string;
      supervisor: { id: string; username: string; nombres?: string };
      motivo_rechazo?: string;
      sesion_caja_id?: string;
    },
  ) {
    this.server.to(`empresa:${empresaId}`).emit('autorizacion:rechazada', data);

    if (data.sesion_caja_id) {
      this.server.to(`sesion:${data.sesion_caja_id}`).emit('autorizacion:rechazada', data);
    }
  }

  /**
   * Notifica cambios de estado en la sesión de caja (apertura, cierre, bloqueo)
   */
  notificarCambioSesion(
    empresaId: string,
    data: {
      sesion_id: string;
      caja_id: string;
      evento: 'abierta' | 'cerrada' | 'bloqueada' | 'desbloqueada';
      usuario: { id: string; username: string };
    },
  ) {
    this.server.to(`empresa:${empresaId}`).emit('sesion:cambio', data);
  }

  /**
   * Retorna estadísticas de conexiones activas de una empresa
   */
  getConexionesActivas(empresaId: string) {
    return {
      supervisores: this.supervisorRooms.get(empresaId)?.size ?? 0,
      cajeros: this.cajeroRooms.get(empresaId)?.size ?? 0,
    };
  }
}
