const t = require('tap')

t.cleanSnapshot = str => str.replace(/published .*? ago/g, 'published {TIME} ago')

// run the same as tap does when running directly with node
process.stdout.columns = undefined

const { fake: mockNpm } = require('../../fixtures/mock-npm')

let logs
const cleanLogs = () => {
  logs = ''
  const fn = (...args) => {
    logs += '\n'
    args.map(el => logs += el)
  }
  console.log = fn
}

// 25 hours ago
const yesterday = new Date(Date.now() - 1000 * 60 * 60 * 25)

const packument = (nv, opts) => {
  if (!opts.fullMetadata)
    throw new Error('must fetch fullMetadata')

  if (!opts.preferOnline)
    throw new Error('must fetch with preferOnline')

  const mocks = {
    red: {
      name: 'red',
      'dist-tags': {
        '1.0.1': {},
      },
      time: {
        unpublished: new Date(),
      },
    },
    blue: {
      name: 'blue',
      'dist-tags': {
        latest: '1.0.0',
      },
      time: {
        '1.0.0': yesterday,
      },
      versions: {
        '1.0.0': {
          name: 'blue',
          version: '1.0.0',
          dist: {
            shasum: '123',
            tarball: 'http://hm.blue.com/1.0.0.tgz',
            integrity: '---',
            fileCount: 1,
            unpackedSize: 1,
          },
        },
        '1.0.1': {
          name: 'blue',
          version: '1.0.1',
          dist: {
            shasum: '124',
            tarball: 'http://hm.blue.com/1.0.1.tgz',
            integrity: '---',
            fileCount: 1,
            unpackedSize: 1,
          },
        },
      },
    },
    cyan: {
      _npmUser: {
        name: 'claudia',
        email: 'claudia@cyan.com',
      },
      name: 'cyan',
      'dist-tags': {
        latest: '1.0.0',
      },
      versions: {
        '1.0.0': {
          version: '1.0.0',
          name: 'cyan',
          dist: {
            shasum: '123',
            tarball: 'http://hm.cyan.com/1.0.0.tgz',
            integrity: '---',
            fileCount: 1,
            unpackedSize: 1,
          },
        },
        '1.0.1': {},
      },
    },
    brown: {
      name: 'brown',
    },
    yellow: {
      _id: 'yellow',
      name: 'yellow',
      author: {
        name: 'foo',
        email: 'foo@yellow.com',
        twitter: 'foo',
      },
      readme: 'a very useful readme',
      versions: {
        '1.0.0': {
          version: '1.0.0',
          author: 'claudia',
          readme: 'a very useful readme',
          maintainers: [
            { name: 'claudia', email: 'c@yellow.com', twitter: 'cyellow' },
            { name: 'isaacs', email: 'i@yellow.com', twitter: 'iyellow' },
          ],
        },
        '1.0.1': {
          version: '1.0.1',
          author: 'claudia',
        },
        '1.0.2': {
          version: '1.0.2',
          author: 'claudia',
        },
      },
    },
    purple: {
      name: 'purple',
      versions: {
        '1.0.0': {
          foo: 1,
          maintainers: [
            { name: 'claudia' },
          ],
        },
        '1.0.1': {},
      },
    },
    green: {
      _id: 'green',
      name: 'green',
      'dist-tags': {
        latest: '1.0.0',
      },
      maintainers: [
        { name: 'claudia', email: 'c@yellow.com', twitter: 'cyellow' },
        { name: 'isaacs', email: 'i@yellow.com', twitter: 'iyellow' },
      ],
      keywords: ['colors', 'green', 'crayola'],
      versions: {
        '1.0.0': {
          _id: 'green',
          version: '1.0.0',
          description: 'green is a very important color',
          bugs: {
            url: 'http://bugs.green.com',
          },
          deprecated: true,
          repository: {
            url: 'http://repository.green.com',
          },
          license: { type: 'ACME' },
          bin: {
            green: 'bin/green.js',
          },
          dependencies: {
            red: '1.0.0',
            yellow: '1.0.0',
          },
          dist: {
            shasum: '123',
            tarball: 'http://hm.green.com/1.0.0.tgz',
            integrity: '---',
            fileCount: 1,
            unpackedSize: 1,
          },
        },
        '1.0.1': {},
      },
    },
    black: {
      name: 'black',
      'dist-tags': {
        latest: '1.0.0',
      },
      versions: {
        '1.0.0': {
          version: '1.0.0',
          bugs: 'http://bugs.black.com',
          license: {},
          dependencies: (() => {
            const deps = {}
            for (let i = 0; i < 25; i++)
              deps[i] = '1.0.0'

            return deps
          })(),
          dist: {
            shasum: '123',
            tarball: 'http://hm.black.com/1.0.0.tgz',
            integrity: '---',
            fileCount: 1,
            unpackedSize: 1,
          },
        },
        '1.0.1': {},
      },
    },
    pink: {
      name: 'pink',
      'dist-tags': {
        latest: '1.0.0',
      },
      versions: {
        '1.0.0': {
          version: '1.0.0',
          maintainers: [
            { name: 'claudia', url: 'http://c.pink.com' },
            { name: 'isaacs', url: 'http://i.pink.com' },
          ],
          repository: 'http://repository.pink.com',
          license: {},
          dist: {
            shasum: '123',
            tarball: 'http://hm.pink.com/1.0.0.tgz',
            integrity: '---',
            fileCount: 1,
            unpackedSize: 1,
          },
        },
        '1.0.1': {},
      },
    },
    orange: {
      name: 'orange',
      'dist-tags': {
        latest: '1.0.0',
      },
      versions: {
        '1.0.0': {
          version: '1.0.0',
          homepage: 'http://hm.orange.com',
          license: {},
          dist: {
            shasum: '123',
            tarball: 'http://hm.orange.com/1.0.0.tgz',
            integrity: '---',
            fileCount: 1,
            unpackedSize: 1,
          },
        },
        '1.0.1': {},
      },
    },
  }
  if (nv.type === 'git')
    return mocks[nv.hosted.project]
  return mocks[nv.name]
}

