const express = require('express');
const adb = require('adbkit');
const path = require('path');

// Expirements - Android Rented Remote Control

const app = express();
const PORT = process.env.PORT || 3000;

// Middleware
app.use(express.json());
app.use(express.static(path.join(__dirname, 'public')));

// Get client IP address middleware
function getClientIP(req) {
    return req.headers['x-forwarded-for'] ||
           req.headers['x-real-ip'] ||
           req.connection.remoteAddress ||
           req.socket.remoteAddress ||
           (req.connection.socket ? req.connection.socket.remoteAddress : null) ||
           req.ip;
}

// ADB client
const client = adb.createClient();

// Store connected devices
let connectedDevices = [];
let activeDevices = new Map(); // deviceId -> { userId, timer, timeRemaining }
let deviceScreenSizes = new Map(); // deviceId -> screenSize
let activeDevice = null; // Keep for backward compatibility with screen streaming
let deviceScreenSize = { width: 720, height: 1280 }; // Global fallback screen size

// Queue management
let userQueue = [];
let activeUsers = new Map(); // userId -> deviceId
let deviceTimers = new Map(); // deviceId -> timer
let timeRemainingMap = new Map(); // deviceId -> timeRemaining
let ipUserMap = new Map(); // Track users by IP address

// API Routes
app.get('/api/devices', async (req, res) => {
    try {
      const devices = await client.listDevices();
      connectedDevices = devices.map(device => ({
        id: device.id,
        type: device.type,
        status: 'connected'
      }));


      res.json(connectedDevices);
    } catch (error) {
      console.error('Error getting devices:', error);
      res.status(500).json({ error: 'Failed to get devices' });
    }
});

app.post('/api/set-device/:deviceId', async (req, res) => {
    try {
      const { deviceId } = req.params;
      const device = connectedDevices.find(d => d.id === deviceId);

      if (device) {
        activeDevice = device;

        // Get device screen size using multiple methods
        try {
          // Method 1: Try wm size first (most reliable)
          try {
            const wmSize = await client.shell(activeDevice.id, 'wm size');
            const wmOutput = await wmSize.toString();

            // Try different patterns for wm size output
            const wmPatterns = [
              /Physical size: (\d+)x(\d+)/i,
              /Override size: (\d+)x(\d+)/i,
              /(\d+)x(\d+)/i
            ];

            for (const pattern of wmPatterns) {
              const wmMatch = wmOutput.match(pattern);
              if (wmMatch) {
                deviceScreenSize = {
                  width: parseInt(wmMatch[1]),
                  height: parseInt(wmMatch[2])
                };
                break;
              }
            }
          } catch (wmError) {
            console.error('Error getting wm size:', wmError);
          }

          // Method 2: If wm didn't work, try dumpsys window
          if (!deviceScreenSize) {
            try {
              const dumpsysWindow = await client.shell(activeDevice.id, 'dumpsys window');
              const windowOutput = await dumpsysWindow.toString();
              console.log('Dumpsys window output (first 1000 chars):', windowOutput.substring(0, 1000));

              // Look for app window size
              const windowMatch = windowOutput.match(/Requested.*width=(\d+).*height=(\d+)/i) ||
                                 windowOutput.match(/DisplayWidth=(\d+).*DisplayHeight=(\d+)/i) ||
                                 windowOutput.match(/mSurfaceSize\.width=(\d+).*mSurfaceSize\.height=(\d+)/i);

              if (windowMatch) {
                deviceScreenSize = {
                  width: parseInt(windowMatch[1]),
                  height: parseInt(windowMatch[2])
                };
                console.log('Device screen size from dumpsys window:', deviceScreenSize);
              }
            } catch (windowError) {
              console.error('Error getting dumpsys window:', windowError);
            }
          }

          // Method 3: Try dumpsys display as last resort
          if (!deviceScreenSize) {
            try {
              const dumpsysDisplay = await client.shell(activeDevice.id, 'dumpsys display');
              const displayOutput = await dumpsysDisplay.toString();
              console.log('Dumpsys display output (first 1000 chars):', displayOutput.substring(0, 1000));

              const displayPatterns = [
                /DisplayViewport.*width=(\d+).*height=(\d+)/i,
                /mCurrentDisplayRect.*width=(\d+).*height=(\d+)/i,
                /mDisplayRect.*width=(\d+).*height=(\d+)/i,
                /(\d+)x(\d+) \d+\.\d+ fps/i
              ];

              for (const pattern of displayPatterns) {
                const displayMatch = displayOutput.match(pattern);
                if (displayMatch) {
                  deviceScreenSize = {
                    width: parseInt(displayMatch[1]),
                    height: parseInt(displayMatch[2])
                  };
                  console.log('Device screen size from dumpsys display:', deviceScreenSize);
                  break;
                }
              }
            } catch (displayError) {
              console.error('Error getting dumpsys display:', displayError);
            }
          }
        } catch (sizeError) {
          console.error('Error getting screen size:', sizeError);
          deviceScreenSize = { width: 720, height: 1280 }; // Use the actual screenshot size as default
        }

        // Ensure we have a valid screen size
        if (!deviceScreenSize || deviceScreenSize.width <= 0 || deviceScreenSize.height <= 0) {
          deviceScreenSize = { width: 720, height: 1280 }; // Use the actual screenshot size
          console.log('Using fallback screen size:', deviceScreenSize);
        }


        res.json({ success: true, device, screenSize: deviceScreenSize });
      } else {
        res.status(404).json({ error: 'Device not found' });
      }
    } catch (error) {
      console.error('Error setting active device:', error);
      res.status(500).json({ error: 'Failed to set active device' });
    }
});

