import { uploadFile } from '@squiz/virus-scanner-lib';

import { zipDirectory } from '@squiz/dx-common-lib';
import { ManifestServiceForDev } from '@squiz/component-lib';
import fsp from 'fs/promises';
import path from 'path';
import { AxiosInstance } from 'axios';
import color from 'cli-color';
import { getLogger, Logger } from '@squiz/dx-logger-lib';
import {
  watchAndWaitForUploadAndScanComplete,
  checkIfVersionExists,
  handleResponse,
  isAxiosResponse,
  isUserInOrganization,
} from './utils';

export const logger: Logger = getLogger({ name: 'upload-component', format: 'human' });

export async function uploadComponentFolder(
  apiClient: AxiosInstance,
  componentServiceManagementUrl: string,
  folderPath: string,
  baseTempDir: string = '',
): Promise<void> {
  const tmpDir = await fsp.mkdtemp(path.resolve(baseTempDir, 'cmp-upload'));

  try {
    const manifestService = new ManifestServiceForDev(folderPath, logger);
    const manifestPath = path.join(folderPath, `manifest.json`);
    const manifest = await manifestService.readManifest(manifestPath);
    const shouldValidateNamespace = !isUserInOrganization();
    await manifestService.assertManifestIsValid(manifestPath, manifest.getModel(), {
      validateNamespace: shouldValidateNamespace,
    });

    if (
      await checkIfVersionExists(
        apiClient,
        `/v1/component/${manifest.getName()}/${manifest.getVersion()}`,
        componentServiceManagementUrl,
      )
    ) {
      throw new Error(`Cannot upload component version, ${manifest.getName()} ${manifest.getVersion()} already exists`);
    }
    logger.info('Initial scanning');
    const zip = await zipDirectory(folderPath, tmpDir);

    const initialUpload = await handleResponse<any>(
      apiClient.post('/v1/upload-component', {}, { baseURL: componentServiceManagementUrl }),
    );

    logger.info(`deployment id: ${initialUpload.id} status: transferring`);
    await uploadFile(initialUpload, zip);

    await watchAndWaitForUploadAndScanComplete(
      apiClient,
      '/v1/upload-component/status/',
      initialUpload.id,
      componentServiceManagementUrl,
    );

    logger.info(`deployment id: ${initialUpload.id} status: deploying component folder`);

    //Rarely this call will return 504, while successfully uploading the component - the cause is currently unknown
    let result = await handleResponse<any>(
      apiClient.post(`/v1/upload-component/next/${initialUpload.id}`, {}, { baseURL: componentServiceManagementUrl }),
    );

    if (!isAxiosResponse(result)) {
      let retriesAllowed = 12;
      while (retriesAllowed > 0) {
        logger.info(
          `deployment id: ${initialUpload.id} status: ${color.yellow(
            `unknown, retrying to check the upload status ${retriesAllowed} more times`,
          )}`,
        );
        if (
          await checkIfVersionExists(
            apiClient,
            `/v1/component/${manifest.getName()}/${manifest.getVersion()}`,
            componentServiceManagementUrl,
          )
        ) {
          result = {
            status: 'successful',
            accessLink: 'unknown',
          };
          break;
        }
        await new Promise((r) => setTimeout(r, 10000));
        retriesAllowed--;
      }
      if (retriesAllowed === 0) {
        logger.error(`deployment id: ${initialUpload.id} status: ${color.red('failed to check the upload status')}`);
        throw new Error(result.toString());
      }
    }

    await fsp.rm(tmpDir, { force: true, recursive: true }).catch((error: any) => {
      logger.error('failed to remove temp directory', error);
    });

    if (result.status === 'successful') {
      logger.info(`deployment id: ${initialUpload.id} status: ${color.green('success')}`);
      logger.info(`uploaded location: ${result.accessLink}`);
    } else {
      const message = result?.message ?? 'unknown';
      logger.error(`failed due an unexpected reason: ${message}`);
      await fsp.rm(tmpDir, { force: true, recursive: true }).catch((error: any) => {
        logger.error('failed to remove temp directory', error);
      });
    }
  } catch (e) {
    await fsp.rm(tmpDir, { force: true, recursive: true }).catch((error: any) => {
      logger.error('failed to remove temp directory', error);
    });
    throw e;
  }
}