t.beforeEach(cleanLogs)

t.test('should log package info', async t => {
  const View = t.mock('../../../lib/commands/view.js', {
    pacote: {
      packument,
    },
  })
  const npm = mockNpm({
    config: { unicode: false },
  })
  const view = new View(npm)

  const ViewJson = t.mock('../../../lib/commands/view.js', {
    pacote: {
      packument,
    },
  })
  const jsonNpm = mockNpm({
    config: {
      json: true,
      tag: 'latest',
    },
  })
  const viewJson = new ViewJson(jsonNpm)

  const ViewUnicode = t.mock('../../../lib/commands/view.js', {
    pacote: {
      packument,
    },
  })
  const unicodeNpm = mockNpm({
    config: { unicode: true },
  })
  const viewUnicode = new ViewUnicode(unicodeNpm)

  t.test('package from git', async t => {
    await view.exec(['https://github.com/npm/green'])
    t.matchSnapshot(logs)
  })

  t.test('package with license, bugs, repository and other fields', async t => {
    await view.exec(['green@1.0.0'])
    t.matchSnapshot(logs)
  })

  t.test('package with more than 25 deps', async t => {
    await view.exec(['black@1.0.0'])
    t.matchSnapshot(logs)
  })

  t.test('package with maintainers info as object', async t => {
    await view.exec(['pink@1.0.0'])
    t.matchSnapshot(logs)
  })

  t.test('package with homepage', async t => {
    await view.exec(['orange@1.0.0'])
    t.matchSnapshot(logs)
  })

  t.test('package with no versions', async t => {
    await view.exec(['brown'])
    t.equal(logs, '', 'no info to display')
  })

  t.test('package with no repo or homepage', async t => {
    await view.exec(['blue@1.0.0'])
    t.matchSnapshot(logs)
  })

  t.test('package with semver range', async t => {
    await view.exec(['blue@^1.0.0'])
    t.matchSnapshot(logs)
  })

  t.test('package with no modified time', async t => {
    await viewUnicode.exec(['cyan@1.0.0'])
    t.matchSnapshot(logs)
  })

  t.test('package with --json and semver range', async t => {
    await viewJson.exec(['cyan@^1.0.0'])
    t.matchSnapshot(logs)
  })

  t.test('package with --json and no versions', async t => {
    await viewJson.exec(['brown'])
    t.equal(logs, '', 'no info to display')
  })
})

