#!/usr/bin/env node

/**
 * Automaker - Cross-Platform Development Environment Setup and Launch Script
 *
 * This script works on Windows, macOS, and Linux.
 */

import { execSync } from 'child_process';
import fs from 'fs';
import http from 'http';
import path from 'path';
import readline from 'readline';
import { fileURLToPath } from 'url';
import { createRequire } from 'module';

const require = createRequire(import.meta.url);
const treeKill = require('tree-kill');
const crossSpawn = require('cross-spawn');

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// Colors for terminal output (works on modern terminals including Windows)
const colors = {
  green: '\x1b[0;32m',
  blue: '\x1b[0;34m',
  yellow: '\x1b[1;33m',
  red: '\x1b[0;31m',
  reset: '\x1b[0m',
};

const isWindows = process.platform === 'win32';

// Track background processes for cleanup
let serverProcess = null;
let webProcess = null;
let electronProcess = null;

/**
 * Print colored output
 */
function log(message, color = 'reset') {
  console.log(`${colors[color]}${message}${colors.reset}`);
}

/**
 * Print the header banner
 */
function printHeader() {
  console.log('╔═══════════════════════════════════════════════════════╗');
  console.log('║        Automaker Development Environment              ║');
  console.log('╚═══════════════════════════════════════════════════════╝');
  console.log('');
}

/**
 * Execute a command synchronously and return stdout
 */
function execCommand(command, options = {}) {
  try {
    return execSync(command, {
      encoding: 'utf8',
      stdio: 'pipe',
      ...options,
    }).trim();
  } catch {
    return null;
  }
}

/**
 * Get process IDs using a specific port (cross-platform)
 */
function getProcessesOnPort(port) {
  const pids = new Set();

  if (isWindows) {
    // Windows: Use netstat to find PIDs
    try {
      const output = execCommand(`netstat -ano | findstr :${port}`);
      if (output) {
        const lines = output.split('\n');
        for (const line of lines) {
          // Match lines with LISTENING or ESTABLISHED on our port
          const match = line.match(/:\d+\s+.*?(\d+)\s*$/);
          if (match) {
            const pid = parseInt(match[1], 10);
            if (pid > 0) pids.add(pid);
          }
        }
      }
    } catch {
      // Ignore errors
    }
  } else {
    // Unix: Use lsof
    try {
      const output = execCommand(`lsof -ti:${port}`);
      if (output) {
        output.split('\n').forEach((pid) => {
          const parsed = parseInt(pid.trim(), 10);
          if (parsed > 0) pids.add(parsed);
        });
      }
    } catch {
      // Ignore errors
    }
  }

  return Array.from(pids);
}

/**
 * Kill a process by PID (cross-platform)
 */
function killProcess(pid) {
  try {
    if (isWindows) {
      execCommand(`taskkill /F /PID ${pid}`);
    } else {
      process.kill(pid, 'SIGKILL');
    }
    return true;
  } catch {
    return false;
  }
}

/**
 * Kill processes on a port and wait for it to be freed
 */
async function killPort(port) {
  const pids = getProcessesOnPort(port);

  if (pids.length === 0) {
    log(`✓ Port ${port} is available`, 'green');
    return true;
  }

  log(`Killing process(es) on port ${port}: ${pids.join(', ')}`, 'yellow');

  for (const pid of pids) {
    killProcess(pid);
  }

  // Wait for port to be freed (max 5 seconds)
  for (let i = 0; i < 10; i++) {
    await sleep(500);
    const remainingPids = getProcessesOnPort(port);
    if (remainingPids.length === 0) {
      log(`✓ Port ${port} is now free`, 'green');
      return true;
    }
  }

  log(`Warning: Port ${port} may still be in use`, 'red');
  return false;
}

/**
 * Sleep for a given number of milliseconds
 */
function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

/**
 * Check if the server health endpoint is responding
 */
function checkHealth() {
  return new Promise((resolve) => {
    const req = http.get('http://localhost:3008/api/health', (res) => {
      resolve(res.statusCode === 200);
    });
    req.on('error', () => resolve(false));
    req.setTimeout(2000, () => {
      req.destroy();
      resolve(false);
    });
  });
}

/**
 * Prompt the user for input
 */
function prompt(question) {
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
  });

  return new Promise((resolve) => {
    rl.question(question, (answer) => {
      rl.close();
      resolve(answer.trim());
    });
  });
}

/**
 * Run npm command using cross-spawn for Windows compatibility
 */
function runNpm(args, options = {}) {
  const spawnOptions = {
    stdio: 'inherit',
    cwd: __dirname,
    ...options,
  };
  // cross-spawn handles Windows .cmd files automatically
  return crossSpawn('npm', args, spawnOptions);
}

/**
 * Run npx command using cross-spawn for Windows compatibility
 */
function runNpx(args, options = {}) {
  const spawnOptions = {
    stdio: 'inherit',
    cwd: __dirname,
    ...options,
  };
  // cross-spawn handles Windows .cmd files automatically
  return crossSpawn('npx', args, spawnOptions);
}

/**
 * Kill a process tree using tree-kill
 */
