import * as fs from 'fs';
import * as crypto from 'crypto';
import * as path from 'path';
import { promisify } from 'util';

import { kebabCase } from 'lodash';
import * as log from 'loglevel';

import { copyFileOrDir } from '../helpers/helpers';
import { AppOptions } from '../options/model';

const writeFileAsync = promisify(fs.writeFile);

/**
 * Only picks certain app args to pass to nativefier.json
 */
function pickElectronAppArgs(options: AppOptions): any {
  return {
    alwaysOnTop: options.nativefier.alwaysOnTop,
    appCopyright: options.packager.appCopyright,
    appVersion: options.packager.appVersion,
    backgroundColor: options.nativefier.backgroundColor,
    basicAuthPassword: options.nativefier.basicAuthPassword,
    basicAuthUsername: options.nativefier.basicAuthUsername,
    bounce: options.nativefier.bounce,
    browserwindowOptions: options.nativefier.browserwindowOptions,
    buildVersion: options.packager.buildVersion,
    clearCache: options.nativefier.clearCache,
    counter: options.nativefier.counter,
    crashReporter: options.nativefier.crashReporter,
    darwinDarkModeSupport: options.packager.darwinDarkModeSupport,
    disableContextMenu: options.nativefier.disableContextMenu,
    disableDevTools: options.nativefier.disableDevTools,
    disableGpu: options.nativefier.disableGpu,
    diskCacheSize: options.nativefier.diskCacheSize,
    enableEs3Apis: options.nativefier.enableEs3Apis,
    fastQuit: options.nativefier.fastQuit,
    fileDownloadOptions: options.nativefier.fileDownloadOptions,
    flashPluginDir: options.nativefier.flashPluginDir,
    fullScreen: options.nativefier.fullScreen,
    globalShortcuts: options.nativefier.globalShortcuts,
    height: options.nativefier.height,
    hideWindowFrame: options.nativefier.hideWindowFrame,
    ignoreCertificate: options.nativefier.ignoreCertificate,
    ignoreGpuBlacklist: options.nativefier.ignoreGpuBlacklist,
    insecure: options.nativefier.insecure,
    internalUrls: options.nativefier.internalUrls,
    maxHeight: options.nativefier.maxHeight,
    maximize: options.nativefier.maximize,
    maxWidth: options.nativefier.maxWidth,
    minHeight: options.nativefier.minHeight,
    minWidth: options.nativefier.minWidth,
    name: options.packager.name,
    nativefierVersion: options.nativefier.nativefierVersion,
    processEnvs: options.nativefier.processEnvs,
    proxyRules: options.nativefier.proxyRules,
    showMenuBar: options.nativefier.showMenuBar,
    singleInstance: options.nativefier.singleInstance,
    targetUrl: options.packager.targetUrl,
    titleBarStyle: options.nativefier.titleBarStyle,
    tray: options.nativefier.tray,
    userAgent: options.nativefier.userAgent,
    versionString: options.nativefier.versionString,
    width: options.nativefier.width,
    win32metadata: options.packager.win32metadata,
    x: options.nativefier.x,
    y: options.nativefier.y,
    zoom: options.nativefier.zoom,
  };
}

async function maybeCopyScripts(srcs: string[], dest: string): Promise<void> {
  if (!srcs || srcs.length === 0) {
    log.debug('No files to inject, skipping copy.');
    return;
  }

  log.debug(`Copying ${srcs.length} files to inject in app.`);
  for (const src of srcs) {
    if (!fs.existsSync(src)) {
      throw new Error(
        `File ${src} not found. Note that Nativefier expects *local* files, not URLs.`,
      );
    }

    let destFileName: string;
    if (path.extname(src) === '.js') {
      destFileName = 'inject.js';
    } else if (path.extname(src) === '.css') {
      destFileName = 'inject.css';
    } else {
      return;
    }

    const destPath = path.join(dest, 'inject', destFileName);
    log.debug(`Copying injection file "${src}" to "${destPath}"`);
    await copyFileOrDir(src, destPath);
  }
}

function normalizeAppName(appName: string, url: string): string {
  // use a simple 3 byte random string to prevent collision
  const hash = crypto.createHash('md5');
  hash.update(url);
  const postFixHash = hash.digest('hex').substring(0, 6);
  const normalized = kebabCase(appName.toLowerCase());
  return `${normalized}-nativefier-${postFixHash}`;
}

function changeAppPackageJsonName(
  appPath: string,
  name: string,
  url: string,
): void {
  const packageJsonPath = path.join(appPath, '/package.json');
  const packageJson = JSON.parse(fs.readFileSync(packageJsonPath).toString());
  const normalizedAppName = normalizeAppName(name, url);
  packageJson.name = normalizedAppName;
  log.debug(`Updating ${packageJsonPath} 'name' field to ${normalizedAppName}`);

  fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson));
}

/**
 * Creates a temporary directory, copies the './app folder' inside,
 * and adds a text file with the app configuration.
 */
export async function prepareElectronApp(
  src: string,
  dest: string,
  options: AppOptions,
): Promise<void> {
  log.debug(`Copying electron app from ${src} to ${dest}`);
  try {
    await copyFileOrDir(src, dest);
  } catch (err) {
    throw `Error copying electron app from ${src} to temp dir ${dest}. Error: ${err}`;
  }

  const appJsonPath = path.join(dest, '/nativefier.json');
  log.debug(`Writing app config to ${appJsonPath}`);
  await writeFileAsync(
    appJsonPath,
    JSON.stringify(pickElectronAppArgs(options)),
  );

  try {
    await maybeCopyScripts(options.nativefier.inject, dest);
  } catch (err) {
    log.error('Error copying injection files.', err);
  }
  changeAppPackageJsonName(
    dest,
    options.packager.name,
    options.packager.targetUrl,
  );
}