app.post('/api/command', async (req, res) => {
    const clientIP = getClientIP(req);
    const userId = getUserIdFromIP(clientIP);
    const deviceId = activeUsers.get(userId);

    if (!deviceId) {
      return res.status(400).json({ error: 'No device assigned to user' });
    }

    try {
      const { command } = req.body;

      switch (command) {
        case 'home':
          await client.shell(deviceId, 'input keyevent 3');
          break;
        case 'back':
          await client.shell(deviceId, 'input keyevent 4');
          break;
        case 'recent':
          await client.shell(deviceId, 'input keyevent 187');
          break;
        case 'power':
          await client.shell(deviceId, 'input keyevent 26');
          break;
        case 'volume_up':
          await client.shell(deviceId, 'input keyevent 24');
          break;
        case 'volume_down':
          await client.shell(deviceId, 'input keyevent 25');
          break;
      }

      res.json({ success: true, command, deviceId });
    } catch (error) {
      console.error('Error sending command:', error);
      res.status(500).json({ error: 'Failed to send command' });
    }
});

app.post('/api/tap', async (req, res) => {
    const clientIP = getClientIP(req);
    const userId = getUserIdFromIP(clientIP);
    const deviceId = activeUsers.get(userId);

    if (!deviceId) {
      return res.status(400).json({ error: 'No device assigned to user' });
    }

    try {
      let { x, y, containerWidth = 720, containerHeight = 1280 } = req.body;

      // Scale coordinates based on device screen size vs container size
      let scaledX = x;
      let scaledY = y;

      if (deviceScreenSize) {
        scaledX = (x / containerWidth) * deviceScreenSize.width;
        scaledY = (y / containerHeight) * deviceScreenSize.height;
      }

      await client.shell(deviceId, `input tap ${Math.round(scaledX)} ${Math.round(scaledY)}`);
      res.json({ success: true, x: Math.round(scaledX), y: Math.round(scaledY), originalX: x, originalY: y, deviceId });
    } catch (error) {
      console.error('Error sending tap:', error);
      res.status(500).json({ error: 'Failed to send tap' });
    }
});

app.post('/api/text', async (req, res) => {
    const clientIP = getClientIP(req);
    const userId = getUserIdFromIP(clientIP);
    const deviceId = activeUsers.get(userId);

    if (!deviceId) {
      return res.status(400).json({ error: 'No device assigned to user' });
    }

    try {
      const { text } = req.body;
      await client.shell(deviceId, `input text "${text}"`);

      res.json({ success: true, text, deviceId });
    } catch (error) {
      console.error('Error sending text:', error);
      res.status(500).json({ error: 'Failed to send text' });
    }
});

app.get('/api/device-info/:deviceId', async (req, res) => {
    try {
      const { deviceId } = req.params;
      const properties = await client.getProperties(deviceId);
      console.log('Device info:', properties);
      res.json({ deviceId, properties });
    } catch (error) {
      console.error('Error getting device info:', error);
      res.status(500).json({ error: 'Failed to get device info' });
    }
});

