const { createSpinner } = require('nanospinner');
const yaml = require('js-yaml');
const fs = require('fs');
const path = require('path');

const print = require('../../helpers/print');

const createZip = require('./create-zip');
const uploadZip = require('./upload-zip');
const deployRelease = require('./deploy-release');
const assumeRole = require('./assume-role');
const getProductInfo = require('./get-product');
const deployWebapp = require('./deploy-webapp');
const updateProduct = require('./update-product');
const updateContents = require('./update-contents');
const getFieldDefinition = require('./get-field-definition');
const updateSubProduct = require('./update-sub-products');
const updateIntegration = require('./update-integration');

/**
 * @typedef CommandFlagsDeploy
 * @property {string=} plugin
 * @property {string=} host
 * @property {string=} version
 * @property {boolean=} fail-on-existing-version
 */

/**
 * @typedef KaskoPluginConfigV1
 * @property {string} id
 * @property {string} build_location
 * @property {string=} account_id
 */

/**
 * @typedef KaskoConfigV1
 * @property {1} version
 * @property {Record<string, KaskoPluginConfigV1>} plugins
 */

/**
 * @typedef KaskoPluginConfigV2
 * @property {string} build_location
 * @property {string} contents_location
 * @property {string} product_id
 * @property {string=} account_id
 */

/**
 * @typedef KaskoConfigV2
 * @property {2} version
 * @property {Record<string, KaskoPluginConfigV2>} plugins
 */

/**
 * @typedef KaskoConfig
 * @type {KaskoConfigV1 | KaskoConfigV2}
 */

/**
 * @param {CommandFlagsDeploy} flags
 * @param {KaskoConfigV1} config
 * @returns {Promise<void>}
 */
async function deployV1(
  // rome-ignore lint/style/useDefaultParameterLast: <explanation>
  {
    plugin: name,
    host = 'eu1.kaskocloud.com',
    version,
    'fail-on-existing-version': failOnExistingVersion = false,
  } = {},
  config,
) {
  const plugins = Object.keys(config.plugins).map((pluginName) => ({
    ...config.plugins[pluginName],
    name: pluginName,
  }));
  const filteredPlugins = name
    ? plugins.filter((pluginConfig) => pluginConfig.name === name)
    : plugins;

  filteredPlugins.forEach((plugin) => {
    if (!fs.existsSync(path.join(plugin.build_location, '/.kasko/config.json'))) {
      throw new Error(`.kasko/config.json file not found for plugin ${plugin.name}`);
    }
  });

  for (const plugin of filteredPlugins) {
    const spinner = createSpinner(`Deploying ${plugin.name}`).start();
    let fileId;

    try {
      let token = process.env.KASKO_PRIVATE_KEY;

      if (plugin.account_id) {
        token = await assumeRole(host, plugin.account_id, token);
      }

      const file = await createZip(plugin.build_location);
      fileId = await uploadZip(host, file, token);
      await deployRelease(host, plugin.id, fileId, token, version);
      spinner.success({ text: `Deployed ${plugin.name}` });

      if (fileId) {
        print(`- Uploaded zip id: ${fileId}`);
      }
    } catch (e) {
      spinner.error({ text: `Failed deploying ${plugin.name}` });

      if (fileId) {
        print(`- Uploaded zip id: ${fileId}`);
      }

      if (!failOnExistingVersion && e.response && e.response.status === 409) {
        print(`Skipping __${plugin.name}__. ${e.response.data.message}`);
        // Adds space between skipped plugin and next one
        print('');
        continue;
      }

      if (e.response?.request) {
        print(`  ${e.response.status} ${e.response.request.method} ${e.response.request.path}`);
      }

      print(`  ${JSON.stringify(e.response ? e.response.data : e)}`);

      process.exit(1);
    }
  }
}

/**
 * @param {CommandFlagsDeploy} flags
 * @param {KaskoConfigV2} config
 * @returns {Promise<void>}
 */