function killProcessTree(pid) {
  return new Promise((resolve) => {
    if (!pid) {
      resolve();
      return;
    }
    treeKill(pid, 'SIGTERM', (err) => {
      if (err) {
        // Try force kill if graceful termination fails
        treeKill(pid, 'SIGKILL', () => resolve());
      } else {
        resolve();
      }
    });
  });
}

/**
 * Cleanup function to kill all spawned processes
 */
async function cleanup() {
  console.log('\nCleaning up...');

  const killPromises = [];

  if (serverProcess && !serverProcess.killed && serverProcess.pid) {
    killPromises.push(killProcessTree(serverProcess.pid));
  }

  if (webProcess && !webProcess.killed && webProcess.pid) {
    killPromises.push(killProcessTree(webProcess.pid));
  }

  if (electronProcess && !electronProcess.killed && electronProcess.pid) {
    killPromises.push(killProcessTree(electronProcess.pid));
  }

  await Promise.all(killPromises);
}

/**
 * Main function
 */
async function main() {
  // Change to script directory
  process.chdir(__dirname);

  printHeader();

  // Check if node_modules exists
  if (!fs.existsSync(path.join(__dirname, 'node_modules'))) {
    log('Installing dependencies...', 'blue');
    const install = runNpm(['install'], { stdio: 'inherit' });
    await new Promise((resolve, reject) => {
      install.on('close', (code) => {
        if (code === 0) resolve();
        else reject(new Error(`npm install failed with code ${code}`));
      });
    });
  }

  // Install Playwright browsers from apps/ui where @playwright/test is installed
  log('Checking Playwright browsers...', 'yellow');
  try {
    const exitCode = await new Promise((resolve) => {
      const playwright = crossSpawn('npx', ['playwright', 'install', 'chromium'], {
        stdio: 'inherit',
        cwd: path.join(__dirname, 'apps', 'ui'),
      });
      playwright.on('close', (code) => resolve(code));
      playwright.on('error', () => resolve(1));
    });

    if (exitCode === 0) {
      log('Playwright browsers ready', 'green');
    } else {
      log('Playwright installation failed (browser automation may not work)', 'yellow');
    }
  } catch {
    log('Playwright installation skipped', 'yellow');
  }

  // Kill any existing processes on required ports
  log('Checking for processes on ports 3007 and 3008...', 'yellow');
  await killPort(3007);
  await killPort(3008);
  console.log('');

  // Show menu
  console.log('═══════════════════════════════════════════════════════');
  console.log('  Select Application Mode:');
  console.log('═══════════════════════════════════════════════════════');
  console.log('  1) Web Application (Browser)');
  console.log('  2) Desktop Application (Electron)');
  console.log('═══════════════════════════════════════════════════════');
  console.log('');

  // Setup cleanup handlers
  let cleaningUp = false;
  const handleExit = async (signal) => {
    if (cleaningUp) return;
    cleaningUp = true;
    await cleanup();
    process.exit(0);
  };

  process.on('SIGINT', () => handleExit('SIGINT'));
  process.on('SIGTERM', () => handleExit('SIGTERM'));

  // Prompt for choice
  while (true) {
    const choice = await prompt('Enter your choice (1 or 2): ');

    if (choice === '1') {
      console.log('');
      log('Launching Web Application...', 'blue');

      // Start the backend server
      log('Starting backend server on port 3008...', 'blue');

      // Create logs directory
      if (!fs.existsSync(path.join(__dirname, 'logs'))) {
        fs.mkdirSync(path.join(__dirname, 'logs'), { recursive: true });
      }

      // Start server in background
      const logStream = fs.createWriteStream(path.join(__dirname, 'logs', 'server.log'));
      serverProcess = runNpm(['run', 'dev:server'], {
        stdio: ['ignore', 'pipe', 'pipe'],
      });

      serverProcess.stdout?.pipe(logStream);
      serverProcess.stderr?.pipe(logStream);

      log('Waiting for server to be ready...', 'yellow');

      // Wait for server health check
      const maxRetries = 30;
      let serverReady = false;

      for (let i = 0; i < maxRetries; i++) {
        if (await checkHealth()) {
          serverReady = true;
          break;
        }
        process.stdout.write('.');
        await sleep(1000);
      }

      console.log('');

      if (!serverReady) {
        log('Error: Server failed to start', 'red');
        console.log('Check logs/server.log for details');
        cleanup();
        process.exit(1);
      }

      log('✓ Server is ready!', 'green');
      log(`The application will be available at: http://localhost:3007`, 'green');
      console.log('');

      // Start web app
      webProcess = runNpm(['run', 'dev:web'], { stdio: 'inherit' });
      await new Promise((resolve) => {
        webProcess.on('close', resolve);
      });

      break;
    } else if (choice === '2') {
      console.log('');
      log('Launching Desktop Application...', 'blue');
      log('(Electron will start its own backend server)', 'yellow');
      console.log('');

      electronProcess = runNpm(['run', 'dev:electron'], { stdio: 'inherit' });
      await new Promise((resolve) => {
        electronProcess.on('close', resolve);
      });

      break;
    } else {
      log('Invalid choice. Please enter 1 or 2.', 'red');
    }
  }
}

// Run main function
main().catch((err) => {
  console.error(err);
  cleanup();
  process.exit(1);
});