app.get('/screen.png', async (req, res) => {
    const clientIP = getClientIP(req);
    const userId = getUserIdFromIP(clientIP);
    const deviceId = activeUsers.get(userId);

    if (!deviceId) {
      return res.status(400).send('No device assigned to user');
    }

    try {
      // Use screencap command to capture screen
      const screencap = await client.screencap(deviceId);

      // Collect the image data to analyze it
      const chunks = [];
      screencap.on('data', chunk => chunks.push(chunk));
      screencap.on('end', async () => {
        try {
          const buffer = Buffer.concat(chunks);

          // Analyze PNG header to get dimensions
          const width = buffer.readUInt32BE(16);
          const height = buffer.readUInt32BE(20);

          // Update device screen size if we got valid dimensions
          if (width > 0 && height > 0 && (!deviceScreenSize || deviceScreenSize.width !== width || deviceScreenSize.height !== height)) {
            deviceScreenSize = { width, height };
          }

          // Set response headers for PNG image
          res.setHeader('Content-Type', 'image/png');
          res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
          res.setHeader('Pragma', 'no-cache');
          res.setHeader('Expires', '0');

          // Send the image data
          res.send(buffer);
        } catch (analysisError) {
          console.error('Error analyzing image:', analysisError);
          // Fallback: send the image without analysis
          res.setHeader('Content-Type', 'image/png');
          res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
          res.setHeader('Pragma', 'no-cache');
          res.setHeader('Expires', '0');
          res.send(Buffer.concat(chunks));
        }
      });

      screencap.on('error', (error) => {
        console.error('Error in screencap stream:', error);
        res.status(500).send('Failed to capture screen');
      });
    } catch (error) {
      console.error('Error capturing screen:', error);
      res.status(500).send('Failed to capture screen');
    }
});

// Queue management functions
function getUserIdFromIP(clientIP) {
    // Clean up IP address (remove IPv6 prefix if present)
    const cleanIP = clientIP.replace(/^::ffff:/, '');
    return `ip_${cleanIP.replace(/[^a-zA-Z0-9]/g, '_')}`;
}

function getAvailableDevices() {
    return connectedDevices.filter(device => !activeDevices.has(device.id));
}

function addUserToQueue(userId, clientIP) {
    // Check if this IP already has a user in queue or active
    const existingUserIndex = userQueue.findIndex(user => user.ip === clientIP);
    const hasActiveUser = activeUsers.has(userId);

    if (existingUserIndex !== -1 || hasActiveUser) {
      return false;
    }

    userQueue.push({
      id: userId,
      ip: clientIP,
      joinedAt: new Date(),
      position: userQueue.length + 1
    });

    // Track IP to user mapping
    ipUserMap.set(clientIP, userId);
    updateQueuePositions();
    return true;
}

function removeUserFromQueue(userId) {
    const index = userQueue.findIndex(user => user.id === userId);
    if (index !== -1) {
      const userIP = userQueue[index].ip;
      userQueue.splice(index, 1);
      ipUserMap.delete(userIP);
      updateQueuePositions();
      return true;
    }
    return false;
}

function removeUserByIP(clientIP) {
    const index = userQueue.findIndex(user => user.ip === clientIP);
    if (index !== -1) {
      const userId = userQueue[index].id;
      userQueue.splice(index, 1);
      ipUserMap.delete(clientIP);
      updateQueuePositions();
      return userId;
    }
    return null;
}

function updateQueuePositions() {
    userQueue.forEach((user, index) => {
      user.position = index + 1;
    });
}

function getEstimatedWaitTime(position) {
    // Calculate based on available devices and queue length
    const availableDevices = getAvailableDevices().length;
    if (availableDevices === 0) {
      return position * 5; // 5 minutes per position if no devices
    }
    return Math.ceil(position / availableDevices) * 5;
}

function assignUserToDevice(user, device) {
    const deviceId = device.id;
    const userId = user.id;

    // Assign device to user
    activeDevices.set(deviceId, {
      userId: userId,
      deviceId: deviceId,
      assignedAt: new Date()
    });

    activeUsers.set(userId, deviceId);
    timeRemainingMap.set(deviceId, 5 * 60); // 5 minutes

    // Start timer for this device
    startDeviceTimer(deviceId);

    console.log(`✅ Assigned device ${deviceId} to user ${userId} (IP: ${user.ip})`);
    return true;
}

function startDeviceTimer(deviceId) {
    // Clear existing timer
    if (deviceTimers.has(deviceId)) {
      clearInterval(deviceTimers.get(deviceId));
    }

    const timer = setInterval(() => {
      const timeRemaining = timeRemainingMap.get(deviceId) || 0;
      const newTime = timeRemaining - 1;

      if (newTime <= 0) {
        // Time's up
        releaseDevice(deviceId);
        assignNextUserToAvailableDevice();
      } else {
        timeRemainingMap.set(deviceId, newTime);
      }
    }, 1000);

    deviceTimers.set(deviceId, timer);
}

