const path = require('path');
const fs = require('fs');
const os = require('os');
const debug = require('../server/node_modules/debug')('oasis');
const lodash = require('../server/node_modules/lodash');
const ssbClient = require('../server/node_modules/ssb-client');
const ssbConfig = require('../server/node_modules/ssb-config');
const ssbKeys = require('../server/node_modules/ssb-keys');
const { printMetadata } = require('../server/ssb_metadata');
const updateFlagPath = path.join(__dirname, "../server/.update_required");

let internalSSB = null;
try {
  const { server } = require('../server/SSB_server');
  internalSSB = server;
} catch {}

if (process.env.OASIS_TEST) {
  ssbConfig.path = fs.mkdtempSync(path.join(os.tmpdir(), "oasis-"));
  ssbConfig.keys = ssbKeys.generate();
}

const socketPath = path.join(ssbConfig.path, "socket");
const publicInteger = ssbConfig.keys.public.replace(".ed25519", "");
const remote = `unix:${socketPath}~noauth:${publicInteger}`;

const connect = (options) =>
  new Promise((resolve, reject) => {
    ssbClient(process.env.OASIS_TEST ? ssbConfig.keys : null, options)
      .then(resolve)
      .catch(reject);
  });

let closing = false;
let clientHandle;

const attemptConnectionWithBackoff = (attempt = 1) => {
  const maxAttempts = 5;
  const delay = Math.min(1000 * Math.pow(2, attempt), 10000);

  return new Promise((resolve, reject) => {
    connect({ remote })
      .then(resolve)
      .catch((error) => {
        if (attempt >= maxAttempts) {
          return reject(new Error("Failed to connect after multiple attempts"));
        }
        setTimeout(() => {
          attemptConnectionWithBackoff(attempt + 1).then(resolve).catch(reject);
        }, delay);
      });
  });
};

let pendingConnection = null;

const ensureConnection = (customConfig) => {
  if (pendingConnection === null) {
    pendingConnection = new Promise((resolve) => {
      setTimeout(() => {
        attemptConnectionWithBackoff()
          .then(resolve)
          .catch(() => {
            resolve(null);
          });
      });
    });

    const cancel = () => (pendingConnection = null);
    pendingConnection.then(cancel, cancel);
  }

  return pendingConnection;
};

module.exports = ({ offline, port = 3000, host = 'localhost', isPublic = false }) => {
  const customConfig = JSON.parse(JSON.stringify(ssbConfig));
  if (offline === true) {
    lodash.set(customConfig, "conn.autostart", false);
  }
  lodash.set(
    customConfig,
    "conn.hops",
    lodash.get(ssbConfig, "conn.hops", lodash.get(ssbConfig.friends, "hops", 0))
  );

  const cooler = {
    open() {
      return new Promise((resolve, reject) => {
        if (internalSSB) {
          const { printMetadata, colors } = require('../server/ssb_metadata');
          printMetadata('OASIS GUI', colors.yellow, port, host, offline, isPublic);
          return resolve(internalSSB);
        }

        if (clientHandle && clientHandle.closed === false) {
          return resolve(clientHandle);
        }

        ensureConnection(customConfig).then((ssb) => {
          if (!ssb) return reject(new Error("No SSB server available"));
          clientHandle = ssb;
          if (closing) {
            cooler.close();
            reject(new Error("Closing Oasis"));
          } else {
            const { printMetadata, colors } = require('../server/ssb_metadata');
            printMetadata('OASIS GUI', colors.yellow, port, host, offline, isPublic);
            resolve(ssb);
          }
        }).catch(reject);
      });
    },

    close() {
      closing = true;
      if (clientHandle && clientHandle.closed === false) {
        clientHandle.close();
      }
    },
  };

  return cooler;
};
