'use strict'
var util = require('util')
var test = require('tap').test
var requireInject = require('require-inject')
var dezalgo = require('dezalgo')
var mkdirp = require('mkdirp')
var path = require('path')

test('gently/force', function (t) {
  t.plan(5)

  linkOk(t, 'gently=true, force=false, works', {
    // args
    from: 'wibble',
    to: '/foo/bar/baz',
    gently: true,
    abs: true,
    force: false,

    // expect
    rm: {'/foo/bar/baz': {gently: true}},
    mkdir: {'/foo/bar': true},
    lstat: {'/foo/bar/wibble': {isLink: false}},
    stat: {'/foo/bar/wibble': true},
    symlink: {'/foo/bar/wibble': {'/foo/bar/baz': 'junction'}},
    readlink: {}
  })

  linkNotOk(t, 'gently=true, force=false, does not work', {
    // args
    from: 'wibble',
    to: '/foo/bar/baz',
    gently: true,
    abs: true,
    force: false,

    // expect
    rm: {'/foo/bar/baz': {gently: false}},
    mkdir: {'/foo/bar': true},
    lstat: {'/foo/bar/wibble': {isLink: false}},
    stat: {'/foo/bar/wibble': true},
    symlink: {'/foo/bar/wibble': {'/foo/bar/baz': 'junction'}},
    readlink: {}
  })

  linkOk(t, 'gently=false, force=false, aok', {
    // args
    from: 'wibble',
    to: '/foo/bar/baz',
    gently: false,
    abs: true,
    force: false,

    // expect
    rm: {'/foo/bar/baz': {gently: false}},
    mkdir: {'/foo/bar': true},
    lstat: {'/foo/bar/wibble': {isLink: false}},
    stat: {'/foo/bar/wibble': true},
    symlink: {'/foo/bar/wibble': {'/foo/bar/baz': 'junction'}},
    readlink: {}
  })

  linkOk(t, 'gently=true, force=true, aok', {
    // args
    from: 'wibble',
    to: '/foo/bar/baz',
    gently: true,
    abs: true,
    force: true,

    // expect
    rm: {'/foo/bar/baz': {gently: false}},
    mkdir: {'/foo/bar': true},
    lstat: {'/foo/bar/wibble': {isLink: false}},
    stat: {'/foo/bar/wibble': true},
    symlink: {'/foo/bar/wibble': {'/foo/bar/baz': 'junction'}},
    readlink: {}
  })

  linkOk(t, 'gently=false, force=true, aok', {
    // args
    from: 'wibble',
    to: '/foo/bar/baz',
    gently: false,
    abs: true,
    force: true,

    // expect
    rm: {'/foo/bar/baz': {gently: false}},
    mkdir: {'/foo/bar': true},
    lstat: {'/foo/bar/wibble': {isLink: false}},
    stat: {'/foo/bar/wibble': true},
    symlink: {'/foo/bar/wibble': {'/foo/bar/baz': 'junction'}},
    readlink: {}
  })
})

test('abs, noabs', function (t) {
  t.plan(4)

  linkOk(t, 'abs', {
    // args
    from: 'wibble',
    to: '/foo/bar/baz',
    gently: true,
    abs: true,
    force: false,

    // expect
    rm: {'/foo/bar/baz': {gently: true}},
    mkdir: {'/foo/bar': true},
    lstat: {'/foo/bar/wibble': {isLink: false}},
    stat: {'/foo/bar/wibble': true},
    symlink: {'/foo/bar/wibble': {'/foo/bar/baz': 'junction'}},
    readlink: {}
  })

  linkOk(t, 'relative', {
    // args
    from: 'wibble',
    to: '/foo/bar/baz',
    gently: true,
    abs: false,
    force: false,

    // expect
    rm: {'/foo/bar/baz': {gently: true}},
    mkdir: {'/foo/bar': true},
    lstat: {'/foo/bar/wibble': {isLink: false}},
    stat: {'/foo/bar/wibble': true},
    symlink: {'wibble': {'/foo/bar/baz': 'junction'}},
    readlink: {}
  })

  linkOk(t, 'relative ..', {
    // args
    from: '../wibble/bark/blump',
    to: '/foo/bar/baz',
    gently: true,
    abs: false,
    force: false,

    // expect
    rm: {'/foo/bar/baz': {gently: true}},
    mkdir: {'/foo/bar': true},
    lstat: {'/foo/wibble/bark/blump': {isLink: false}},
    stat: {'/foo/wibble/bark/blump': true},
    symlink: {'../wibble/bark/blump': {'/foo/bar/baz': 'junction'}},
    readlink: {}
  })

  linkOk(t, 'relative .. deep', {
    // args
    from: 'zib/zap/../../../wibble/bark/blump',
    to: '/foo/bar/baz',
    gently: true,
    abs: false,
    force: false,

    // expect
    rm: {'/foo/bar/baz': {gently: true}},
    mkdir: {'/foo/bar': true},
    lstat: {'/foo/wibble/bark/blump': {isLink: false}},
    stat: {'/foo/wibble/bark/blump': true},
    symlink: {'../wibble/bark/blump': {'/foo/bar/baz': 'junction'}},
    readlink: {}
  })
})

