import test from 'ava'
import { promises as fs } from 'fs'
import path from 'path'
import proxyquire from 'proxyquire'
import { ensureDirectoryExists, readJSONFile } from '../app/javascripts/Main/Utils/FileUtils'
import { createTmpDir } from './testUtils'
import { AppName } from '../app/javascripts/Main/strings'
import makeFakePaths from './fakePaths'
import { PackageManagerInterface } from '../app/javascripts/Main/Packages/PackageManagerInterface'

const tmpDir = createTmpDir(__filename)
const FakePaths = makeFakePaths(tmpDir.path)

const contentDir = path.join(tmpDir.path, 'Extensions')
let downloadFileCallCount = 0

const { initializePackageManager } = proxyquire('../app/javascripts/Main/Packages/PackageManager', {
  './paths': {
    Paths: FakePaths,
    '@noCallThru': true,
  },
  './networking': {
    /** Download a fake component file */
    async downloadFile(_src: string, dest: string) {
      downloadFileCallCount += 1
      if (!path.normalize(dest).startsWith(tmpDir.path)) {
        throw new Error(`Bad download destination: ${dest}`)
      }
      await ensureDirectoryExists(path.dirname(dest))
      await fs.copyFile(path.join(__dirname, 'data', 'zip-file.zip'), path.join(dest))
    },
  },
})

const fakeWebContents = {
  send(_eventName: string, { error }) {
    if (error) throw error
  },
}

const name = 'Fake Component'
const identifier = 'fake.component'
const uuid = 'fake-component'
const version = '1.0.0'
const modifiers = Array(20)
  .fill(0)
  .map((_, i) => String(i).padStart(2, '0'))

function fakeComponent({ deleted = false, modifier = '' } = {}) {
  return {
    uuid: uuid + modifier,
    deleted,
    content: {
      name: name + modifier,
      autoupdateDisabled: false,
      package_info: {
        version,
        identifier: identifier + modifier,
        download_url: 'https://standardnotes.com',
        url: 'https://standardnotes.com',
        latest_url: 'https://standardnotes.com',
      },
    },
  }
}

let packageManager: PackageManagerInterface

const log = console.log
const error = console.error

test.before(async function () {
  /** Silence the package manager's output. */
  // eslint-disable-next-line @typescript-eslint/no-empty-function
  console.log = () => {}
  // eslint-disable-next-line @typescript-eslint/no-empty-function
  console.error = () => {}
  await ensureDirectoryExists(contentDir)
  packageManager = await initializePackageManager(fakeWebContents)
})

test.after.always(async function () {
  console.log = log
  console.error = error
  await tmpDir.clean()
})

test.beforeEach(function () {
  downloadFileCallCount = 0
})

test('installs multiple components', async (t) => {
  await packageManager.syncComponents(modifiers.map((modifier) => fakeComponent({ modifier })))
  await new Promise((resolve) => setTimeout(resolve, 200))

  const files = await fs.readdir(contentDir)
  t.is(files.length, 1 + modifiers.length)
  for (const modifier of modifiers) {
    t.true(files.includes(identifier + modifier))
  }
  t.true(files.includes('mapping.json'))
  const mappingContents = await fs.readFile(path.join(contentDir, 'mapping.json'), 'utf8')

  t.deepEqual(
    JSON.parse(mappingContents),
    modifiers.reduce((acc, modifier) => {
      acc[uuid + modifier] = {
        location: path.join('Extensions', identifier + modifier),
        version,
      }
      return acc
    }, {}),
  )

  const downloads = await fs.readdir(path.join(tmpDir.path, AppName, 'downloads'))
  t.is(downloads.length, modifiers.length)
  for (const modifier of modifiers) {
    t.true(downloads.includes(`${name + modifier}.zip`))
  }

  for (const modifier of modifiers) {
    const componentFiles = await fs.readdir(path.join(contentDir, identifier + modifier))
    t.is(componentFiles.length, 2)
  }
})

test('uninstalls multiple components', async (t) => {
  await packageManager.syncComponents(modifiers.map((modifier) => fakeComponent({ deleted: true, modifier })))
  await new Promise((resolve) => setTimeout(resolve, 200))

  const files = await fs.readdir(contentDir)
  t.deepEqual(files, ['mapping.json'])

  t.deepEqual(await readJSONFile(path.join(contentDir, 'mapping.json')), {})
})

test("doesn't download anything when two install/uninstall tasks are queued", async (t) => {
  await Promise.all([
    packageManager.syncComponents([fakeComponent({ deleted: false })]),
    packageManager.syncComponents([fakeComponent({ deleted: false })]),
    packageManager.syncComponents([fakeComponent({ deleted: true })]),
  ])
  t.is(downloadFileCallCount, 1)
})

test("Relies on download_url's version field to store the version number", async (t) => {
  await packageManager.syncComponents([fakeComponent()])
  await new Promise((resolve) => setTimeout(resolve, 200))

  const mappingFileVersion = JSON.parse(await fs.readFile(path.join(contentDir, 'mapping.json'), 'utf8'))[uuid].version

  const packageJsonVersion = JSON.parse(
    await fs.readFile(path.join(contentDir, identifier, 'package.json'), 'utf-8'),
  ).version

  t.not(mappingFileVersion, packageJsonVersion)
  t.is(mappingFileVersion, version)
})
