const requireInject = require('require-inject')
const { test } = require('tap')

let log = ''
let warn = ''

const _flatOptions = {
  ssoType: 'oauth'
}
const token = '24528a24f240'
const SSO_URL = 'https://registry.npmjs.org/{SSO_URL}'
const profile = {}
const npmFetch = {}
const sso = requireInject('../../../lib/auth/sso.js', {
  npmlog: {
    info: (...msgs) => {
      log += msgs.join(' ') + '\n'
    },
    warn: (...msgs) => {
      warn += msgs.join(' ')
    }
  },
  'npm-profile': profile,
  'npm-registry-fetch': npmFetch,
  '../../../lib/npm.js': {
    flatOptions: _flatOptions
  },
  '../../../lib/utils/open-url.js': (url, msg, cb) => {
    if (url) {
      cb()
    } else {
      cb(Object.assign(
        new Error('failed open url'),
        { code: 'ERROR' }
      ))
    }
  },
  '../../../lib/utils/otplease.js': (opts, fn) => {
    if (opts) {
      return fn({ ...opts, otp: '1234' })
    } else {
      throw Object.assign(
        new Error('failed retrieving otp'),
        { code: 'ERROR' }
      )
    }
  }
})

test('empty login', async (t) => {
  _flatOptions.ssoType = false

  await t.rejects(
    sso({}),
    { message: 'Missing option: sso-type' },
    'should throw if no sso-type defined in flatOptions'
  )

  t.equal(
    warn,
    'deprecated SSO --auth-type is deprecated',
    'should print deprecation warning'
  )

  _flatOptions.ssoType = 'oauth'
  log = ''
  warn = ''
})

test('simple login', async (t) => {
  t.plan(6)

  profile.loginCouch = (username, password, opts) => {
    t.equal(username, 'npm_oauth_auth_dummy_user', 'should use dummy user')
    t.equal(password, 'placeholder', 'should use dummy password')
    t.deepEqual(
      opts,
      {
        creds: {},
        otp: '1234',
        registry: 'https://registry.npmjs.org/',
        scope: '',
        ssoType: 'oauth'
      },
      'should use dummy password'
    )

    return { token, sso: SSO_URL }
  }
  npmFetch.json = () => Promise.resolve({ username: 'foo' })

  const {
    message,
    newCreds
  } = await sso({
    creds: {},
    registry: 'https://registry.npmjs.org/',
    scope: ''
  })

  t.equal(
    message,
    'Logged in as foo on https://registry.npmjs.org/.',
    'should have correct message result'
  )

  t.equal(
    log,
    'adduser Polling for validated SSO session\nadduser Authorized user foo\n',
    'should have correct logged info msg'
  )

  t.deepEqual(
    newCreds,
    { token },
    'should return expected resulting credentials'
  )

  log = ''
  warn = ''
  delete profile.loginCouch
  delete npmFetch.json
})

test('polling retry', async (t) => {
  t.plan(3)

  profile.loginCouch = () => ({ token, sso: SSO_URL })
  npmFetch.json = () => {
    // assert expected values during retry
    npmFetch.json = (url, { registry, forceAuth: { token: expected } }) => {
      t.equal(
        url,
        '/-/whoami',
        'should reach for expected endpoint'
      )

      t.equal(
        registry,
        'https://registry.npmjs.org/',
        'should use expected registry value'
      )

      t.equal(
        expected,
        token,
        'should use expected token retrieved from initial loginCouch'
      )

      return Promise.resolve({ username: 'foo' })
    }

    // initial fetch returns retry code
    return Promise.reject(Object.assign(
      new Error('nothing yet'),
      { code: 'E401' }
    ))
  }

  await sso({
    creds: {},
    registry: 'https://registry.npmjs.org/',
    scope: ''
  })

  log = ''
  warn = ''
  delete profile.loginCouch
  delete npmFetch.json
})

test('polling error', async (t) => {
  profile.loginCouch = () => ({ token, sso: SSO_URL })
  npmFetch.json = () => Promise.reject(Object.assign(
    new Error('unknown error'),
    { code: 'ERROR' }
  ))

  await t.rejects(
    sso({
      creds: {},
      registry: 'https://registry.npmjs.org/',
      scope: ''
    }),
    { message: 'unknown error', code: 'ERROR' },
    'should throw unknown error'
  )

  log = ''
  warn = ''
  delete profile.loginCouch
  delete npmFetch.json
})

test('no token retrieved from loginCouch', async (t) => {
  profile.loginCouch = () => ({})

  await t.rejects(
    sso({
      creds: {},
      registry: 'https://registry.npmjs.org/',
      scope: ''
    }),
    { message: 'no SSO token returned' },
    'should throw no SSO token returned error'
  )

  log = ''
  warn = ''
  delete profile.loginCouch
})

test('no sso url retrieved from loginCouch', async (t) => {
  profile.loginCouch = () => Promise.resolve({ token })

  await t.rejects(
    sso({
      creds: {},
      registry: 'https://registry.npmjs.org/',
      scope: ''
    }),
    { message: 'no SSO URL returned by services' },
    'should throw no SSO url returned error'
  )

  log = ''
  warn = ''
  delete profile.loginCouch
})

test('scoped login', async (t) => {
  profile.loginCouch = () => ({ token, sso: SSO_URL })
  npmFetch.json = () => Promise.resolve({ username: 'foo' })

  const {
    message,
    newCreds
  } = await sso({
    creds: {},
    registry: 'https://diff-registry.npmjs.org/',
    scope: 'myscope'
  })

  t.equal(
    message,
    'Logged in as foo to scope myscope on https://diff-registry.npmjs.org/.',
    'should have correct message result'
  )

  t.equal(
    log,
    'adduser Polling for validated SSO session\nadduser Authorized user foo\n',
    'should have correct logged info msg'
  )

  t.deepEqual(
    newCreds,
    { token },
    'should return expected resulting credentials'
  )

  log = ''
  warn = ''
  delete profile.loginCouch
  delete npmFetch.json
})