async function deployV2(
  // rome-ignore lint/style/useDefaultParameterLast: <explanation>
  {
    plugin: name,
    host = 'eu1.kaskocloud.com',
    'fail-on-existing-version': failOnExistingVersion = false,
  } = {},
  config,
) {
  const plugins = Object.keys(config.plugins).map((pluginName) => ({
    ...config.plugins[pluginName],
    name: pluginName,
  }));
  const filteredPlugins = name
    ? plugins.filter((pluginConfig) => pluginConfig.name === name)
    : plugins;

  filteredPlugins.forEach((plugin) => {
    const buildLocation = path.join(plugin.build_location, 'products', plugin.product_id, 'webapp');

    if (!fs.existsSync(path.join(buildLocation, '/.kasko/config.json'))) {
      throw new Error(`.kasko/config.json file not found for plugin ${plugin.name}`);
    }
  });

  for (const plugin of filteredPlugins) {
    const buildLocation = path.join(plugin.build_location, 'products', plugin.product_id, 'webapp');
    const spinner = createSpinner(`Deploying ${plugin.name}`).start();
    let fileId;

    try {
      let token = process.env.KASKO_PRIVATE_KEY;

      if (plugin.account_id) {
        token = await assumeRole(host, plugin.account_id, token);
      }

      const product = await getProductInfo(host, plugin.product_id, token);

      const manifestsLocation = path.join(buildLocation, 'manifests');
      const fieldDefinitionLocation = path.join(manifestsLocation, 'field_definition.json');
      const fieldDefinition = await getFieldDefinition(fieldDefinitionLocation);

      if (!product.data.has_subproducts) {
        const contentsLocation = path.join(
          plugin.build_location,
          'products',
          plugin.product_id,
          'contents.json',
        );
        await updateContents(host, plugin.product_id, undefined, contentsLocation, token);
      }

      const initialWebappId = product.data.webapp_id;
      const file = await createZip(buildLocation);
      fileId = await uploadZip(host, file, token);

      const webapp = await deployWebapp(host, initialWebappId, fileId, token);
      const webappId = webapp.data.id;
      const webappRevisionId = webapp.data.revision_id;

      const subProductsLocation = path.join(manifestsLocation, 'sub_products');
      const subProducts = fs
        .readdirSync(subProductsLocation, 'utf-8')
        .filter((filePath) => filePath.startsWith('spr_'));

      for (const subProductId of subProducts) {
        const subProductPath = path.join(subProductsLocation, subProductId);

        await updateContents(
          host,
          plugin.product_id,
          subProductId,
          path.join(subProductPath, 'contents.json'),
          token,
        );

        const subProductFieldDefinition = await getFieldDefinition(
          path.join(subProductPath, 'field_definition.json'),
        );

        if (!subProductFieldDefinition) {
          continue;
        }

        await updateSubProduct(
          host,
          plugin.product_id,
          subProductId,
          {
            field_definition: subProductFieldDefinition,
          },
          token,
        );
      }

      const integrationsLocation = path.join(
        plugin.build_location,
        'products',
        plugin.product_id,
        'integrations',
      );
      const integrations = fs
        .readdirSync(integrationsLocation, 'utf-8')
        .filter((filePath) => filePath.startsWith('in_'));

      for (const integrationFilename of integrations) {
        const integrationId = integrationFilename.replace(/\.json$/, '');
        const integrationPath = path.join(integrationsLocation, integrationFilename);

        await updateIntegration(host, plugin.product_id, integrationId, integrationPath, token);
      }

      await updateProduct(
        host,
        plugin.product_id,
        {
          webapp_id: webappId,
          webapp_revision_id: webappRevisionId,
          field_definition: fieldDefinition,
        },
        token,
      );

      spinner.success({ text: `Deployed ${plugin.name}` });

      if (fileId) {
        print(`- Uploaded zip id: ${fileId}`);
      }
    } catch (e) {
      spinner.error({ text: `Failed deploying ${plugin.name}` });

      if (fileId) {
        print(`- Uploaded zip id: ${fileId}`);
      }

      if (!failOnExistingVersion && e.response && e.response.status === 409) {
        print(`Skipping __${plugin.name}__. ${e.response.data.message}`);
        // Adds space between skipped plugin and next one
        print('');
        continue;
      }

      if (e.response?.request) {
        print(`  ${e.response.status} ${e.response.request.method} ${e.response.request.path}`);
      }

      print(`  ${JSON.stringify(e.response ? e.response.data : e)}`);

      process.exit(1);
    }
  }
}

/**
 * @param {CommandFlagsDeploy} flags
 * @returns {void}
 */
function deploy(flags = {}) {
  if (!process.env.KASKO_PRIVATE_KEY) {
    throw new Error('KASKO_PRIVATE_KEY environment variable must be set');
  }

  if (!fs.existsSync('kasko.yaml')) {
    throw new Error('kasko.yaml file not found');
  }

  /** @type {KaskoConfig} */
  const config = yaml.load(fs.readFileSync('kasko.yaml', 'utf8'));

  switch (config.version) {
    case 1: {
      deployV1(flags, config);
      break;
    }

    case 2: {
      deployV2(flags, config);
      break;
    }

    default:
      break;
  }
}

module.exports = deploy;
module.exports.readme = require('./readme.md');
