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

import { zipDirectory } from '@squiz/dx-common-lib';
import { ManifestServiceForDev, JobManifestV1Model } 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, handleResponse, checkIfVersionExists, isAxiosResponse } from './utils';

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

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

  try {
    const manifest = await getAndValidateManifest(apiClient, folderPath);

    const initialUpload = await initializeUpload(apiClient, folderPath, tmpDir);

    await uploadFile(initialUpload.upload, initialUpload.zip);

    await watchAndWaitForUploadAndScanComplete(apiClient, '/upload-job/status/', initialUpload.upload.id);

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

    const result = await pollForResult(apiClient, initialUpload.upload.id, manifest.getModel());

    await cleanupTmpDir(tmpDir);

    if (result.status === 'successful') {
      logger.info(`deployment id: ${initialUpload.upload.id} status: ${color.green('success')}`);
    } else {
      const message = result?.message ?? 'unknown';
      logger.error(`failed due an unexpected reason: ${message}`);
      await cleanupTmpDir(tmpDir);
    }
  } catch (e) {
    await cleanupTmpDir(tmpDir);
    throw e;
  }
}

async function initializeUpload(apiClient: AxiosInstance, folderPath: string, tmpDir: string) {
  logger.info('Initial scanning');
  const zip = await zipDirectory(folderPath, tmpDir);
  const initialUpload = { zip: zip, upload: await handleResponse<any>(apiClient.post('/upload-job', {})) };
  logger.info(`deployment id: ${initialUpload.upload.id} status: transferring`);
  return initialUpload;
}

async function getAndValidateManifest(apiClient: AxiosInstance, folderPath: string) {
  const manifestService = new ManifestServiceForDev(folderPath, logger);
  const manifestPath = path.join(folderPath, `manifest.json`);

  const manifest = await manifestService.readJobManifest(manifestPath);

  if (await checkIfVersionExists(apiClient, `/job/${manifest.getName()}/${manifest.getVersion()}`)) {
    throw new Error(`Cannot upload job version, ${manifest.getName()} ${manifest.getVersion()} already exists`);
  }
  return manifest;
}

async function pollForResult(apiClient: AxiosInstance, uploadId: string, manifest: JobManifestV1Model) {
  let result = await handleResponse<any>(apiClient.post(`/upload-job/next`, { id: uploadId, manifest: manifest }));

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

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