const YarnLock = require('../lib/yarn-lock.js')
const Arborist = require('../lib/arborist')
const Node = require('../lib/node.js')

const t = require('tap')
const { resolve, basename } = require('node:path')
const fixtures = [
  resolve(__dirname, 'fixtures/tap-with-yarn-lock'),
  resolve(__dirname, 'fixtures/yarn-stuff'),
]
const { readFileSync } = require('node:fs')

fixtures.forEach(f => t.test(basename(f), t => {
  const lockdata = readFileSync(f + '/yarn.lock')
  const yarnLock = new YarnLock()
  // parse the data
  yarnLock.parse(lockdata)
  // then turn it into output
  const lockOutput = yarnLock.toString()
  // snapshot the result
  t.matchSnapshot(lockOutput, 'generated output from input')
  const yarnLock2 = new YarnLock()
  yarnLock2.parse(lockOutput)
  t.strictSame(yarnLock2, yarnLock, 'same parsed result from string output')
  t.end()
}))

t.test('invalid yarn lockfile data throws', t => {
  t.throws(() => YarnLock.parse(`
asdf@foo:
  this !is not vlid
            i mean
what even is it??
   not yarn lock, that's for sure
      {"maybe":"json"}?
 - or: even
 - yaml?
 - NO
`), { content: '  this !is not vlid\n', line: 3, position: 11 }, 'just garbage')

  t.throws(() => YarnLock.parse(`
asdf@foo:
  dependencies:
    foo bar baz blork
`), { content: '    foo bar baz blork\n', line: 4 }, 'invalid subkey')

  t.end()
})

t.test('omits empty dependency list on toString output', t => {
  const y = new YarnLock()
  y.parse(`
foo@bar:
  version "1.2.3"
  resolved "https://registry.local/foo/-/foo-1.2.3.tgz"
  dependencies:

# Note: do not require a \\n at the end of the file, just add it if missing
# Also: comments are not preserved.

bar@foo:
  version "1.2.3"`)
  t.equal(y.toString(), `# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1


bar@foo:
  version "1.2.3"

foo@bar:
  version "1.2.3"
  resolved "https://registry.local/foo/-/foo-1.2.3.tgz"
`)
  t.end()
})

t.test('exports YarnLockEntry class', t => {
  t.type(YarnLock.Entry, 'function')
  t.end()
})

t.test('load a yarn lock from an actual tree', t => {
  // make sure the symlinks are loaded
  require('./fixtures/index.js')
  const fixtures = [
    resolve(__dirname, 'fixtures/install-types'),
    resolve(__dirname, 'fixtures/links-all-over'),
  ]
  for (const fixture of fixtures) {
    t.test(basename(fixture), async t => {
      const tree = await new Arborist({ path: fixture }).loadActual()
      const y = YarnLock.fromTree(tree)
      t.matchSnapshot(y.toString(), 'yarn.lock from a package tree')
    })
  }
  t.end()
})

t.test('yarn lock with dedupes yarn wouldnt do', async t => {
  const tree = new Node({
    path: '/my/project',
    pkg: { dependencies: { x: '1.x', y: '1.x', z: '1.x' } },
    children: [
      { pkg: { name: 'x', version: '1.2.0' } },
      { pkg: { name: 'y', version: '1.0.0', dependencies: { x: '1.1', z: '2.x' } },
        children: [
          { pkg: { name: 'x', version: '1.1.0' } },
          { pkg: { name: 'z', version: '2.0.0', dependencies: { x: '1.x' } } },
          // note: yarn nests another x@1.2.0 here, because it locks
          // the 1.x resolution to 1.2.0 even when unnecessary
        ],
      },
      { pkg: { name: 'z', version: '1.0.0' } },
    ],
  })

  const y = YarnLock.fromTree(tree)
  t.matchSnapshot(y.toString(), 'yarn.lock from deduped tree')
})

