#!/usr/bin/env node

const { spawn } = require('child_process');
const http = require('http');
const net = require('net');
const fs = require('fs');
const path = require('path');

const TTYD_PORT = 4001;
const TTYD_HOST = '0.0.0.0';
const IDLE_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes of inactivity
const STARTUP_TIMEOUT_MS = 10000; // 10 seconds to start ttyd

let ttydProcess = null;
let lastActivityTime = Date.now();
let clientCount = 0;
let startupPromise = null;

const logFile = '/var/log/ttyd-proxy.log';

function log(message) {
  const timestamp = new Date().toISOString();
  const logLine = `[${timestamp}] ${message}\n`;
  fs.appendFileSync(logFile, logLine);
  console.log(logLine.trim());
}

function startTtyd() {
  if (startupPromise) {
    return startupPromise;
  }

  if (ttydProcess) {
    return Promise.resolve();
  }

  log('Starting ttyd process...');

  startupPromise = new Promise((resolve, reject) => {
    const args = [
      '-W',
      '-p', '4002', // Use port 4002 for the actual ttyd instance
      '-i', '0.0.0.0',
      '/usr/bin/pwsh'
    ];

    // Add password protection if ACCESS_PASSWORD is set
    if (process.env.ACCESS_PASSWORD) {
      args.splice(0, 0, '-c', `user:${process.env.ACCESS_PASSWORD}`);
    }

    ttydProcess = spawn('/usr/local/bin/ttyd', args, {
      env: process.env,
      stdio: ['ignore', 'pipe', 'pipe']
    });

    const startupTimer = setTimeout(() => {
      if (ttydProcess) {
        log('ttyd startup timeout, killing process');
        ttydProcess.kill('SIGKILL');
        ttydProcess = null;
        startupPromise = null;
        reject(new Error('ttyd startup timeout'));
      }
    }, STARTUP_TIMEOUT_MS);

    ttydProcess.stdout.on('data', (data) => {
      log(`ttyd stdout: ${data.slice(0, 200)}`);
    });

    ttydProcess.stderr.on('data', (data) => {
      const stderr = data.toString();
      if (!stderr.includes('client disconnected') && !stderr.includes('new client')) {
        log(`ttyd stderr: ${stderr.slice(0, 200)}`);
      }
    });

    ttydProcess.on('exit', (code, signal) => {
      log(`ttyd exited: code=${code}, signal=${signal}`);
      ttydProcess = null;
      startupPromise = null;
    });

    // Wait for ttyd to start listening on port 4002
    let attempts = 0;
    const checkReady = () => {
      attempts++;
      const client = net.connect(4002, '0.0.0.0', () => {
        clearTimeout(startupTimer);
        client.destroy();
        log('ttyd is ready on port 4002');
        resolve();
      });

      client.on('error', () => {
        client.destroy();
        if (attempts < 50) {
          setTimeout(checkReady, 200);
        } else {
          clearTimeout(startupTimer);
          if (ttydProcess) {
            ttydProcess.kill('SIGKILL');
            ttydProcess = null;
          }
          startupPromise = null;
          reject(new Error('ttyd failed to start'));
        }
      });
    };

    checkReady();
  });

  return startupPromise;
}

function stopTtyd() {
  if (ttydProcess) {
    log('Stopping idle ttyd process...');
    ttydProcess.kill('SIGTERM');
    setTimeout(() => {
      if (ttydProcess) {
        ttydProcess.kill('SIGKILL');
        ttydProcess = null;
      }
    }, 5000);
  }
}

function checkIdleTimeout() {
  const now = Date.now();
  const idleTime = now - lastActivityTime;

  if (clientCount === 0 && idleTime > IDLE_TIMEOUT_MS) {
    log(`ttyd idle for ${idleTime}ms, stopping...`);
    stopTtyd();
  }
}

setInterval(checkIdleTimeout, 60000); // Check every minute

