/* global describe, it, beforeEach */
/* eslint-disable no-new */

var assert = require('assert')
var ecdsa = require('../src/ecdsa')
var ecurve = require('ecurve')
var networks = require('../src/networks')
var proxyquire = require('proxyquire')
var sinon = require('sinon')

var BigInteger = require('bigi')
var ECPair = require('../src/ecpair')

var fixtures = require('./fixtures/ecpair.json')

describe('ECPair', function () {
  describe('constructor', function () {
    it('defaults to compressed', function () {
      var keyPair = new ECPair(BigInteger.ONE)

      assert.strictEqual(keyPair.compressed, true)
    })

    it('supports the uncompressed option', function () {
      var keyPair = new ECPair(BigInteger.ONE, null, {
        compressed: false
      })

      assert.strictEqual(keyPair.compressed, false)
    })

    it('supports the network option', function () {
      var keyPair = new ECPair(BigInteger.ONE, null, {
        compressed: false,
        network: networks.testnet
      })

      assert.strictEqual(keyPair.network, networks.testnet)
    })

    it('throws if compressed option is not a bool', function () {
      assert.throws(function () {
        new ECPair(null, null, {
          compressed: 2
        }, /Expected Boolean, got 2/)
      })
    })

    it('throws if public and private key given', function () {
      var qBuffer = new Buffer('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798', 'hex')
      var Q = ecurve.Point.decodeFrom(ECPair.curve, qBuffer)

      assert.throws(function () {
        new ECPair(BigInteger.ONE, Q)
      }, /Unexpected publicKey parameter/)
    })

    it('throws if network is missing pubKeyHash constants', function () {
      assert.throws(function () {
        new ECPair(null, null, {
          network: {}
        }, /Unknown pubKeyHash constants for network/)
      })
    })

    fixtures.valid.forEach(function (f) {
      it('calculates the public point for ' + f.WIF, function () {
        var d = new BigInteger(f.d)
        var keyPair = new ECPair(d, null, {
          compressed: f.compressed
        })

        assert.strictEqual(keyPair.getPublicKeyBuffer().toString('hex'), f.Q)
      })
    })

    fixtures.invalid.constructor.forEach(function (f) {
      it('throws on ' + f.d, function () {
        var d = new BigInteger(f.d)

        assert.throws(function () {
          new ECPair(d)
        }, new RegExp(f.exception))
      })
    })
  })

  describe('getPublicKeyBuffer', function () {
    var keyPair

    beforeEach(function () {
      keyPair = new ECPair(BigInteger.ONE)
    })

    it('wraps Q.getEncoded', sinon.test(function () {
      this.mock(keyPair.Q).expects('getEncoded')
        .once().calledWith(keyPair.compressed)

      keyPair.getPublicKeyBuffer()
    }))
  })

  describe('fromWIF', function () {
    fixtures.valid.forEach(function (f) {
      it('imports ' + f.WIF + ' correctly', function () {
        var keyPair = ECPair.fromWIF(f.WIF)

        assert.strictEqual(keyPair.d.toString(), f.d)
        assert.strictEqual(keyPair.compressed, f.compressed)
        assert.strictEqual(keyPair.network, networks[f.network])
      })
    })

    fixtures.invalid.fromWIF.forEach(function (f) {
      it('throws on ' + f.string, function () {
        assert.throws(function () {
          ECPair.fromWIF(f.string)
        }, new RegExp(f.exception))
      })
    })
  })

  describe('toWIF', function () {
    fixtures.valid.forEach(function (f) {
      it('exports ' + f.WIF + ' correctly', function () {
        var keyPair = ECPair.fromWIF(f.WIF)
        var result = keyPair.toWIF()

        assert.strictEqual(result, f.WIF)
      })
    })
  })

  describe('makeRandom', function () {
    var d = new Buffer('0404040404040404040404040404040404040404040404040404040404040404', 'hex')
    var exWIF = 'KwMWvwRJeFqxYyhZgNwYuYjbQENDAPAudQx5VEmKJrUZcq6aL2pv'

    describe('uses randombytes RNG', function () {
      it('generates a ECPair', function () {
        var stub = { randombytes: function () { return d } }
        var ProxiedECPair = proxyquire('../src/ecpair', stub)

        var keyPair = ProxiedECPair.makeRandom()
        assert.strictEqual(keyPair.toWIF(), exWIF)
      })

      it('passes the options param', sinon.test(function () {
        var options = {
          compressed: true
        }

        // FIXME: waiting on https://github.com/cjohansen/Sinon.JS/issues/613
//        this.mock(ECPair).expects('constructor')
//          .once().calledWith(options)

        ECPair.makeRandom(options)
      }))
    })

    it('allows a custom RNG to be used', function () {
      var keyPair = ECPair.makeRandom({
        rng: function (size) { return d.slice(0, size) }
      })

      assert.strictEqual(keyPair.toWIF(), exWIF)
    })
  })

  describe('getAddress', function () {
    fixtures.valid.forEach(function (f) {
      it('returns ' + f.address + ' for ' + f.WIF, function () {
        var keyPair = ECPair.fromWIF(f.WIF)

        assert.strictEqual(keyPair.getAddress(), f.address)
      })
    })
  })

  describe('ecdsa wrappers', function () {
    var keyPair, hash

    beforeEach(function () {
      keyPair = ECPair.makeRandom()
      hash = new Buffer(32)
    })

    it('uses the secp256k1 curve by default', function () {
      var secp256k1 = ecurve.getCurveByName('secp256k1')

      for (var property in secp256k1) {
        // FIXME: circular structures in ecurve
        if (property === 'G') continue
        if (property === 'infinity') continue

        var actual = ECPair.curve[property]
        var expected = secp256k1[property]

        assert.deepEqual(actual, expected)
      }
    })

    describe('signing', function () {
      it('wraps ecdsa.sign', sinon.test(function () {
        this.mock(ecdsa).expects('sign')
          .once().calledWith(ECPair.curve, hash, keyPair.d)

        keyPair.sign(hash)
      }))

      it('throws if no private key is found', function () {
        keyPair.d = null

        assert.throws(function () {
          keyPair.sign(hash)
        }, /Missing private key/)
      })
    })

    describe('verify', function () {
      var signature

      beforeEach(function () {
        signature = keyPair.sign(hash)
      })

      it('wraps ecdsa.verify', sinon.test(function () {
        this.mock(ecdsa).expects('verify')
          .once().calledWith(ECPair.curve, hash, signature, keyPair.Q)

        keyPair.verify(hash, signature)
      }))
    })
  })
})