import { createServer } from 'http'
import { Server, Socket } from 'socket.io'

const httpServer = createServer()
const io = new Server(httpServer, {
  // DO NOT change the path, it is used by Caddy to forward the request to the correct port
  path: '/',
  cors: {
    origin: "*",
    methods: ["GET", "POST"]
  },
  pingTimeout: 60000,
  pingInterval: 25000,
})

// Bidirectional mappings for quick lookup
const socketToUser = new Map<string, string>() // socketId -> userId
const userToSocket = new Map<string, string>() // userId -> socketId

// Helper to get socket by userId
const getSocketByUserId = (userId: string): Socket | null => {
  const socketId = userToSocket.get(userId)
  if (!socketId) return null
  return io.sockets.sockets.get(socketId) ?? null
}

io.on('connection', (socket: Socket) => {
  console.log(`[CONNECT] Socket connected: ${socket.id}`)

  // ─── Register ────────────────────────────────────────────────
  // User connects with their user ID
  socket.on('register', (data: { userId: string }) => {
    const { userId } = data

    // Clean up any previous mapping for this socket
    const prevUserId = socketToUser.get(socket.id)
    if (prevUserId) {
      userToSocket.delete(prevUserId)
    }

    // Clean up any previous socket for this userId (user reconnected)
    const prevSocketId = userToSocket.get(userId)
    if (prevSocketId) {
      socketToUser.delete(prevSocketId)
    }

    // Store bidirectional mapping
    socketToUser.set(socket.id, userId)
    userToSocket.set(userId, socket.id)

    console.log(`[REGISTER] User ${userId} registered on socket ${socket.id}`)
    console.log(`[REGISTER] Online users: ${userToSocket.size}`)

    socket.emit('register-success', { userId, socketId: socket.id })
  })

  // ─── Call User ───────────────────────────────────────────────
  // Initiate a call to another user
  socket.on('call-user', (data: { receiverId: string; callType: 'audio' | 'video'; callerName: string; signalData?: any }) => {
    const callerId = socketToUser.get(socket.id)
    if (!callerId) {
      console.warn(`[CALL-USER] Unregistered socket tried to call: ${socket.id}`)
      socket.emit('call-error', { message: 'You must register before making calls' })
      return
    }

    const { receiverId, callType, callerName, signalData } = data
    const receiverSocket = getSocketByUserId(receiverId)

    if (!receiverSocket) {
      console.warn(`[CALL-USER] Receiver ${receiverId} is not online`)
      socket.emit('call-error', { message: 'User is not online' })
      return
    }

    console.log(`[CALL-USER] ${callerId} (${callerName}) calling ${receiverId} [${callType}]`)

    receiverSocket.emit('incoming-call', {
      callerId,
      callerName,
      callType,
      signalData
    })
  })

  // ─── Answer Call ─────────────────────────────────────────────
  // Accept an incoming call
  socket.on('answer-call', (data: { callerId: string; signalData: any }) => {
    const answererId = socketToUser.get(socket.id)
    if (!answererId) {
      console.warn(`[ANSWER-CALL] Unregistered socket tried to answer: ${socket.id}`)
      return
    }

    const { callerId, signalData } = data
    const callerSocket = getSocketByUserId(callerId)

    if (!callerSocket) {
      console.warn(`[ANSWER-CALL] Caller ${callerId} is not online`)
      socket.emit('call-error', { message: 'Caller is no longer online' })
      return
    }

    console.log(`[ANSWER-CALL] ${answererId} accepted call from ${callerId}`)

    callerSocket.emit('call-accepted', {
      signalData
    })
  })

  // ─── Reject Call ─────────────────────────────────────────────
  // Reject an incoming call
  socket.on('reject-call', (data: { callerId: string }) => {
    const rejecterId = socketToUser.get(socket.id)
    if (!rejecterId) {
      console.warn(`[REJECT-CALL] Unregistered socket tried to reject: ${socket.id}`)
      return
    }

    const { callerId } = data
    const callerSocket = getSocketByUserId(callerId)

    if (!callerSocket) {
      console.warn(`[REJECT-CALL] Caller ${callerId} is not online`)
      return
    }

    console.log(`[REJECT-CALL] ${rejecterId} rejected call from ${callerId}`)

    callerSocket.emit('call-rejected', {})
  })

  // ─── End Call ────────────────────────────────────────────────
  // End an ongoing call
  socket.on('end-call', (data: { otherUserId: string }) => {
    const enderId = socketToUser.get(socket.id)
    if (!enderId) {
      console.warn(`[END-CALL] Unregistered socket tried to end call: ${socket.id}`)
      return
    }

    const { otherUserId } = data
    const otherSocket = getSocketByUserId(otherUserId)

    console.log(`[END-CALL] ${enderId} ended call with ${otherUserId}`)

    if (otherSocket) {
      otherSocket.emit('call-ended', {})
    }
  })

  // ─── ICE Candidate ───────────────────────────────────────────
  // Exchange ICE candidates for WebRTC
  socket.on('ice-candidate', (data: { receiverId: string; candidate: any }) => {
    const senderId = socketToUser.get(socket.id)
    if (!senderId) {
      console.warn(`[ICE-CANDIDATE] Unregistered socket: ${socket.id}`)
      return
    }

    const { receiverId, candidate } = data
    const receiverSocket = getSocketByUserId(receiverId)

    if (!receiverSocket) {
      console.warn(`[ICE-CANDIDATE] Receiver ${receiverId} is not online`)
      return
    }

    receiverSocket.emit('ice-candidate', {
      senderId,
      candidate
    })
  })

  // ─── Offer ───────────────────────────────────────────────────
  // Send WebRTC offer
  socket.on('offer', (data: { receiverId: string; offer: any }) => {
    const senderId = socketToUser.get(socket.id)
    if (!senderId) {
      console.warn(`[OFFER] Unregistered socket: ${socket.id}`)
      return
    }

    const { receiverId, offer } = data
    const receiverSocket = getSocketByUserId(receiverId)

    if (!receiverSocket) {
      console.warn(`[OFFER] Receiver ${receiverId} is not online`)
      socket.emit('call-error', { message: 'User is not online' })
      return
    }

    console.log(`[OFFER] ${senderId} -> ${receiverId}`)

    receiverSocket.emit('offer', {
      senderId,
      offer
    })
  })

  // ─── Answer ──────────────────────────────────────────────────
  // Send WebRTC answer
  socket.on('answer', (data: { receiverId: string; answer: any }) => {
    const senderId = socketToUser.get(socket.id)
    if (!senderId) {
      console.warn(`[ANSWER] Unregistered socket: ${socket.id}`)
      return
    }

    const { receiverId, answer } = data
    const receiverSocket = getSocketByUserId(receiverId)

    if (!receiverSocket) {
      console.warn(`[ANSWER] Receiver ${receiverId} is not online`)
      return
    }

    console.log(`[ANSWER] ${senderId} -> ${receiverId}`)

    receiverSocket.emit('answer', {
      senderId,
      answer
    })
  })

  // ─── Disconnect ──────────────────────────────────────────────
  socket.on('disconnect', (reason) => {
    const userId = socketToUser.get(socket.id)

    if (userId) {
      // Clean up both maps
      socketToUser.delete(socket.id)
      userToSocket.delete(userId)

      console.log(`[DISCONNECT] User ${userId} disconnected (socket ${socket.id}, reason: ${reason})`)
      console.log(`[DISCONNECT] Online users: ${userToSocket.size}`)
    } else {
      console.log(`[DISCONNECT] Unregistered socket disconnected: ${socket.id} (reason: ${reason})`)
    }
  })

  // ─── Error ───────────────────────────────────────────────────
  socket.on('error', (error: Error) => {
    const userId = socketToUser.get(socket.id)
    console.error(`[ERROR] Socket error (${userId ?? socket.id}):`, error.message)
  })
})

const PORT = 3005
httpServer.listen(PORT, () => {
  console.log(`📞 WebRTC Call Signaling Server running on port ${PORT}`)
})

// Graceful shutdown
process.on('SIGTERM', () => {
  console.log('Received SIGTERM signal, shutting down server...')
  io.disconnectSockets()
  httpServer.close(() => {
    console.log('Call Signaling Server closed')
    process.exit(0)
  })
})

process.on('SIGINT', () => {
  console.log('Received SIGINT signal, shutting down server...')
  io.disconnectSockets()
  httpServer.close(() => {
    console.log('Call Signaling Server closed')
    process.exit(0)
  })
})
