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

@WebSocketGateway({
  cors: {
    origin: '*', // Configure this properly for production
  },
})
export class FamiliesGateway
  implements OnGatewayConnection, OnGatewayDisconnect
{
  @WebSocketServer()
  server: Server;

  private logger = new Logger('FamiliesGateway');
  private connectedClients = new Map<
    string,
    { socket: Socket; userId: string; familyId: string; username: string }
  >();
  // Track online users per family
  private familyPresence = new Map<string, Set<string>>(); // familyId -> Set of userIds

  constructor(
    private jwtService: JwtService,
    private familiesService: FamiliesService,
  ) {}

  async handleConnection(client: Socket) {
    try {
      // Extract token from handshake
      const token =
        client.handshake.auth?.token ||
        client.handshake.headers?.authorization?.split(' ')[1];

      if (!token) {
        this.logger.warn(
          `Client ${client.id} attempted connection without token`,
        );
        client.disconnect();
        return;
      }

      // Verify JWT token
      const payload = await this.jwtService.verifyAsync(token);
      const userId = payload.userId;
      const username = payload.name || payload.email || 'Unknown';

      this.logger.log(`Client connected: ${client.id}, User: ${userId}`);

      // Store client connection
      this.connectedClients.set(client.id, {
        socket: client,
        userId,
        familyId: null, // Will be set when user joins a family room
        username,
      });

      // Emit connection success
      client.emit('connected', { message: 'Connected successfully' });
    } catch (error) {
      this.logger.error(
        `Connection failed for client ${client.id}:`,
        error.message,
      );
      client.emit('error', { message: 'Authentication failed' });
      client.disconnect();
    }
  }

  handleDisconnect(client: Socket) {
    const clientData = this.connectedClients.get(client.id);
    if (clientData) {
      this.logger.log(
        `Client disconnected: ${client.id}, User: ${clientData.userId}`,
      );

      // Leave family room if connected
      if (clientData.familyId) {
        this.removeUserFromPresence(clientData.familyId, clientData.userId);
        client.leave(`family:${clientData.familyId}`);
        // Notify others in the family
        this.broadcastPresence(clientData.familyId);
      }

      this.connectedClients.delete(client.id);
    }
  }

  @SubscribeMessage('joinFamily')
  async handleJoinFamily(
    @MessageBody() data: { familyId: string },
    @ConnectedSocket() client: Socket,
  ) {
    const clientData = this.connectedClients.get(client.id);

    if (!clientData) {
      client.emit('error', { message: 'Client not authenticated' });
      return;
    }

    try {
      // Verify user is a member of this family
      await this.familiesService.getFamily(clientData.userId, data.familyId);

      // Leave previous family room if any
      if (clientData.familyId) {
        this.removeUserFromPresence(clientData.familyId, clientData.userId);
        client.leave(`family:${clientData.familyId}`);
        this.broadcastPresence(clientData.familyId);
      }

      // Join new family room
      client.join(`family:${data.familyId}`);
      clientData.familyId = data.familyId;

      // Add user to presence tracking
      this.addUserToPresence(data.familyId, clientData.userId);

      this.logger.log(
        `User ${clientData.userId} joined family room: ${data.familyId}`,
      );

      // Send current presence to the joining client
      const onlineUsers = this.getOnlineUsers(data.familyId);
      client.emit('familyJoined', {
        familyId: data.familyId,
        message: 'Successfully joined family updates',
        onlineUsers,
      });

      // Broadcast presence update to all family members
      this.broadcastPresence(data.familyId);
    } catch (error) {
      this.logger.error(`Failed to join family: ${error.message}`);
      client.emit('error', { message: 'Failed to join family room' });
    }
  }

  @SubscribeMessage('leaveFamily')
  async handleLeaveFamily(@ConnectedSocket() client: Socket) {
    const clientData = this.connectedClients.get(client.id);

    if (!clientData || !clientData.familyId) {
      return;
    }

    client.leave(`family:${clientData.familyId}`);
    this.logger.log(
      `User ${clientData.userId} left family room: ${clientData.familyId}`,
    );

    clientData.familyId = null;
    client.emit('familyLeft', { message: 'Left family updates' });
  }

  // Broadcast methods to be called from services

  notifyFamilyActivityCreated(familyId: string, activity: any) {
    this.server.to(`family:${familyId}`).emit('activityCreated', activity);
    this.logger.log(
      `Activity created notification sent to family: ${familyId}`,
    );
  }

  notifyFamilyActivityUpdated(familyId: string, activity: any) {
    this.server.to(`family:${familyId}`).emit('activityUpdated', activity);
    this.logger.log(
      `Activity updated notification sent to family: ${familyId}`,
    );
  }

  notifyFamilyActivityDeleted(familyId: string, activityId: string) {
    this.server
      .to(`family:${familyId}`)
      .emit('activityDeleted', { activityId });
    this.logger.log(
      `Activity deleted notification sent to family: ${familyId}`,
    );
  }

  notifyFamilyMemberAdded(familyId: string, member: any) {
    this.server.to(`family:${familyId}`).emit('memberAdded', member);
    this.logger.log(`Member added notification sent to family: ${familyId}`);
  }

  notifyFamilyMemberUpdated(familyId: string, member: any) {
    this.server.to(`family:${familyId}`).emit('memberUpdated', member);
    this.logger.log(`Member updated notification sent to family: ${familyId}`);
  }

  notifyFamilyMemberRemoved(familyId: string, userId: string) {
    this.server.to(`family:${familyId}`).emit('memberRemoved', { userId });
    this.logger.log(`Member removed notification sent to family: ${familyId}`);
  }

  notifyFamilyChildAdded(familyId: string, child: any) {
    this.server.to(`family:${familyId}`).emit('childAdded', child);
    this.logger.log(`Child added notification sent to family: ${familyId}`);
  }

  notifyFamilyChildUpdated(familyId: string, child: any) {
    this.server.to(`family:${familyId}`).emit('childUpdated', child);
    this.logger.log(`Child updated notification sent to family: ${familyId}`);
  }

  notifyFamilyChildDeleted(familyId: string, childId: string) {
    this.server.to(`family:${familyId}`).emit('childDeleted', { childId });
    this.logger.log(`Child deleted notification sent to family: ${familyId}`);
  }

  // Presence management methods

  private addUserToPresence(familyId: string, userId: string) {
    if (!this.familyPresence.has(familyId)) {
      this.familyPresence.set(familyId, new Set());
    }
    this.familyPresence.get(familyId).add(userId);
  }

  private removeUserFromPresence(familyId: string, userId: string) {
    const presence = this.familyPresence.get(familyId);
    if (presence) {
      presence.delete(userId);
      if (presence.size === 0) {
        this.familyPresence.delete(familyId);
      }
    }
  }

  private getOnlineUsers(familyId: string): string[] {
    const presence = this.familyPresence.get(familyId);
    return presence ? Array.from(presence) : [];
  }

  private broadcastPresence(familyId: string) {
    const onlineUsers = this.getOnlineUsers(familyId);
    this.server.to(`family:${familyId}`).emit('presenceUpdate', {
      onlineUsers,
      count: onlineUsers.length,
    });
    this.logger.log(
      `Presence update sent to family ${familyId}: ${onlineUsers.length} online`,
    );
  }

  // Public method to get presence for a family (can be called from services)
  getPresenceForFamily(familyId: string): {
    onlineUsers: string[];
    count: number;
  } {
    const onlineUsers = this.getOnlineUsers(familyId);
    return {
      onlineUsers,
      count: onlineUsers.length,
    };
  }
}
