'use strict'

const common = require('../common')
const download = require('../download')
const fs = require('fs-extra')
const hostArch = require('electron-download/lib/arch').host
const packager = require('..')
const path = require('path')
const sinon = require('sinon')
const test = require('ava')
const util = require('./_util')

// Generates a path to the generated app that reflects the name given in the options.
// Returns the Helper.app location on darwin since the top-level .app is already tested for the
// resources path; on other OSes, returns the executable.
function generateNamePath (opts) {
  if (common.isPlatformMac(opts.platform)) {
    return path.join(`${opts.name}.app`, 'Contents', 'Frameworks', `${opts.name} Helper.app`)
  }

  return opts.name + (opts.platform === 'win32' ? '.exe' : '')
}

test('setting the quiet option does not print messages', (t) => {
  util.setupConsoleWarnSpy()
  sinon.spy(console, 'error')

  common.warning('warning', true)
  t.true(console.warn.notCalled, 'quieted common.warning should not call console.warn')
  common.info('info', true)
  t.true(console.error.notCalled, 'quieted common.info should not call console.error')
})

test('download argument test: download.{arch,platform,version} does not overwrite {arch,platform,version}', t => {
  const opts = {
    download: {
      arch: 'ia32',
      platform: 'win32',
      version: '0.30.0'
    },
    electronVersion: '0.36.0'
  }

  const downloadOpts = download.createDownloadOpts(opts, 'linux', 'x64')
  t.deepEqual(downloadOpts, { arch: 'x64', platform: 'linux', version: '0.36.0' })
})

test('sanitize app name for use in file/directory names', t => {
  t.is('@username-package', common.sanitizeAppName('@username/package'), 'slash should be replaced')
})

test('sanitize app name for use in the out directory name', t => {
  let opts = {
    arch: 'x64',
    name: '@username/package-name',
    platform: 'linux'
  }
  t.is('@username-package-name-linux-x64', common.generateFinalBasename(opts), 'generateFinalBasename output should be sanitized')
})

test('cannot build apps where the name ends in " Helper"', (t) => {
  const opts = {
    arch: 'x64',
    dir: path.join(__dirname, 'fixtures', 'el-0374'),
    name: 'Bad Helper',
    platform: 'linux'
  }

  return packager(opts)
    .then(
      () => { throw new Error('should not finish') },
      (err) => t.is(err.message, 'Application names cannot end in " Helper" due to limitations on macOS')
    )
})

test('deprecatedParameter moves value in deprecated param to new param if new param is not set', (t) => {
  let opts = {
    old: 'value'
  }
  common.deprecatedParameter(opts, 'old', 'new', 'new-value')

  t.false(opts.hasOwnProperty('old'), 'old property is not set')
  t.true(opts.hasOwnProperty('new'), 'new property is set')

  opts.not_overwritten_old = 'another'
  common.deprecatedParameter(opts, 'not_overwritten_old', 'new', 'new-value')

  t.false(opts.hasOwnProperty('not_overwritten_old'), 'not_overwritten_old property is not set')
  t.true(opts.hasOwnProperty('new'), 'new property is set')
  t.is('value', opts.new, 'new property is not overwritten')
})

