const { resolve } = require('path')

const Arborist = require('@npmcli/arborist')
const t = require('tap')
const requireInject = require('require-inject')

const redactCwd = (path) => {
  const normalizePath = p => p
    .replace(/\\+/g, '/')
    .replace(/\r\n/g, '\n')
  return normalizePath(path)
    .replace(new RegExp(normalizePath(process.cwd()), 'g'), '{CWD}')
}

t.cleanSnapshot = (str) => redactCwd(str)

let reifyOutput
const npm = {
  globalDir: null,
  prefix: null,
  flatOptions: {},
  config: {
    get () {
      return false
    },
    find () {},
  },
}
const printLinks = async (opts) => {
  let res = ''
  const arb = new Arborist(opts)
  const tree = await arb.loadActual()
  const linkedItems = [...tree.inventory.values()]
    .sort((a, b) => a.pkgid.localeCompare(b.pkgid))
  for (const item of linkedItems) {
    if (item.target)
      res += `${item.path} -> ${item.target.path}\n`
  }
  return res
}

const mocks = {
  '../../lib/npm.js': npm,
  '../../lib/utils/reify-output.js': () => reifyOutput(),
}

const link = requireInject('../../lib/link.js', mocks)

t.test('link to globalDir when in current working dir of pkg and no args', (t) => {
  t.plan(2)

  const testdir = t.testdir({
    'global-prefix': {
      lib: {
        node_modules: {
          a: {
            'package.json': JSON.stringify({
              name: 'a',
              version: '1.0.0',
            }),
          },
        },
      },
    },
    'test-pkg-link': {
      'package.json': JSON.stringify({
        name: 'test-pkg-link',
        version: '1.0.0',
      }),
    },
  })
  npm.globalDir = resolve(testdir, 'global-prefix', 'lib', 'node_modules')
  npm.prefix = resolve(testdir, 'test-pkg-link')

  reifyOutput = async () => {
    reifyOutput = undefined

    const links = await printLinks({
      path: resolve(npm.globalDir, '..'),
      global: true,
    })

    t.matchSnapshot(links, 'should create a global link to current pkg')
  }

  link([], (err) => {
    t.ifError(err, 'should not error out')
  })
})

t.test('link global linked pkg to local nm when using args', (t) => {
  t.plan(2)

  const testdir = t.testdir({
    'global-prefix': {
      lib: {
        node_modules: {
          '@myscope': {
            foo: {
              'package.json': JSON.stringify({
                name: '@myscope/foo',
                version: '1.0.0',
              }),
            },
            bar: {
              'package.json': JSON.stringify({
                name: '@myscope/bar',
                version: '1.0.0',
              }),
            },
            linked: t.fixture('symlink', '../../../../scoped-linked'),
          },
          a: {
            'package.json': JSON.stringify({
              name: 'a',
              version: '1.0.0',
            }),
          },
          b: {
            'package.json': JSON.stringify({
              name: 'b',
              version: '1.0.0',
            }),
          },
          'test-pkg-link': t.fixture('symlink', '../../../test-pkg-link'),
        },
      },
    },
    'test-pkg-link': {
      'package.json': JSON.stringify({
        name: 'test-pkg-link',
        version: '1.0.0',
      }),
    },
    'link-me-too': {
      'package.json': JSON.stringify({
        name: 'link-me-too',
        version: '1.0.0',
      }),
    },
    'scoped-linked': {
      'package.json': JSON.stringify({
        name: '@myscope/linked',
        version: '1.0.0',
      }),
    },
    'my-project': {
      'package.json': JSON.stringify({
        name: 'my-project',
        version: '1.0.0',
        dependencies: {
          foo: '^1.0.0',
        },
      }),
      node_modules: {
        foo: {
          'package.json': JSON.stringify({
            name: 'foo',
            version: '1.0.0',
          }),
        },
      },
    },
  })
  npm.globalDir = resolve(testdir, 'global-prefix', 'lib', 'node_modules')
  npm.prefix = resolve(testdir, 'my-project')

  const _cwd = process.cwd()
  process.chdir(npm.prefix)

  reifyOutput = async () => {
    reifyOutput = undefined
    process.chdir(_cwd)

    const links = await printLinks({
      path: npm.prefix,
    })

    t.matchSnapshot(links, 'should create a local symlink to global pkg')
  }

  // installs examples for:
  // - test-pkg-link: pkg linked to globalDir from local fs
  // - @myscope/linked: scoped pkg linked to globalDir from local fs
  // - @myscope/bar: prev installed scoped package available in globalDir
  // - a: prev installed package available in globalDir
  // - file:./link-me-too: pkg that needs to be reified in globalDir first
  link([
    'test-pkg-link',
    '@myscope/linked',
    '@myscope/bar',
    'a',
    'file:../link-me-too',
  ], (err) => {
    t.ifError(err, 'should not error out')
  })
})

