import unzip from 'unzipper';
// import fetch from 'node-fetch';
import fs from 'fs-extra';
import path from 'path';
import { remote } from 'electron';
import { exec } from 'child_process';

import { Readable } from 'stream';
import { modsText } from '../helpers/static-text';
import { debugConsole } from '../helpers/console-log';

// Defines which portion of the loading bar is for download progress,
// and the remaining is for unzipping progress.
const progressDownloadPortion = 0.7;
const progressUnzipPortion = 0.3;

export async function downloadFile(
  url: string,
  filePath: string,
  onProgress: ProgressHandler
) {
  let receivedBytes = 0;

  const response = await fetch(url);

  return new Promise((resolve, reject) => {
    if (!response.ok) {
      reject(new Error(`${response.statusText} (${response.status})`));
      return;
    }

    const totalBytes = parseInt(
      response.headers.get('content-length') || '0',
      10
    );

    if (!response || !response.body) {
      reject(new Error('Response body not available'));
      return;
    }

    const writer = fs.createWriteStream(filePath);
    const reader = response.body.getReader();
    const readable = new Readable();
    // eslint-disable-next-line no-underscore-dangle
    readable._read = async () => {
      const result = await reader.read();
      if (!result.done) {
        readable.push(Buffer.from(result.value));
      } else {
        readable.push(null);
      }
    };
    readable.pipe(writer);

    readable.on('data', (data) => {
      receivedBytes += data.length;
      onProgress(receivedBytes / totalBytes);
    });

    writer.on('finish', resolve);
  });
}

export async function unzipFile(
  zipPath: string,
  unzipPath: string,
  onProgress: ProgressHandler
) {
  const absUnzipPath = path.resolve(unzipPath);
  const extract = unzip.Extract({ path: absUnzipPath });
  const reader = fs.createReadStream(zipPath);

  const totalBytes = fs.statSync(zipPath).size;
  let extractedBytes = 0;

  reader.pipe(extract);

  reader.on('data', (data) => {
    extractedBytes += data.length;
    onProgress(extractedBytes / totalBytes);
  });

  return new Promise((resolve) => {
    extract.on('close', resolve);
  });
}

export async function createFolders(dir: string) {
  await fs.mkdirs(dir);
}

export async function copyFolder(sourcePath: string, targetPath: string) {
  debugConsole.log('copy folder from', sourcePath, 'to', targetPath);
  const sourceContents = fs.readdirSync(sourcePath);
  const innerPath =
    sourceContents.length === 1 &&
    fs.lstatSync(`${sourcePath}/${sourceContents[0]}`).isDirectory()
      ? `${sourcePath}/${sourceContents[0]}`
      : sourcePath;
  await fs.copy(innerPath, targetPath, {
    errorOnExist: false,
    overwrite: true,
    recursive: true,
  });
}

export function deleteFolder(folderPath: string) {
  if (fs.existsSync(folderPath)) {
    fs.emptyDirSync(folderPath);
    fs.removeSync(folderPath);
  } else {
    throw new Error(`${modsText.deleteNonExistingError}: "${folderPath}"`);
  }
}

export function deleteFolderExcept(folderPath: string, pathsToKeep: string[]) {
  if (!fs.existsSync(folderPath)) {
    throw new Error(`${modsText.deleteNonExistingError}: "${folderPath}"`);
  }

  const files = fs.readdirSync(folderPath);

  files.forEach((file) => {
    const fileDir = path.join(folderPath, file);

    if (!pathsToKeep.includes(file)) {
      fs.removeSync(fileDir);
    }
  });
}

export function deleteFile(filePath: string) {
  if (fs.existsSync(filePath)) {
    fs.removeSync(filePath);
  } else {
    throw new Error(`${modsText.deleteNonExistingError}: "${filePath}"`);
  }
}

export async function unzipRemoteFile(
  mod: Mod,
  url: string,
  onProgress: ProgressHandler
) {
  const destinationPath = mod.modPath;

  const onDownloadProgress: ProgressHandler = (progress) => {
    debugConsole.log('onDownloadProgress', progress);
    onProgress(progress * progressDownloadPortion);
  };

  const onUnzipProgress: ProgressHandler = (progress) => {
    debugConsole.log('onUnzipProgress', progress);
    onProgress(progressDownloadPortion + progress * progressUnzipPortion);
  };

  const temporaryName = path.basename(destinationPath);
  const userDataPath = remote.app.getPath('userData');
  const temporaryPath = `${userDataPath}/temp/${temporaryName}-${new Date().getTime()}`;
  const zipPath = `${temporaryPath}/${temporaryName}.zip`;
  const unzipPath = `${temporaryPath}/${temporaryName}`;
  const temporaryConfigPath = `${unzipPath}/config.json`;

  debugConsole.log('unzip remote file from', url, 'to', temporaryPath);

  await createFolders(unzipPath);

  await downloadFile(url, zipPath, onDownloadProgress);
  await unzipFile(zipPath, unzipPath, onUnzipProgress);

  // Prevent config.json in release from overwriting the existing one
  if (fs.existsSync(temporaryConfigPath)) {
    deleteFile(temporaryConfigPath);
  }

  return [temporaryPath, unzipPath];
}

export function openDirectory(directoryPath: string) {
  if (!directoryPath) {
    throw new Error(modsText.modPathNotDefinedError);
  }
  if (!fs.existsSync(directoryPath)) {
    throw new Error(modsText.openNonExistingDirectoryError);
  }

  exec(`start "open directory" "${path.resolve(directoryPath)}"`);
}