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

// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------

type UserRole = 'admin' | 'user'

interface OnlineUser {
  id: string
  name: string
  status: 'online'
  lastSeen: string
  role: UserRole
}

interface SystemNotification {
  id: string
  type: 'registration' | 'payment' | 'ticket' | 'system'
  title: string
  message: string
  createdAt: string
  read: boolean
}

interface DashboardStats {
  totalUsers: number
  activeUsers: number
  revenue: number
  activeTickets: number
  pendingPayments: number
  bandwidthUsage: number
}

interface TicketUpdate {
  ticketId: string
  status: string
  message: string
  updatedAt: string
}

interface PaymentReceived {
  paymentId: string
  userId: string
  amount: number
  plan: string
  timestamp: string
}

// ---------------------------------------------------------------------------
// In-memory state
// ---------------------------------------------------------------------------

const onlineUsers = new Map<string, OnlineUser>()
const notifications: SystemNotification[] = []
const dashboardSubscribers = new Set<string>() // socket ids
const onlineSubscribers = new Set<string>()    // socket ids

// Stub dashboard stats (in production these would come from a DB)
const stats: DashboardStats = {
  totalUsers: 0,
  activeUsers: 0,
  revenue: 0,
  activeTickets: 0,
  pendingPayments: 0,
  bandwidthUsage: 0,
}

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

const generateId = () =>
  `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 9)}`

const now = () => new Date().toISOString()

function broadcastOnlineUsers() {
  const users = Array.from(onlineUsers.values())
  const payload = { users }
  for (const sid of onlineSubscribers) {
    const sock = io.sockets.sockets.get(sid)
    if (sock) sock.emit('online-users', payload)
  }
}

function broadcastDashboardStats() {
  stats.activeUsers = onlineUsers.size
  for (const sid of dashboardSubscribers) {
    const sock = io.sockets.sockets.get(sid)
    if (sock) sock.emit('dashboard:stats', { ...stats })
  }
}

function broadcastNotification(notification: SystemNotification) {
  // Store in memory
  notifications.push(notification)

  // Send to all admin users
  for (const [sid, user] of onlineUsers) {
    if (user.role === 'admin') {
      const sock = io.sockets.sockets.get(sid)
      if (sock) sock.emit('notification', notification)
    }
  }
}

// ---------------------------------------------------------------------------
// Socket.IO server setup
// ---------------------------------------------------------------------------

const httpServer = createServer()
const io = new Server(httpServer, {
  path: '/',                // DO NOT change — Caddy routes on this path
  cors: {
    origin: '*',
    methods: ['GET', 'POST'],
  },
  pingTimeout: 60000,
  pingInterval: 25000,
})

const PORT = 3003

// ---------------------------------------------------------------------------
// Connection handler
// ---------------------------------------------------------------------------

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

  // -----------------------------------------------------------------------
  // Client → Server: join
  // -----------------------------------------------------------------------
  socket.on(
    'join',
    (data: { userId: string; name: string; role: UserRole }) => {
      const { userId, name, role } = data

      const user: OnlineUser = {
        id: userId,
        name: name || userId,
        status: 'online',
        lastSeen: now(),
        role: role || 'user',
      }

      onlineUsers.set(socket.id, user)
      console.log(`[isp-socket] user joined: ${user.name} (${user.role}) — total online: ${onlineUsers.size}`)

      // Immediately send the current user list to this subscriber if already subscribed
      if (onlineSubscribers.has(socket.id)) {
        const users = Array.from(onlineUsers.values())
        socket.emit('online-users', { users })
      }

      // Refresh dashboard stats
      broadcastDashboardStats()
    },
  )

  // -----------------------------------------------------------------------
  // Client → Server: subscribe:dashboard
  // -----------------------------------------------------------------------
  socket.on('subscribe:dashboard', () => {
    dashboardSubscribers.add(socket.id)
    console.log(`[isp-socket] ${socket.id} subscribed to dashboard updates`)
    // Push current stats immediately
    stats.activeUsers = onlineUsers.size
    socket.emit('dashboard:stats', { ...stats })
  })

  // -----------------------------------------------------------------------
  // Client → Server: subscribe:online
  // -----------------------------------------------------------------------
  socket.on('subscribe:online', () => {
    onlineSubscribers.add(socket.id)
    console.log(`[isp-socket] ${socket.id} subscribed to online user list`)
    // Push current list immediately
    const users = Array.from(onlineUsers.values())
    socket.emit('online-users', { users })
  })

  // -----------------------------------------------------------------------
  // Client → Server: notification:read
  // -----------------------------------------------------------------------
  socket.on('notification:read', (data: { notificationId: string }) => {
    const notification = notifications.find((n) => n.id === data.notificationId)
    if (notification) {
      notification.read = true
      console.log(`[isp-socket] notification marked read: ${data.notificationId}`)
    }
  })

  // -----------------------------------------------------------------------
  // Client → Server: leave (explicit disconnect)
  // -----------------------------------------------------------------------
  socket.on('leave', () => {
    handleDisconnect(socket)
    socket.disconnect(true)
  })

  // -----------------------------------------------------------------------
  // Internal helper — clean up on disconnect
  // -----------------------------------------------------------------------
  function handleDisconnect(sock: Socket) {
    const user = onlineUsers.get(sock.id)
    if (user) {
      onlineUsers.delete(sock.id)
      dashboardSubscribers.delete(sock.id)
      onlineSubscribers.delete(sock.id)
      console.log(
        `[isp-socket] user left: ${user.name} — total online: ${onlineUsers.size}`,
      )
      broadcastOnlineUsers()
      broadcastDashboardStats()
    }
  }

  // -----------------------------------------------------------------------
  // Disconnect (implicit)
  // -----------------------------------------------------------------------
  socket.on('disconnect', (reason) => {
    console.log(`[isp-socket] disconnected: ${socket.id} (${reason})`)
    handleDisconnect(socket)
  })

  socket.on('error', (err) => {
    console.error(`[isp-socket] error on ${socket.id}:`, err)
  })
})

// ---------------------------------------------------------------------------
// Public API — can be called from other services / API routes via HTTP
// ---------------------------------------------------------------------------

// Export the io instance so other modules can import it (for inter-service comms)
export { io }

// Also attach to global so the HTTP server can trigger pushes
;(globalThis as any).__ispSocketIO = io

// ---------------------------------------------------------------------------
// Start server
// ---------------------------------------------------------------------------

httpServer.listen(PORT, () => {
  console.log(`[isp-socket] ISP Socket.IO service running on port ${PORT}`)
})

// Graceful shutdown
function shutdown(signal: string) {
  console.log(`[isp-socket] received ${signal}, shutting down…`)
  io.close()
  httpServer.close(() => {
    console.log('[isp-socket] server closed')
    process.exit(0)
  })
}

process.on('SIGTERM', () => shutdown('SIGTERM'))
process.on('SIGINT', () => shutdown('SIGINT'))