function releaseDevice(deviceId) {
    const deviceInfo = activeDevices.get(deviceId);
    if (deviceInfo) {
      const userId = deviceInfo.userId;

      // Clean up
      activeDevices.delete(deviceId);
      activeUsers.delete(userId);
      timeRemainingMap.delete(deviceId);

      if (deviceTimers.has(deviceId)) {
        clearInterval(deviceTimers.get(deviceId));
        deviceTimers.delete(deviceId);
      }

      console.log(`⏰ Time's up for device ${deviceId}, user ${userId} released`);
    }
}

function assignNextUserToAvailableDevice() {
    const availableDevices = getAvailableDevices();
    if (availableDevices.length > 0 && userQueue.length > 0) {
      const nextUser = userQueue.shift();
      const device = availableDevices[0]; // Use first available device

      assignUserToDevice(nextUser, device);
      updateQueuePositions();
    }
}

// Auto-refresh devices on server start
async function initializeDevices() {
    try {
      console.log('🔄 Initializing devices on server start...');
      const devices = await client.listDevices();
      connectedDevices = devices.map(device => ({
        id: device.id,
        type: device.type,
        status: 'connected'
      }));

      console.log(`📱 Found ${connectedDevices.length} devices:`, connectedDevices.map(d => d.id));

      // Auto-assign users to available devices
      if (connectedDevices.length > 0 && userQueue.length > 0) {
        console.log('🚀 Auto-assigning users to available devices...');
        assignNextUserToAvailableDevice();
      }
    } catch (error) {
      console.error('❌ Error initializing devices:', error);
    }
}

// Manual trigger for device assignment (for testing)
app.post('/api/assign-device', (req, res) => {
    const availableDevices = getAvailableDevices();
    if (availableDevices.length > 0 && userQueue.length > 0) {
      const nextUser = userQueue.shift();
      const device = availableDevices[0];
      assignUserToDevice(nextUser, device);
      updateQueuePositions();

      res.json({
        success: true,
        assignedUser: nextUser.id,
        deviceId: device.id,
        timeRemaining: 5 * 60
      });
    } else {
      res.status(400).json({ error: 'No devices available or no users in queue' });
    }
});

// Queue API endpoints
app.post('/api/join-queue', (req, res) => {
    const clientIP = getClientIP(req);
    const userId = getUserIdFromIP(clientIP);
    console.log(`Join queue request from IP: ${clientIP}, UserID: ${userId}`);

    const added = addUserToQueue(userId, clientIP);

    if (added) {
      const user = userQueue.find(u => u.id === userId);
      const waitTime = getEstimatedWaitTime(user.position);
      const availableDevices = getAvailableDevices();

      console.log(`User added to queue. Position: ${user.position}, Queue length: ${userQueue.length}, Available devices: ${availableDevices.length}`);

      // If devices are available, assign immediately
      if (availableDevices.length > 0) {
        console.log('Devices available - assigning immediately');
        assignNextUserToAvailableDevice();
      }

      res.json({
        success: true,
        position: user.position,
        waitTime: waitTime,
        queueLength: userQueue.length,
        availableDevices: availableDevices.length,
        userId: userId
      });
    } else {
      console.log('User already in queue or active');
      res.status(400).json({ error: 'IP address already in queue or active' });
    }
});

app.get('/api/queue-status', (req, res) => {
    const clientIP = getClientIP(req);
    const userId = getUserIdFromIP(clientIP);
    const user = userQueue.find(u => u.id === userId);
    const activeDeviceId = activeUsers.get(userId);
    const timeRemaining = activeDeviceId ? timeRemainingMap.get(activeDeviceId) : 0;

    if (user) {
      const waitTime = getEstimatedWaitTime(user.position);
      res.json({
        inQueue: true,
        position: user.position,
        waitTime: waitTime,
        queueLength: userQueue.length,
        activeDevices: Array.from(activeDevices.keys()),
        activeUsers: Array.from(activeUsers.entries()),
        timeRemaining: timeRemaining,
        userId: userId
      });
    } else if (activeUsers.has(userId)) {
      res.json({
        inQueue: false,
        active: true,
        deviceId: activeDeviceId,
        timeRemaining: timeRemaining,
        activeUsers: Array.from(activeUsers.entries()),
        userId: userId
      });
    } else {
      res.json({
        inQueue: false,
        active: false,
        queueLength: userQueue.length,
        availableDevices: getAvailableDevices().length,
        activeDevices: Array.from(activeDevices.keys()),
        userId: userId
      });
    }
});