function linkOk (t, msg, opts) {
  testLink(opts, function (err) {
    t.ifError(err, msg)
  })
}

function linkNotOk (t, msg, opts) {
  testLink(opts, function (err) {
    t.ok(err, msg)
  })
}

function platformPath (unixPath) {
  if (unixPath[0] === '/') {
    return path.resolve(unixPath)
  } else {
    return path.join.apply(path, unixPath.split('/'))
  }
}

function platformerize (obj) {
  Object.keys(obj).forEach(function (key) {
    var newKey = platformPath(key)
    if (typeof obj[key] === 'object') {
      platformerize(obj[key])
    } else if (typeof obj[key] === 'string') {
      obj[key] = platformPath(obj[key])
    }
    if (newKey !== key) {
      obj[newKey] = obj[key]
      delete obj[key]
    }
  })
}

function testLink (opts, cb) {
  var mkdirpMock = dezalgo(function (dir, cb) {
    if (opts.mkdir[dir]) {
      cb()
    } else {
      cb(new Error('mkdirp failed: ' + util.inspect(dir)))
    }
  })
  // sync version used by istanbul for test coverage
  // we shouldn't have to do this ;.;
  // require-inject and/or instanbul will need patching
  mkdirpMock.sync = mkdirp.sync

  // convert any paths in our opts into platform specific paths, for windows support.
  platformerize(opts)

  var link = requireInject('../../lib/utils/link.js', {
    '../../lib/npm.js': {
      config: {
        get: function (name) {
          if (name !== 'force') return new Error('unknown config key: ' + name)
          return opts.force
        }
      }
    },
    '../../lib/utils/gently-rm.js': dezalgo(function (toRemove, gently, cb) {
      if (opts.rm[toRemove] && opts.rm[toRemove].gently === gently) {
        cb()
      } else {
        cb(new Error('Removing toRemove: ' + util.inspect(toRemove) +
          ' gently: ' + util.inspect(gently) +
          ' not allowed: ' + util.inspect(opts.rm)))
      }
    }),
    'mkdirp': mkdirpMock,
    'graceful-fs': {
      'stat': dezalgo(function (file, cb) {
        if (opts.stat[file]) {
          cb(null, {})
        } else {
          cb(new Error('stat failed for: ' + util.inspect(file)))
        }
      }),
      'lstat': dezalgo(function (file, cb) {
        if (opts.lstat[file]) {
          var linkStat = opts.lstat[file]
          cb(null, {
            isSymbolicLink: function () {
              return linkStat.isLink
            }
          })
        } else {
          cb(new Error('lstat failed for: ' + util.inspect(file)))
        }
      }),
      'symlink': dezalgo(function (from, to, type, cb) {
        if (!cb) {
          cb = type
          type = null
        }
        if (opts.symlink[from] && opts.symlink[from][to] === type) {
          cb()
        } else {
          cb(new Error('symlink failed from: ' + util.inspect(from) + ' to: ' + util.inspect(to) + ' type: ' + util.inspect(type)))
        }
      }),
      'readlink': function (file, cb) {
        if (opts.readlink[file]) {
          cb()
        } else {
          cb(new Error('readlink failed for: ' + util.inspect(file)))
        }
      }
    }
  })
  link(opts.from, opts.to, opts.gently, opts.abs, cb)
}
