import { createServer } from 'node:http'
import closeWithGrace from 'close-with-grace'
import { Server } from 'socket.io'
import { createApp } from './app.js'
import { env } from './config/env.js'
import { connectDb, disconnectDb } from './db.js'
import { logger } from './lib/logger.js'
import { readAccessToken } from './lib/tokens.js'
import { mediaRouter } from './signaling/mediaRouter.js'
import { registerSignaling } from './signaling/register.js'

const app = createApp()
const httpServer = createServer(app)
const io = new Server(httpServer, {
  cors: { origin: env.CLIENT_ORIGIN, credentials: true },
})

io.use((socket, next) => {
  const token = socket.handshake.auth?.token
  if (typeof token !== 'string' || token.length === 0) {
    next(new Error('Unauthorized'))
    return
  }
  try {
    socket.data.identity = readAccessToken(token)
    next()
  } catch (error) {
    next(error instanceof Error ? error : new Error('Unauthorized'))
  }
})

registerSignaling(io)

const start = async () => {
  await new Promise<void>((resolve) => {
    httpServer.listen(env.PORT, '0.0.0.0', () => resolve())
  })
  logger.info(`API listening on ${env.PORT}`, { media: mediaRouter.mode })

  for (let attempt = 1; ; attempt++) {
    try {
      await connectDb()
      return
    } catch (error) {
      logger.error(`MongoDB is not ready (${attempt})`, error)
      await new Promise((resolve) => setTimeout(resolve, 2000))
    }
  }
}

closeWithGrace({ delay: 10_000 }, async ({ err, signal }) => {
  if (err) logger.error('Shutdown because of error', err)
  else logger.info(`Received ${signal ?? 'shutdown'}, closing`)
  io.close()
  await new Promise<void>((resolve, reject) => {
    httpServer.close((error) => (error ? reject(error) : resolve()))
  })
  await disconnectDb()
})

start().catch((error: unknown) => {
  logger.error('Failed to start', error)
  process.exit(1)
})