util.testSinglePlatform('defaults test', (t, opts) => {
  opts.name = 'defaultsTest'
  opts.dir = util.fixtureSubdir('basic')
  delete opts.platform
  delete opts.arch

  let defaultOpts = {
    arch: hostArch(),
    name: opts.name,
    platform: process.platform
  }

  let finalPath
  let resourcesPath

  return packager(opts)
    .then(paths => {
      t.true(Array.isArray(paths), 'packager call should resolve to an array')
      t.is(paths.length, 1, 'Single-target run should resolve to a 1-item array')

      finalPath = paths[0]
      t.is(finalPath, path.join(t.context.workDir, common.generateFinalBasename(defaultOpts)),
           'Path should follow the expected format and be in the cwd')
      return util.assertDirectory(t, finalPath, 'The expected output directory should exist')
    }).then(() => {
      resourcesPath = path.join(finalPath, util.generateResourcesPath(defaultOpts))
      const appPath = path.join(finalPath, generateNamePath(defaultOpts))

      if (common.isPlatformMac(defaultOpts.platform)) {
        return util.assertDirectory(t, appPath, 'The Helper.app should reflect opts.name')
      } else {
        return util.assertFile(t, appPath, 'The executable should reflect opts.name')
      }
    }).then(() => util.assertDirectory(t, resourcesPath, 'The output directory should contain the expected resources subdirectory'))
    .then(() => util.assertPathNotExists(t, path.join(resourcesPath, 'app', 'node_modules', 'run-waterfall'), 'The output directory should NOT contain devDependencies by default (prune=true)'))
    .then(() => util.assertFilesEqual(t, path.join(opts.dir, 'main.js'), path.join(resourcesPath, 'app', 'main.js'), 'File under packaged app directory should match source file'))
    .then(() => util.assertFilesEqual(t, path.join(opts.dir, 'ignore', 'this.txt'), path.join(resourcesPath, 'app', 'ignore', 'this.txt'), 'File under subdirectory of packaged app directory should match source file and not be ignored by default'))
    .then(() => util.assertPathNotExists(t, path.join(resourcesPath, 'default_app'), 'The output directory should not contain the Electron default_app directory'))
    .then(() => util.assertPathNotExists(t, path.join(resourcesPath, 'default_app.asar'), 'The output directory should not contain the Electron default_app.asar file'))
})

util.testSinglePlatform('out test', (t, opts) => {
  opts.name = 'outTest'
  opts.dir = util.fixtureSubdir('basic')
  opts.out = 'dist'

  let finalPath

  return packager(opts)
    .then(paths => {
      finalPath = paths[0]
      t.is(finalPath, path.join('dist', common.generateFinalBasename(opts)),
           'Path should follow the expected format and be under the folder specified in `out`')
      return util.assertDirectory(t, finalPath, 'The expected output directory should exist')
    }).then(() => util.assertDirectory(t, path.join(finalPath, util.generateResourcesPath(opts)), 'The output directory should contain the expected resources subdirectory'))
})

util.testSinglePlatform('overwrite test', (t, opts) => {
  opts.name = 'overwriteTest'
  opts.dir = util.fixtureSubdir('basic')

  let finalPath
  let testPath

  return packager(opts)
    .then(paths => {
      finalPath = paths[0]
      return util.assertDirectory(t, finalPath, 'The expected output directory should exist')
    }).then(() => {
      // Create a dummy file to detect whether the output directory is replaced in subsequent runs
      testPath = path.join(finalPath, 'test.txt')
      return fs.writeFile(testPath, 'test')
    }).then(() => packager(opts)) // Run again, defaulting to overwrite false
    .then(paths => util.assertFile(t, testPath, 'The existing output directory should exist as before (skipped by default)'))
    .then(() => {
      // Run a third time, explicitly setting overwrite to true
      opts.overwrite = true
      return packager(opts)
    }).then(paths => util.assertPathNotExists(t, testPath, 'The output directory should be regenerated when overwrite is true'))
})

util.testSinglePlatform('overwrite test sans platform/arch set', (t, opts) => {
  delete opts.platfrom
  delete opts.arch
  opts.dir = util.fixtureSubdir('basic')
  opts.overwrite = true

  return packager(opts)
    .then(paths => util.assertPathExists(t, paths[0], 'The output directory exists'))
    .then(() => packager(opts))
    .then(paths => util.assertPathExists(t, paths[0], 'The output directory exists'))
})

util.testSinglePlatform('tmpdir test', (t, opts) => {
  opts.name = 'tmpdirTest'
  opts.dir = path.join(__dirname, 'fixtures', 'basic')
  opts.out = 'dist'
  opts.tmpdir = path.join(t.context.workDir, 'tmp')

  return packager(opts)
    .then(paths => util.assertDirectory(t, path.join(opts.tmpdir, 'electron-packager'), 'The expected temp directory should exist'))
})

util.testSinglePlatform('disable tmpdir test', (t, opts) => {
  opts.name = 'disableTmpdirTest'
  opts.dir = util.fixtureSubdir('basic')
  opts.out = 'dist'
  opts.tmpdir = false

  return packager(opts)
    .then(paths => util.assertDirectory(t, paths[0], 'The expected out directory should exist'))
})

