const crypto = require('crypto');
const path = require('path');
const {promisify} = require('util');
const child_process = require('child_process');
const {spawn} = child_process;
const execFile = promisify(child_process.execFile);
const realpath = promisify(require('fs').realpath);
const timeout = promisify(setTimeout);

const Koa = require('koa');
const proxy = require('koa-proxy');
const nssocket = require('nssocket');
const path_is_inside = require('path-is-inside');
const Dockerfile = require('dockerfilejs').Dockerfile;
const resolve = promisify(require('resolve'));
const StdBuffer = require('./stdbuffer');
const buildAppImage = require('./build-image');
const log = require('@cardstack/plugin-utils/logger')('hub/spawn-hub');
const {waitForExit} = require('../util/process');

const HUB_HEARTBEAT_INTERVAL = 1 * 1000;

module.exports = async function(projectRoot) {
  await waitForExit(buildAppImage());
  let logs = await spawnHubContainer(projectRoot);

  let hub;
  try {
    hub = await socketToHub();
  } catch (e) {
    if (e.code === "ECONNREFUSED") {
      log.error("The hub container failed to start:");
      log.error(logs.err);
      throw new Error('Hub failed to start');
    } else {
      throw e;
    }
  }

  await new Promise(function(resolve) {
    hub.data('ready', resolve);
  });

  log.info('Ready message received from hub container');

  startHeartbeat(hub);

  let app = new Koa();
  app.use(proxy({
    host: 'http://localhost:3000'
  }));
  return app.callback();
};


async function linkedHubPath(projectRoot) {
  let hubPath = path.dirname(await resolve('@cardstack/hub/package.json', {basedir: projectRoot}));
  hubPath = await realpath(hubPath);

  // If hub is coming from outside our node_modules, we're linked to it,
  // so we should bind it in the container as well
  if (!path_is_inside(hubPath, projectRoot)) {
    return hubPath;
  } else {
    return false;
  }
}

async function socketToHub() {
  let hub = new nssocket.NsSocket();
  hub.connect(6785);

  return new Promise(function(resolve, reject) {
    log.trace("Attempting to connect to the hub's heartbeat port");

    async function onClose() {
      await timeout(50);
      resolve(socketToHub());
    }

    hub.on('close', onClose);
    hub.data('shake', function() {
      log.trace("Hub heartbeat connection established");
      hub.removeListener('close', onClose);
      resolve(hub);
    });
    hub.on('error', reject);
    hub.send('hand');
  });
}

// Spawns the hub container, and returns an object for getting it stdio
async function spawnHubContainer(projectRoot) {
  let hubPath = await linkedHubPath(projectRoot);

  let hubBinding;
  if (hubPath) {
    log.info('Binding locally linked hub: '+hubPath);
    hubBinding = [
      '--mount', `type=bind,src=${hubPath},dst=/hub/app/node_modules/@cardstack/hub`
    ];
  } else {
    hubBinding = [];
  }

  let key = crypto.randomBytes(32).toString('base64');


  let {stdout} = await execFile('docker', [
    'run',
    '-d',
    '--rm',
    '--publish', '3000:3000',
    '--publish', '6785:6785',
    '--mount', 'type=bind,src=/var/run/docker.sock,dst=/var/run/docker.sock',
    ...hubBinding,
    '-e', `CARDSTACK_SESSIONS_KEY=${key}`,
    'cardstack-app'
  ]);

  let container_id = stdout.trim();

  return new StdBuffer(spawn('docker', ['logs', '-f', container_id]));
}

function startHeartbeat(hub) {
  let beat = function() {
    log.trace('Sending heartbeat to hub container');
    hub.send('heartbeat');
  };
  beat();
  setInterval(beat, HUB_HEARTBEAT_INTERVAL);
}

@@ -473,63 +473,8 @@
 ');%0A
-const Dockerfile = require('dockerfilejs').Dockerfile;%0A
 cons
@@ -727,16 +727,74 @@
 ocess');
+%0Aconst crawlPackages = require('./crawl-module-linkages');
 %0A%0Aconst 
@@ -876,16 +876,67 @@
 Root) %7B%0A
+  let packages = await crawlPackages(projectRoot);%0A
   await 
@@ -961,20 +961,29 @@
 ppImage(
+packages
 ));%0A
+%0A
   let lo
@@ -1573,417 +1573,8 @@
 ;%0A%0A%0A
-async function linkedHubPath(projectRoot) %7B%0A  let hubPath = path.dirname(await resolve('@cardstack/hub/package.json', %7Bbasedir: projectRoot%7D));%0A  hubPath = await realpath(hubPath);%0A%0A  // If hub is coming from outside our node_modules, we're linked to it,%0A  // so we should bind it in the container as well%0A  if (!path_is_inside(hubPath, projectRoot)) %7B%0A    return hubPath;%0A  %7D else %7B%0A    return false;%0A  %7D%0A%7D%0A%0A
 asyn
@@ -2193,350 +2193,111 @@
 g it
+s
  stdio%0A
-async function spawnHubContainer(projectRoot) %7B%0A  let hubPath = await linkedHubPath(projectRoot);%0A%0A  let hubBinding;%0A  if (hubPath) %7B%0A    log.info('Binding locally linked hub: '+hubPath);%0A    hubBinding = %5B%0A      '--mount', %60type=bind,src=$%7BhubPath%7D,dst=/hub/app/node_modules/@cardstack/hub%60%0A    %5D;%0A  %7D else %7B%0A    hubBinding = %5B%5D;%0A  %7D%0A
+// We should, later, live bind code in as well.%0Aasync function spawnHubContainer(projectRoot) %7B
 %0A  l
@@ -2569,27 +2569,8 @@
 k',%0A
-    ...hubBinding,%0A
     

Fix `ember serve` to use new docker building