const proxy = http.createServer((clientReq, clientRes) => {
  lastActivityTime = Date.now();

  // Update client count based on request type
  if (clientReq.url === '/' && clientReq.method === 'GET') {
    clientCount++;
  }

  const handleProxy = async () => {
    try {
      await startTtyd();

      const options = {
        hostname: 'localhost',
        port: 4002,
        path: clientReq.url,
        method: clientReq.method,
        headers: {
          ...clientReq.headers,
          host: 'localhost:4002'
        }
      };

      const proxyReq = http.request(options, (proxyRes) => {
        clientRes.writeHead(proxyRes.statusCode, proxyRes.headers);
        proxyRes.pipe(clientRes);

        proxyRes.on('end', () => {
          clientCount = Math.max(0, clientCount - 1);
          lastActivityTime = Date.now();
        });
      });

      proxyReq.on('error', (err) => {
        log(`Proxy request error: ${err.message}`);
        clientCount = Math.max(0, clientCount - 1);
        if (!clientRes.headersSent) {
          clientRes.writeHead(502, { 'Content-Type': 'text/plain' });
          clientRes.end('Bad Gateway: ttyd not available');
        }
      });

      clientReq.pipe(proxyReq);
    } catch (err) {
      log(`Proxy error: ${err.message}`);
      clientCount = Math.max(0, clientCount - 1);
      if (!clientRes.headersSent) {
        clientRes.writeHead(503, { 'Content-Type': 'text/plain' });
        clientRes.end('Service Unavailable: ttyd failed to start');
      }
    }
  };

  handleProxy();
});

proxy.on('upgrade', (clientReq, clientSocket, head) => {
  lastActivityTime = Date.now();
  clientCount++;

  const handleUpgrade = async () => {
    try {
      await startTtyd();

      const proxyReq = http.request({
        hostname: 'localhost',
        port: 4002,
        path: clientReq.url,
        method: clientReq.method,
        headers: {
          ...clientReq.headers,
          host: 'localhost:4002'
        }
      });

      proxyReq.on('upgrade', (proxyRes, proxySocket, proxyHead) => {
        clientSocket.write(
          'HTTP/1.1 101 Switching Protocols\r\n' +
          `Upgrade: ${proxyRes.headers.upgrade}\r\n` +
          `Connection: Upgrade\r\n` +
          Object.entries(proxyRes.headers)
            .filter(([k]) => k !== 'upgrade' && k !== 'connection')
            .map(([k, v]) => `${k}: ${v}`)
            .join('\r\n') +
          '\r\n\r\n'
        );

        proxySocket.pipe(clientSocket).pipe(proxySocket);

        proxySocket.on('close', () => {
          clientCount = Math.max(0, clientCount - 1);
          lastActivityTime = Date.now();
        });

        clientSocket.on('close', () => {
          clientCount = Math.max(0, clientCount - 1);
          lastActivityTime = Date.now();
          proxySocket.end();
        });
      });

      proxyReq.on('error', (err) => {
        log(`WebSocket proxy error: ${err.message}`);
        clientCount = Math.max(0, clientCount - 1);
        clientSocket.end();
      });

      proxyReq.end();
    } catch (err) {
      log(`WebSocket upgrade error: ${err.message}`);
      clientCount = Math.max(0, clientCount - 1);
      clientSocket.end();
    }
  };

  handleUpgrade();
});

proxy.listen(TTYD_PORT, TTYD_HOST, () => {
  log(`ttyd proxy listening on ${TTYD_HOST}:${TTYD_PORT}`);
  log(`ttyd will start on-demand after ${IDLE_TIMEOUT_MS/1000} minutes of inactivity`);
});

process.on('SIGTERM', () => {
  log('Received SIGTERM, shutting down...');
  stopTtyd();
  proxy.close(() => {
    log('Proxy server closed');
    process.exit(0);
  });
});

process.on('SIGINT', () => {
  log('Received SIGINT, shutting down...');
  stopTtyd();
  proxy.close(() => {
    log('Proxy server closed');
    process.exit(0);
  });
});