t.test('should log info of package in current working dir', async t => {
  const testDir = t.testdir({
    'package.json': JSON.stringify({
      name: 'blue',
      version: '1.0.0',
    }, null, 2),
  })

  const View = t.mock('../../../lib/commands/view.js', {
    pacote: {
      packument,
    },
  })
  const npm = mockNpm({
    prefix: testDir,
    config: {
      tag: '1.0.0',
    },
  })
  const view = new View(npm)

  t.test('specific version', async t => {
    await view.exec(['.@1.0.0'])
    t.matchSnapshot(logs)
  })

  t.test('non-specific version', async t => {
    await view.exec(['.'])
    t.matchSnapshot(logs)
  })
})

t.test('should log info by field name', async t => {
  const ViewJson = t.mock('../../../lib/commands/view.js', {
    pacote: {
      packument,
    },
  })
  const jsonNpm = mockNpm({
    config: {
      tag: 'latest',
      json: true,
    },
  })

  const viewJson = new ViewJson(jsonNpm)

  const View = t.mock('../../../lib/commands/view.js', {
    pacote: {
      packument,
    },
  })
  const npm = mockNpm()
  const view = new View(npm)

  t.test('readme', async t => {
    await view.exec(['yellow@1.0.0', 'readme'])
    t.matchSnapshot(logs)
  })

  t.test('several fields', async t => {
    await viewJson.exec(['yellow@1.0.0', 'name', 'version', 'foo[bar]'])
    t.matchSnapshot(logs)
  })

  t.test('several fields with several versions', async t => {
    await view.exec(['yellow@1.x.x', 'author'])
    t.matchSnapshot(logs)
  })

  t.test('nested field with brackets', async t => {
    await viewJson.exec(['orange@1.0.0', 'dist[shasum]'])
    t.matchSnapshot(logs)
  })

  t.test('maintainers with email', async t => {
    await viewJson.exec(['yellow@1.0.0', 'maintainers', 'name'])
    t.matchSnapshot(logs)
  })

  t.test('maintainers with url', async t => {
    await viewJson.exec(['pink@1.0.0', 'maintainers'])
    t.matchSnapshot(logs)
  })

  t.test('unknown nested field ', async t => {
    await view.exec(['yellow@1.0.0', 'dist.foobar'])
    t.equal(logs, '', 'no info to display')
  })

  t.test('array field - 1 element', async t => {
    await view.exec(['purple@1.0.0', 'maintainers.name'])
    t.matchSnapshot(logs)
  })

  t.test('array field - 2 elements', async t => {
    await view.exec(['yellow@1.x.x', 'maintainers.name'])
    t.matchSnapshot(logs)
  })
})

t.test('throw error if global mode', async t => {
  const View = t.mock('../../../lib/commands/view.js')
  const npm = mockNpm({
    config: {
      global: true,
      tag: 'latest',
    },
  })
  const view = new View(npm)
  await t.rejects(
    view.exec([]),
    /Cannot use view command in global mode./
  )
})

t.test('throw ENOENT error if package.json missing', async t => {
  const testDir = t.testdir({})

  const View = t.mock('../../../lib/commands/view.js')
  const npm = mockNpm({
    prefix: testDir,
  })
  const view = new View(npm)
  await t.rejects(
    view.exec([]),
    { code: 'ENOENT' }
  )
})

t.test('throw EJSONPARSE error if package.json not json', async t => {
  const testDir = t.testdir({
    'package.json': 'not json, nope, not even a little bit!',
  })

  const View = t.mock('../../../lib/commands/view.js')
  const npm = mockNpm({
    prefix: testDir,
  })
  const view = new View(npm)
  await t.rejects(
    view.exec([]),
    { code: 'EJSONPARSE' }
  )
})

