import type { Server as HTTPServer } from 'http';
import { Server } from 'socket.io';
import { verifyAccessToken } from '../utils/jwt.js';
import { prisma } from '../config/database.js';

// Online status tracking
const onlineUsers = new Map<string, Set<string>>();

export function setupSocket(httpServer: HTTPServer) {
  const io = new Server(httpServer, {
    cors: {
      origin: process.env['CLIENT_URL'] || 'http://localhost:5173',
      credentials: true,
    },
  });

  io.use((socket, next) => {
    const token = socket.handshake.auth.token;
    if (!token) return next(new Error('Authentication required'));
    try {
      const payload = verifyAccessToken(token);
      socket.data.userId = payload.userId;
      next();
    } catch {
      next(new Error('Invalid token'));
    }
  });

  io.on('connection', (socket) => {
    const userId = socket.data.userId;
    socket.join(`user:${userId}`);

    // Track online status
    if (!onlineUsers.has(userId)) {
      onlineUsers.set(userId, new Set());
    }
    onlineUsers.get(userId)!.add(socket.id);

    // Broadcast online status
    socket.broadcast.emit('user_online', { userId });

    socket.on('join_conversation', (conversationId: string) => {
      socket.join(`conversation:${conversationId}`);
    });

    socket.on('leave_conversation', (conversationId: string) => {
      socket.leave(`conversation:${conversationId}`);
    });

    socket.on('send_message', async (data: { conversationId: string; content: string; offerAmount?: number }) => {
      try {
        const message = await prisma.message.create({
          data: {
            content: data.content,
            senderId: userId,
            conversationId: data.conversationId,
            offerAmount: data.offerAmount,
          },
          include: { sender: { select: { id: true, fullName: true, avatar: true } } },
        });

        await prisma.conversation.update({
          where: { id: data.conversationId },
          data: { updatedAt: new Date() },
        });

        io.to(`conversation:${data.conversationId}`).emit('new_message', message);

        const conversation = await prisma.conversation.findUnique({ where: { id: data.conversationId } });
        if (conversation) {
          const recipientId = conversation.user1Id === userId ? conversation.user2Id : conversation.user1Id;
          io.to(`user:${recipientId}`).emit('message_notification', { conversationId: data.conversationId, message });

          // Create notification
          const sender = await prisma.user.findUnique({ where: { id: userId }, select: { fullName: true } });
          const notification = await prisma.notification.create({
            data: {
              userId: recipientId,
              type: 'NEW_MESSAGE',
              title: 'New Message',
              body: `${sender?.fullName || 'Someone'} sent you a message`,
              data: { conversationId: data.conversationId },
            },
          });
          io.to(`user:${recipientId}`).emit('new_notification', notification);
        }
      } catch (error) {
        socket.emit('error', { message: 'Failed to send message' });
      }
    });

    socket.on('typing', (conversationId: string) => {
      socket.to(`conversation:${conversationId}`).emit('user_typing', { userId, conversationId });
    });

    socket.on('stop_typing', (conversationId: string) => {
      socket.to(`conversation:${conversationId}`).emit('user_stop_typing', { userId, conversationId });
    });

    socket.on('mark_read', async (conversationId: string) => {
      await prisma.message.updateMany({
        where: { conversationId, senderId: { not: userId }, isRead: false },
        data: { isRead: true },
      });
    });

    socket.on('get_online_users', () => {
      const users = Array.from(onlineUsers.keys());
      socket.emit('online_users', users);
    });

    socket.on('disconnect', () => {
      const userSockets = onlineUsers.get(userId);
      if (userSockets) {
        userSockets.delete(socket.id);
        if (userSockets.size === 0) {
          onlineUsers.delete(userId);
          socket.broadcast.emit('user_offline', { userId });
        }
      }
    });
  });

  return io;
}