util.testSinglePlatform('deref symlink test', (t, opts) => {
  opts.name = 'disableSymlinkDerefTest'
  opts.dir = util.fixtureSubdir('basic')
  opts.derefSymlinks = false

  const src = path.join(opts.dir, 'main.js')
  const dest = path.join(opts.dir, 'main-link.js')

  return fs.ensureSymlink(src, dest)
    .then(() => packager(opts))
    .then(paths => {
      const destLink = path.join(paths[0], 'resources', 'app', 'main-link.js')
      return util.assertSymlink(t, destLink, 'The expected file should still be a symlink')
    }).then(() => fs.remove(dest))
})

function createExtraResourceStringTest (t, opts, platform) {
  const extra1Base = 'data1.txt'
  const extra1Path = path.join(__dirname, 'fixtures', extra1Base)

  opts.name = 'extraResourceStringTest'
  opts.dir = util.fixtureSubdir('basic')
  opts.out = 'dist'
  opts.platform = platform
  opts.extraResource = extra1Path

  return util.packageAndEnsureResourcesPath(t, opts)
    .then(resourcesPath => util.assertFilesEqual(t, extra1Path, path.join(resourcesPath, extra1Base), 'resource file data1.txt should match'))
}

function createExtraResourceArrayTest (t, opts, platform) {
  const extra1Base = 'data1.txt'
  const extra1Path = path.join(__dirname, 'fixtures', extra1Base)
  const extra2Base = 'extrainfo.plist'
  const extra2Path = path.join(__dirname, 'fixtures', extra2Base)

  opts.name = 'extraResourceArrayTest'
  opts.dir = util.fixtureSubdir('basic')
  opts.out = 'dist'
  opts.platform = platform
  opts.extraResource = [extra1Path, extra2Path]

  let extra1DistPath
  let extra2DistPath

  return util.packageAndEnsureResourcesPath(t, opts)
    .then(resourcesPath => {
      extra1DistPath = path.join(resourcesPath, extra1Base)
      extra2DistPath = path.join(resourcesPath, extra2Base)
      return util.assertPathExists(t, extra1DistPath, 'resource file data1.txt exists')
    }).then(() => util.assertFilesEqual(t, extra1Path, extra1DistPath, 'resource file data1.txt should match'))
    .then(() => util.assertPathExists(t, extra2DistPath, 'resource file extrainfo.plist exists'))
    .then(() => util.assertFilesEqual(t, extra2Path, extra2DistPath, 'resource file extrainfo.plist should match'))
}

for (const platform of ['darwin', 'linux']) {
  util.testSinglePlatform(`extraResource test: string (${platform})`, createExtraResourceStringTest, platform)
  util.testSinglePlatform(`extraResource test: array (${platform})`, createExtraResourceArrayTest, platform)
}

util.testSinglePlatform('building for Linux target sanitizes binary name', (t, opts) => {
  opts.name = '@username/package-name'
  opts.dir = util.fixtureSubdir('basic')

  return packager(opts)
    .then(paths => {
      t.is(1, paths.length, '1 bundle created')
      return util.assertFile(t, path.join(paths[0], '@username-package-name'), 'The sanitized binary filename should exist')
    })
})

util.testSinglePlatform('executableName honored when building for Linux target', (t, opts) => {
  opts.name = 'PackageName'
  opts.executableName = 'my-package'
  opts.dir = util.fixtureSubdir('basic')

  return packager(opts)
    .then(paths => {
      t.is(1, paths.length, '1 bundle created')
      return util.assertFile(t, path.join(paths[0], 'my-package'), 'The executableName-based filename should exist')
    })
})

util.packagerTest('fails with invalid version', util.invalidOptionTest({
  name: 'invalidElectronTest',
  dir: util.fixtureSubdir('el-0374'),
  electronVersion: '0.0.1',
  arch: 'x64',
  platform: 'linux',
  download: {
    quiet: !!process.env.CI
  }
}))

util.testSinglePlatform('dir argument test: should work with relative path', (t, opts) => {
  opts.name = 'ElectronTest'
  opts.dir = path.join('..', 'fixtures', 'el-0374')
  opts.electronVersion = '0.37.4'

  return packager(opts)
    .then(paths => t.is(path.join(t.context.workDir, 'ElectronTest-linux-x64'), paths[0], 'paths returned'))
})
