import { Server as SocketIOServer } from 'socket.io';

import { Server as HTTPServer } from 'http';

import { socketAuthentication } from '../middleware/socketMiddleware';
 
import db from '../models';

const NotificationHistoryModel = db.NotificationHistory;

const TrackexNotificationModel = db.TrackexNotification;

export class WebSocketService {

    private io: SocketIOServer;

    private static instance: WebSocketService;

    private userSockets: Map<number, string> = new Map(); // userId -> socketId

    private constructor(server: HTTPServer) {

        this.io = new SocketIOServer(server, {

            cors: {

                origin: ['https://app.trackex.co', 'http://localhost:3000', 'https://api.trackex.co'],

                methods: ["GET", "POST"],

            },

            path: `/${process.env.SECRET_PATH}`,

            transports: ['websocket', 'polling'],

            allowEIO3: true

        });
 
        this.io.use(socketAuthentication);

        this.io.on('connection', (socket) => {

            console.log('Client connected:', socket.id);

            // Handle user authentication

            socket.on('authenticate', (userId: number) => {

                if (!userId) {

                    console.error('Received empty userId in authenticate event');

                    return;

                }

                // Store the mapping

                this.userSockets.set(userId, socket.id);

                console.log(`User ${userId} authenticated with socket ${socket.id}`);

                console.log('Current user-socket mappings:', [...this.userSockets.entries()]);

                // Send initial notifications to the user

                this.updateUserNotifications(userId).catch(err => {

                    console.error(`Failed to send initial notifications to user ${userId}:`, err);

                });

            });
 
            socket.on('pingTest', (data) => {

                console.log(`Received pingTest from socket ${socket.id}:`, data);

                // Send a response back to client

                socket.emit('pingTest', { message: 'Hello from server!', received: data });

            });

            socket.on('disconnect', () => {

                console.log('Client disconnected:', socket.id);

                // Remove user from userSockets map

                for (const [userId, socketId] of this.userSockets.entries()) {

                    if (socketId === socket.id) {

                        console.log(`Removing user ${userId} with socket ${socketId} from map`);

                        this.userSockets.delete(userId);

                        break;

                    }

                }

            });

        });

    }

    public static initialize(server: HTTPServer): WebSocketService {

        if (!WebSocketService.instance) {

            WebSocketService.instance = new WebSocketService(server);

        }

        return WebSocketService.instance;

    }

    public static getInstance(): WebSocketService {

        if (!WebSocketService.instance) {

            throw new Error('WebSocketService not initialized');

        }

        return WebSocketService.instance;

    }

    public sendNotification(userId: number, notification: any) {

        if (!userId) {

            console.error('Invalid userId in sendNotification:', userId);

            return;

        }

        const socketId = this.userSockets.get(userId);

        console.log(`Attempting to send notification to user ${userId}`);

        console.log("User's socketId:", socketId);

        console.log("All user-socket mappings:", [...this.userSockets.entries()]);

        if (socketId) {

            console.log(`Emitting notification to user ${userId} on socket ${socketId}`);

            this.io.to(socketId).emit('notification', notification);

            // Also update the user's notification panel

            this.updateUserNotifications(userId).catch(err => {

                console.error(`Failed to update notifications for user ${userId}:`, err);

            });

        } else {

            console.error(`No active socket found for user ${userId}`);

        }

    }

    public broadcastNotification(notification: any) {

        console.log('Broadcasting notification to all connected clients');

        this.io.emit('notification', notification);

    }
 
    public async sendToUser(userId: number, event: string, data: any): Promise<void> {

        if (!userId) {

            console.error('Invalid userId in sendToUser:', userId);

            return;

        }

        const socketId = this.userSockets.get(userId);

        if (socketId) {

            console.log(`Emitting to user ${userId} on socket ${socketId} with event '${event}'`);

            this.io.to(socketId).emit(event, data);

        } else {

            console.error(`No active socket found for user ${userId}`);

        }

    }
 
    // Update notifications in the UI

    public async updateUserNotifications(userId: number): Promise<void> {

        if (!userId) {

            console.error('Invalid userId in updateUserNotifications:', userId);

            return;

        }

        try {

            const notifications = await NotificationHistoryModel.findAll({

                where: {

                    target_to: userId,

                },

                include: [{

                    model: TrackexNotificationModel,

                    as: 'notification'

                }],

                order: [

                    ['is_seen', 'ASC'],

                    ['createdAt', 'DESC']

                ],

                limit: 5

            });

            const unreadCount = await NotificationHistoryModel.count({

                where: {

                    target_to: userId,

                    is_seen: false

                }

            });
 
            console.log(`Emitting notifications:update to userId ${userId} with ${notifications.length} notifications and ${unreadCount} unread`);

            await this.sendToUser(userId, 'notifications:update', {

                data: notifications,

                unreadCount

            });

        } catch (error) {

            console.error('Error updating user notifications:', error);

            throw error; // Re-throw to allow caller to handle

        }

    }

}
 