'use strict';
const { ServerlessSDK } = require('@serverless/platform-client');
const chokidar = require('chokidar'); // todo move to FS

const FS = require('../models/FS');
const User = require('../models/User');
const Org = require('../models/Org');
const API = require('../models/API');
const Service = require('../models/Service');
const Instance = require('../models/Instance');
const Code = require('../models/Code');
const Data = require('../models/Data');

module.exports = async (config, cli) => {
  cli.logLogo();

  const fs = new FS();
  const user = new User();
  const api = new API();

  let isFirstLog = true;

  if (await fs.isCwdEmpty()) {
    await fs.generateTemplate();

    if (isFirstLog) {
      cli.log();
      isFirstLog = false;
    }

    cli.logSuccess(`created local files for instance ${cli.styles.bold(user.username)}`);
  }

  cli.sessionStart('deploying', { timer: true });

  if (!(await fs.isProjectDirectory())) {
    throw new Error('package.json not found in the cwd');
  }

  const org = new Org({ user }); // todo pass org name
  await org.authorize();

  if (!org.accessKey) {
    // not logged in
    throw new Error('please login by running "sc login".');
  }

  if (!org.orgName) {
    // this could happen if using access key env var
    // without setting org in package.json
    throw new Error('please add an "org" property in package.json');
  }

  api.authorize(org);

  const service = new Service({ api, org });

  const instance = new Instance({
    fs,
    api,
    org,
    service,
    instanceName: user.username,
  });

  const code = new Code({
    fs,
    api,
    org,
    service,
    instance,
    isForce: config.force,
  });

  const data = new Data({
    fs,
    api,
    org,
    service,
    instance,
  });

  const sdk = new ServerlessSDK({
    platformStage: 'dev',
    accessKey: org.accessKey,
    context: {
      orgName: org.orgName,
    },
  });

  let isLogsSectionFirstLog = true;

  await code.reload();

  const onEvent = (event) => {
    if (isLogsSectionFirstLog) {
      cli.log();
      isLogsSectionFirstLog = false;
    }
    // Logs
    if (event.event === 'instance.logs') {
      if (event.data.logs && Array.isArray(event.data.logs)) {
        event.data.logs.forEach((log) => {
          // Remove strange formatting that comes from stderr
          if (typeof log.data === 'string' && log.data.startsWith("'")) {
            log.data = log.data.substr(1);
          }
          if (typeof log.data === 'string' && log.data.endsWith("'")) {
            log.data = log.data.substring(0, log.data.length - 1);
          }
          if (typeof log.data === 'string' && log.data.endsWith('\\n')) {
            log.data = log.data.substring(0, log.data.length - 2);
          }

          if (log.type === 'log' || log.type === 'stdout') {
            cli.log(log.data);
          }
          if (log.type === 'debug' || log.type === 'info' || log.type === 'stderr') {
            cli.log(log.data);
          }
          if (log.type === 'warn') {
            cli.log(log.data, 'grey');
          }
          if (log.type === 'error') {
            cli.log(log.data, 'red');
          }
        });
      }
    }
  };

  // Filter configuration
  const filter = {
    appName: service.serviceName,
    instanceName: instance.instanceName,
    events: [],
  };

  // Establish connection with Serverless Platform
  // todo after websocket connection, any errors does not exit cli session
  // we probably need to manually disconnect from the socket on errors
  try {
    await sdk.connect({
      org: org.orgName,
      filter,
      onEvent,
    });
  } catch (error) {
    if (error.message.includes('401')) {
      cli.sessionStop(
        `Your credentials do not have access to the Organization with the name of: ${org.orgName}.  Try logging into a different account.`,
        'error'
      );
      return null;
    }
    throw new Error(error);
  }

  if (isFirstLog) {
    cli.log();
    isFirstLog = false;
  }

  cli.logSuccess(`deployed instance ${cli.styles.bold(user.username)} successfully`);
  cli.logSuccess('enabled automatic deployments and log streaming'); // auto deployment enabled

  cli.log();
  cli.logUrl(instance.url);

  cli.sessionStatus('watching', { timer: false });

  const watcher = chokidar.watch(fs.projectRoot);

  const reseed = async () => {
    cli.sessionStatus('reseeding');

    await data.seed();

    cli.sessionStatus('watching');
  };

  let isProcessing = false; // whether there's already a deployment in progress
  let queuedOperation = false; // whether there's another deployment queued

  watcher.on('raw', async (event, filePath) => {
    if (filePath === fs.dataFilePath) {
      await reseed();
      return;
    }
    // Skip if processing already and there is a queued operation
    if (isProcessing && queuedOperation) {
      return;
    }

    // If already deploying and user made more changes, queue another deploy operation to be run after the first one
    if (isProcessing && !queuedOperation) {
      queuedOperation = true;
      return;
    }

    const reloadingMessage = 'deploying';

    // If it's not processing and there is no queued operation
    if (!isProcessing) {
      isProcessing = true;
      cli.sessionStatus(reloadingMessage, { timer: true });

      await code.reload();

      cli.tempStatus({ status: 'deployed' });
      cli.sessionStatus('watching', { timer: false });

      if (queuedOperation) {
        cli.sessionStatus(reloadingMessage, { timer: true });
        await code.reload();

        cli.tempStatus({ status: 'deployed' });
        cli.sessionStatus('watching', { timer: false });
      }

      isProcessing = false;
      queuedOperation = false;
    }
  });
};