app.post('/api/leave-queue', (req, res) => {
    const clientIP = getClientIP(req);
    const userId = removeUserByIP(clientIP);

    if (userId) {
      // Also check if user is active and release their device
      if (activeUsers.has(userId)) {
        const deviceId = activeUsers.get(userId);
        releaseDevice(deviceId);
      }

      res.json({ success: true, userId: userId });
    } else {
      res.status(404).json({ error: 'IP address not found in queue' });
    }
});

// Legacy endpoints for backward compatibility (updated for multi-device)
app.get('/api/queue-status/:userId', (req, res) => {
    const userId = req.params.userId;
    const user = userQueue.find(u => u.id === userId);

    if (user) {
      const waitTime = getEstimatedWaitTime(user.position);
      res.json({
        inQueue: true,
        position: user.position,
        waitTime: waitTime,
        queueLength: userQueue.length,
        activeDevices: Array.from(activeDevices.keys()),
        activeUsers: Array.from(activeUsers.entries()),
        timeRemaining: 0
      });
    } else if (activeUsers.has(userId)) {
      const deviceId = activeUsers.get(userId);
      const timeRemaining = timeRemainingMap.get(deviceId) || 0;
      res.json({
        inQueue: false,
        active: true,
        deviceId: deviceId,
        timeRemaining: timeRemaining,
        activeUsers: Array.from(activeUsers.entries())
      });
    } else {
      res.json({
        inQueue: false,
        active: false,
        queueLength: userQueue.length,
        availableDevices: getAvailableDevices().length,
        activeDevices: Array.from(activeDevices.keys())
      });
    }
});

app.post('/api/leave-queue/:userId', (req, res) => {
    const userId = req.params.userId;
    const removed = removeUserFromQueue(userId);

    if (removed) {
      // Also check if user is active and release their device
      if (activeUsers.has(userId)) {
        const deviceId = activeUsers.get(userId);
        releaseDevice(deviceId);
      }
      res.json({ success: true });
    } else {
      res.status(404).json({ error: 'User not found in queue' });
    }
});

// Modified device connection handler
app.get('/api/devices', async (req, res) => {
    try {
      const devices = await client.listDevices();
      const previousDeviceCount = connectedDevices.length;
      connectedDevices = devices.map(device => ({
        id: device.id,
        type: device.type,
        status: 'connected'
      }));

      console.log(`📱 Device check: ${connectedDevices.length} devices, previous: ${previousDeviceCount}`);
      console.log(`👥 Queue: ${userQueue.length} users, Active devices: ${activeDevices.size}`);

      // Check for new devices and assign to waiting users
      if (connectedDevices.length > previousDeviceCount) {
        console.log('🔌 New devices detected!');
        const availableDevices = getAvailableDevices();
        console.log(`🎯 Available devices: ${availableDevices.length}`);

        if (availableDevices.length > 0 && userQueue.length > 0) {
          console.log('🚀 Assigning waiting users to new devices...');
          assignNextUserToAvailableDevice();
        }
      }

      // Also check if we can assign existing users to available devices
      const availableDevices = getAvailableDevices();
      if (availableDevices.length > 0 && userQueue.length > 0) {
        console.log('📋 Assigning queued users to available devices...');
        assignNextUserToAvailableDevice();
      }

      res.json(connectedDevices);
    } catch (error) {
      console.error('❌ Error getting devices:', error);
      res.status(500).json({ error: 'Failed to get devices' });
    }
});

// Serve the main HTML file
app.get('/', (req, res) => {
    res.sendFile(path.join(__dirname, 'public', 'index.html'));
});

// Debug endpoint to check current state
app.get('/api/debug', (req, res) => {
    res.json({
      connectedDevices: connectedDevices,
      activeDevices: Array.from(activeDevices.entries()),
      activeUsers: Array.from(activeUsers.entries()),
      userQueue: userQueue,
      timeRemainingMap: Array.from(timeRemainingMap.entries()),
      deviceTimers: Array.from(deviceTimers.keys()),
      ipUserMap: Array.from(ipUserMap.entries()),
      availableDevices: getAvailableDevices().length
    });
});

// Start server
app.listen(PORT, async () => {
    console.log(`Android Remote Control Server running on port ${PORT}`);
    console.log(`Open http://localhost:${PORT} in your browser`);

    // Initialize devices on server start
    await initializeDevices();
});