t.test('deduped prior entries that do not match one another', async t => {
  const tree = new Node({
    path: '/my/project',
    pkg: { dependencies: { a: '', b: '' } },
    children: [
      { pkg: { name: 'a', dependencies: { i: '', x: '1.x', y: '1.x', z: '1.x' } },
        children: [
          { pkg: { name: 'i', version: '1.0.0', dependencies: { x: '1.2.0' } } },
          { pkg: { name: 'x', version: '1.2.0' }, integrity: 'x120' },
          { pkg: { name: 'y', version: '1.0.0', dependencies: { x: '1.1', z: '2.x' } },
            children: [
              { pkg: { name: 'x', version: '1.1.0' } },
              { pkg: { name: 'z', version: '2.0.0', dependencies: { x: '1.x' } } },
            ],
          },
          { pkg: { name: 'z', version: '1.0.0' } },
        ],
      },
      { pkg: { name: 'b', dependencies: { j: '', x: '1.x', y: '1.x', z: '1.x' } },
        children: [
          { pkg: { name: 'j', version: '1.0.0', dependencies: { x: '1.3.0' } } },
          { pkg: { name: 'x', version: '1.3.0' }, integrity: 'x130' },
          { pkg: { name: 'y', version: '1.0.0', dependencies: { x: '1.1', z: '2.x' } },
            children: [
              { pkg: { name: 'x', version: '1.1.0' } },
              { pkg: { name: 'z', version: '2.0.0', dependencies: { x: '1.x' } } },
            ],
          },
          { pkg: { name: 'z', version: '1.0.0' } },
        ],
      },
    ],
  })

  const y = YarnLock.fromTree(tree)
  t.matchSnapshot(y.toString(), 'yarn.lock with mismatching previous resolutions')
})

t.test('more nesting tree complications', async t => {
  const tree = new Node({
    path: '/my/project',
    pkg: { dependencies: { a: '', b: '', c: '', c2: '', d3: '' } },
    children: [
      { pkg: { name: 'a', dependencies: { x: '', g: '', h: '', i: '', j: '', k: '' } },
        children: [
          { pkg: { name: 'x', version: '1.0.0' }, resolved: 'https://x100.xyz' },
          { pkg: { name: 'g', version: '1.0.0', dependencies: { x: '^1.0.0' } } },
          { pkg: { name: 'h', version: '1.0.0', dependencies: { x: '1.0.0' } } },
          { pkg: { name: 'i', version: '1.0.0', dependencies: { x: '1.0' } } },
          { pkg: { name: 'j', version: '1.0.0', dependencies: { x: '1' } } },
          { pkg: { name: 'k', version: '1.0.0', dependencies: { x: '' } } },
        ],
      },
      // previously-seen specs that don't match
      { pkg: { name: 'b', dependencies: { x: '', l: '', m: '', n: '', n2: '', o: '', p: '' } },
        children: [
          { pkg: { name: 'x', version: '1.0.1' } },
          { pkg: { name: 'l', version: '1.0.0', dependencies: { x: '^1.0.1' } } },
          { pkg: { name: 'm', version: '1.0.0', dependencies: { x: '1.0.1' } } },
          { pkg: { name: 'n', version: '1.0.0', dependencies: { x: '1.0' } } },
          { pkg: { name: 'n2', version: '1.0.0', dependencies: { x: '>=1.0' } } },
          { pkg: { name: 'o', version: '1.0.0', dependencies: { x: '1' } } },
          { pkg: { name: 'p', version: '1.0.0', dependencies: { x: '' } } },
        ],
      },
      // new specs that get added right away to the entry
      { pkg: { name: 'c', dependencies: { d: '', e: '', f: '' } },
        children: [
          { pkg: { name: 'x', version: '1.0.1' }, integrity: 'x101', resolved: 'https://x101.xyz' },
          { pkg: { name: 'd', version: '1.0.0', dependencies: { x: '^1.0.1' } } },
          { pkg: { name: 'e', version: '1.0.0', dependencies: { x: '>=1.0.1 <2' } } },
          { pkg: { name: 'f', version: '1.0.0', dependencies: { x: '>=1.0' } } },
        ],
      },
      // new specs that later match
      { pkg: { name: 'c2', dependencies: { d2: '', e2: '', f2: '' } },
        children: [
          { pkg: { name: 'x', version: '1.0.1' }, resolved: 'https://x101.xyz' },
          { pkg: { name: 'd2', version: '1.0.0', dependencies: { x: '^1.0.0-x' } } },
          { pkg: { name: 'e2', version: '1.0.0', dependencies: { x: '>=1.0.0' } } },
          { pkg: { name: 'f2', version: '1.0.0', dependencies: { x: '1.0.1' } } },
        ],
      },
      // no version, resolved, or integrity, we assume a match
      { pkg: { name: 'd3', dependencies: { x: '' } },
        children: [{ pkg: { name: 'x' } }],
      },
    ],
  })

  const y = YarnLock.fromTree(tree)
  t.matchSnapshot(y.toString(), 'yarn.lock with mismatching previous resolutions')
})