t.test('throw error if package.json has no name', async t => {
  const testDir = t.testdir({
    'package.json': '{}',
  })

  const View = t.mock('../../../lib/commands/view.js')
  const npm = mockNpm({
    prefix: testDir,
  })
  const view = new View(npm)
  await t.rejects(
    view.exec([]),
    /Invalid package.json, no "name" field/
  )
})

t.test('throws when unpublished', async t => {
  const View = t.mock('../../../lib/commands/view.js', {
    pacote: {
      packument,
    },
  })
  const npm = mockNpm({
    config: {
      tag: '1.0.1',
    },
  })
  const view = new View(npm)
  await t.rejects(
    view.exec(['red']),
    { code: 'E404'}
  )
})

t.test('workspaces', async t => {
  t.beforeEach(() => {
    warnMsg = undefined
    config.json = false
  })
  const testDir = t.testdir({
    'package.json': JSON.stringify({
      name: 'workspaces-test-package',
      version: '1.2.3',
      workspaces: ['test-workspace-a', 'test-workspace-b'],
    }),
    'test-workspace-a': {
      'package.json': JSON.stringify({
        name: 'green',
        version: '1.2.3',
      }),
    },
    'test-workspace-b': {
      'package.json': JSON.stringify({
        name: 'orange',
        version: '1.2.3',
      }),
    },
  })
  const View = t.mock('../../../lib/commands/view.js', {
    pacote: {
      packument,
    },
  })
  const config = {
    unicode: false,
    tag: 'latest',
  }
  let warnMsg
  const npm = mockNpm({
    log: {
      warn: (msg) => {
        warnMsg = msg
      },
    },
    config,
    localPrefix: testDir,
  })
  const view = new View(npm)

  t.test('all workspaces', async t => {
    await view.execWorkspaces([], [])
    t.matchSnapshot(logs)
  })

  t.test('one specific workspace', async t => {
    await view.execWorkspaces([], ['green'])
    t.matchSnapshot(logs)
  })

  t.test('all workspaces --json', async t => {
    config.json = true
    await view.execWorkspaces([], [])
    t.matchSnapshot(logs)
  })

  t.test('all workspaces single field', async t => {
    await view.execWorkspaces(['.', 'name'], [])
    t.matchSnapshot(logs)
  })

  t.test('all workspaces nonexistent field', async t => {
    await view.execWorkspaces(['.', 'foo'], [])
    t.matchSnapshot(logs)
  })

  t.test('all workspaces nonexistent field --json', async t => {
    config.json = true
    await view.execWorkspaces(['.', 'foo'], [])
    t.matchSnapshot(logs)
  })

  t.test('all workspaces single field --json', async t => {
    config.json = true
    await view.execWorkspaces(['.', 'name'], [])
    t.matchSnapshot(logs)
  })

  t.test('single workspace --json', async t => {
    config.json = true
    await view.execWorkspaces([], ['green'])
    t.matchSnapshot(logs)
  })

  t.test('remote package name', async t => {
    await view.execWorkspaces(['pink'], [])
    t.matchSnapshot(warnMsg)
    t.matchSnapshot(logs)
  })
})

t.test('completion', async t => {
  const View = t.mock('../../../lib/commands/view.js', {
    pacote: {
      packument,
    },
  })
  const npm = mockNpm({
    config: {
      tag: '1.0.1',
    },
  })
  const view = new View(npm)
  const res = await view.completion({
    conf: { argv: { remain: ['npm', 'view', 'green@1.0.0'] } },
  })
  t.ok(res, 'returns back fields')
})

t.test('no registry completion', async t => {
  const View = t.mock('../../../lib/commands/view.js')
  const npm = mockNpm({
    config: {
      tag: '1.0.1',
    },
  })
  const view = new View(npm)
  const res = await view.completion({conf: { argv: { remain: ['npm', 'view'] } } })
  t.notOk(res, 'there is no package completion')
  t.end()
})
