const npmlog = require('npmlog')
const procLog = require('../../lib/utils/proc-log-listener.js')
procLog.reset()

const realLog = {}
for (const level in npmlog.levels)
  realLog[level] = npmlog[level]

const { title, execPath } = process

const RealMockNpm = (t, otherMocks = {}) => {
  t.afterEach(() => {
    outputs.length = 0
    logs.length = 0
  })
  t.teardown(() => {
    npm.perfStop()
    npmlog.record.length = 0
    for (const level in npmlog.levels)
      npmlog[level] = realLog[level]
    procLog.reset()
    process.title = title
    process.execPath = execPath
    delete process.env.npm_command
    delete process.env.COLOR
  })
  const logs = []
  const outputs = []
  const joinedOutput = () => {
    return outputs.map(o => o.join(' ')).join('\n')
  }
  const npm = t.mock('../../lib/npm.js', otherMocks)
  const command = async (command, args = []) => {
    return new Promise((resolve, reject) => {
      npm.commands[command](args, err => {
        if (err)
          return reject(err)
        return resolve()
      })
    })
  }
  for (const level in npmlog.levels) {
    npmlog[level] = (...msg) => {
      logs.push([level, ...msg])

      const l = npmlog.level
      npmlog.level = 'silent'
      realLog[level](...msg)
      npmlog.level = l
    }
  }
  npm.output = (...msg) => outputs.push(msg)
  return { npm, logs, outputs, command, joinedOutput }
}

const realConfig = require('../../lib/utils/config')

// Basic npm fixture that you can give a config object that acts like
// npm.config You still need a separate flatOptions. Tests should migrate to
// using the real npm mock above
class MockNpm {
  constructor (base = {}) {
    this._mockOutputs = []
    this.isMockNpm = true
    this.base = base

    const config = base.config || {}

    for (const attr in base) {
      if (attr !== 'config') {
        this[attr] = base[attr]
      }
    }

    this.flatOptions = base.flatOptions || {}
    this.config = {
      // for now just set `find` to what config.find should return
      // this works cause `find` is not an existing config entry
      find: (k) => ({...realConfig.defaults, ...config})[k],
      get: (k) => ({...realConfig.defaults, ...config})[k],
      set: (k, v) => config[k] = v,
      list: [{ ...realConfig.defaults, ...config}]
    }
    if (!this.log) {
      this.log = {
        clearProgress: () => {},
        disableProgress: () => {},
        enableProgress: () => {},
        http: () => {},
        info: () => {},
        levels: [],
        notice: () => {},
        pause: () => {},
        silly: () => {},
        verbose: () => {},
        warn: () => {},
      }
    }
  }

  output(...msg) {
    if (this.base.output)
      return this.base.output(msg)
    this._mockOutputs.push(msg)
  }
}

const FakeMockNpm = (base = {}) => {
    return new MockNpm(base)
}

module.exports = {
  fake: FakeMockNpm,
  real: RealMockNpm
}