t.test('link pkg already in global space', (t) => {
  t.plan(3)

  const testdir = t.testdir({
    'global-prefix': {
      lib: {
        node_modules: {
          '@myscope': {
            linked: t.fixture('symlink', '../../../../scoped-linked'),
          },
        },
      },
    },
    'scoped-linked': {
      'package.json': JSON.stringify({
        name: '@myscope/linked',
        version: '1.0.0',
      }),
    },
    'my-project': {
      'package.json': JSON.stringify({
        name: 'my-project',
        version: '1.0.0',
      }),
    },
  })
  npm.globalDir = resolve(testdir, 'global-prefix', 'lib', 'node_modules')
  npm.prefix = resolve(testdir, 'my-project')

  npm.config.find = () => 'default'

  const _cwd = process.cwd()
  process.chdir(npm.prefix)

  reifyOutput = async () => {
    reifyOutput = undefined
    process.chdir(_cwd)
    npm.config.find = () => null

    const links = await printLinks({
      path: npm.prefix,
    })

    t.equal(
      require(resolve(testdir, 'my-project', 'package.json')).dependencies,
      undefined,
      'should not save to package.json upon linking'
    )

    t.matchSnapshot(links, 'should create a local symlink to global pkg')
  }

  // installs examples for:
  // - test-pkg-link: pkg linked to globalDir from local fs
  // - @myscope/linked: scoped pkg linked to globalDir from local fs
  // - @myscope/bar: prev installed scoped package available in globalDir
  // - a: prev installed package available in globalDir
  // - file:./link-me-too: pkg that needs to be reified in globalDir first
  link(['@myscope/linked'], (err) => {
    t.ifError(err, 'should not error out')
  })
})

t.test('link pkg already in global space when prefix is a symlink', (t) => {
  t.plan(3)

  const testdir = t.testdir({
    'global-prefix': t.fixture('symlink', './real-global-prefix'),
    'real-global-prefix': {
      lib: {
        node_modules: {
          '@myscope': {
            linked: t.fixture('symlink', '../../../../scoped-linked'),
          },
        },
      },
    },
    'scoped-linked': {
      'package.json': JSON.stringify({
        name: '@myscope/linked',
        version: '1.0.0',
      }),
    },
    'my-project': {
      'package.json': JSON.stringify({
        name: 'my-project',
        version: '1.0.0',
      }),
    },
  })
  npm.globalDir = resolve(testdir, 'global-prefix', 'lib', 'node_modules')
  npm.prefix = resolve(testdir, 'my-project')

  npm.config.find = () => 'default'

  const _cwd = process.cwd()
  process.chdir(npm.prefix)

  reifyOutput = async () => {
    reifyOutput = undefined
    process.chdir(_cwd)
    npm.config.find = () => null

    const links = await printLinks({
      path: npm.prefix,
    })

    t.equal(
      require(resolve(testdir, 'my-project', 'package.json')).dependencies,
      undefined,
      'should not save to package.json upon linking'
    )

    t.matchSnapshot(links, 'should create a local symlink to global pkg')
  }

  link(['@myscope/linked'], (err) => {
    t.ifError(err, 'should not error out')
  })
})

t.test('completion', (t) => {
  const testdir = t.testdir({
    'global-prefix': {
      lib: {
        node_modules: {
          foo: {},
          bar: {},
          lorem: {},
          ipsum: {},
        },
      },
    },
  })
  npm.globalDir = resolve(testdir, 'global-prefix', 'lib', 'node_modules')

  link.completion({}, (err, words) => {
    t.ifError(err, 'should not error out')
    t.deepEqual(
      words,
      ['bar', 'foo', 'ipsum', 'lorem'],
      'should list all package names available in globalDir'
    )
    t.end()
  })
})

t.test('--global option', (t) => {
  const _config = npm.config
  npm.config = { get () {
    return true
  } }
  link([], (err) => {
    npm.config = _config

    t.match(
      err.message,
      /link should never be --global/,
      'should throw an useful error'
    )

    t.end()
  })
})
