import { WebSocketServer } from 'ws';
import { createServer } from 'http';
import { parse } from 'url';
import { randomUUID } from 'crypto';

const PORT = process.env.PORT || 3001;
const STALE_THRESHOLD_MS = 60 * 60 * 1000; // 1 hour

// Room state storage: Map<roomSlug, RoomState>
const rooms = new Map();

// Client tracking: Map<WebSocket, { roomSlug, participantId }>
const clients = new Map();

function getRoomState(slug) {
  if (!rooms.has(slug)) {
    rooms.set(slug, {
      id: randomUUID(),
      slug: slug,
      name: slug,
      createdAt: new Date().toISOString(),
      participants: {},
      waypoints: [],  // Array to match client expectation
      lastActivity: Date.now()
    });
  }
  const room = rooms.get(slug);
  room.lastActivity = Date.now();
  return room;
}

function cleanupStaleParticipants(room) {
  const now = Date.now();
  const staleIds = [];

  for (const [id, participant] of Object.entries(room.participants)) {
    if (participant.lastSeen && now - participant.lastSeen > STALE_THRESHOLD_MS) {
      staleIds.push(id);
    }
  }

  for (const id of staleIds) {
    delete room.participants[id];
    console.log(`Cleaned up stale participant: ${id}`);
  }

  return staleIds;
}

function broadcast(roomSlug, message, excludeWs = null) {
  const messageStr = JSON.stringify(message);
  let count = 0;

  for (const [ws, clientInfo] of clients.entries()) {
    if (clientInfo.roomSlug === roomSlug && ws !== excludeWs && ws.readyState === 1) {
      ws.send(messageStr);
      count++;
    }
  }

  return count;
}

function handleMessage(ws, data) {
  const clientInfo = clients.get(ws);
  if (!clientInfo) return;

  let message;
  try {
    message = JSON.parse(data);
  } catch (e) {
    console.error('Invalid JSON:', e.message);
    return;
  }

  const room = getRoomState(clientInfo.roomSlug);

  switch (message.type) {
    case 'join': {
      const participant = {
        ...message.participant,
        lastSeen: Date.now()
      };
      room.participants[participant.id] = participant;
      clientInfo.participantId = participant.id;

      console.log(`[${clientInfo.roomSlug}] ${participant.name} joined (${participant.id})`);

      // Broadcast join to others
      broadcast(clientInfo.roomSlug, message, ws);

      // Send current state to the new participant
      ws.send(JSON.stringify({
        type: 'full_state',
        state: room
      }));
      break;
    }

    case 'leave': {
      delete room.participants[message.participantId];
      console.log(`[${clientInfo.roomSlug}] Participant left: ${message.participantId}`);
      broadcast(clientInfo.roomSlug, message, ws);
      break;
    }

    case 'location': {
      if (room.participants[message.participantId]) {
        room.participants[message.participantId].location = message.location;
        room.participants[message.participantId].lastSeen = Date.now();

        // Broadcast to all OTHER participants
        const count = broadcast(clientInfo.roomSlug, message, ws);
        console.log(`[${clientInfo.roomSlug}] Location update from ${message.participantId} -> ${count} clients`);
      }
      break;
    }

    case 'status': {
      if (room.participants[message.participantId]) {
        room.participants[message.participantId].status = message.status;
        room.participants[message.participantId].lastSeen = Date.now();
        broadcast(clientInfo.roomSlug, message, ws);
      }
      break;
    }

    case 'waypoint_add': {
      room.waypoints.push(message.waypoint);
      console.log(`[${clientInfo.roomSlug}] Waypoint added: ${message.waypoint.id}`);
      broadcast(clientInfo.roomSlug, message, ws);
      break;
    }

    case 'waypoint_remove': {
      room.waypoints = room.waypoints.filter(w => w.id !== message.waypointId);
      console.log(`[${clientInfo.roomSlug}] Waypoint removed: ${message.waypointId}`);
      broadcast(clientInfo.roomSlug, message, ws);
      break;
    }

    case 'request_state': {
      cleanupStaleParticipants(room);
      ws.send(JSON.stringify({
        type: 'full_state',
        state: room
      }));
      break;
    }

    default:
      console.log(`Unknown message type: ${message.type}`);
  }
}

function handleClose(ws) {
  const clientInfo = clients.get(ws);
  if (clientInfo) {
    const room = rooms.get(clientInfo.roomSlug);
    if (room && clientInfo.participantId && room.participants[clientInfo.participantId]) {
      // Don't delete - mark as offline and preserve last location
      room.participants[clientInfo.participantId].status = 'offline';
      room.participants[clientInfo.participantId].lastSeen = Date.now();
      broadcast(clientInfo.roomSlug, {
        type: 'status',
        participantId: clientInfo.participantId,
        status: 'offline'
      });
      console.log(`[${clientInfo.roomSlug}] User went offline: ${clientInfo.participantId} (location preserved)`);
    }
    clients.delete(ws);
  }
}

// Periodic cleanup of empty rooms
setInterval(() => {
  const now = Date.now();
  for (const [slug, room] of rooms.entries()) {
    // Clean stale participants
    cleanupStaleParticipants(room);

    // Remove empty rooms older than 24 hours
    if (Object.keys(room.participants).length === 0 &&
        now - room.lastActivity > 24 * 60 * 60 * 1000) {
      rooms.delete(slug);
      console.log(`Cleaned up empty room: ${slug}`);
    }
  }
}, 5 * 60 * 1000); // Every 5 minutes

// Create HTTP server for health checks
const server = createServer((req, res) => {
  const { pathname } = parse(req.url);

  if (pathname === '/health') {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({
      status: 'ok',
      rooms: rooms.size,
      clients: clients.size,
      uptime: process.uptime()
    }));
  } else if (pathname === '/stats') {
    const roomStats = {};
    for (const [slug, room] of rooms.entries()) {
      roomStats[slug] = {
        participants: Object.keys(room.participants).length,
        waypoints: room.waypoints.length,
        lastActivity: room.lastActivity
      };
    }
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ rooms: roomStats, totalClients: clients.size }));
  } else {
    res.writeHead(404);
    res.end('Not found');
  }
});

// Create WebSocket server
const wss = new WebSocketServer({ server });

wss.on('connection', (ws, req) => {
  const { pathname } = parse(req.url);

  // Extract room slug from path: /room/{slug}
  const match = pathname?.match(/^\/room\/([^/]+)$/);
  if (!match) {
    console.log(`Invalid path: ${pathname}`);
    ws.close(4000, 'Invalid room path');
    return;
  }

  const roomSlug = decodeURIComponent(match[1]);
  console.log(`New connection to room: ${roomSlug}`);

  // Register client
  clients.set(ws, { roomSlug, participantId: null });

  // Set up handlers
  ws.on('message', (data) => handleMessage(ws, data.toString()));
  ws.on('close', () => handleClose(ws));
  ws.on('error', (err) => {
    console.error('WebSocket error:', err.message);
    handleClose(ws);
  });

  // Send ping every 30 seconds to keep connection alive
  const pingInterval = setInterval(() => {
    if (ws.readyState === 1) {
      ws.ping();
    } else {
      clearInterval(pingInterval);
    }
  }, 30000);

  ws.on('close', () => clearInterval(pingInterval));
});

server.listen(PORT, () => {
  console.log(`rmaps sync server listening on port ${PORT}`);
  console.log(`WebSocket: ws://localhost:${PORT}/room/{slug}`);
  console.log(`Health check: http://localhost:${PORT}/health`);
});
