/* eslint-env mocha */
'use strict'

const chai = require('chai')
chai.use(require('dirty-chai'))
chai.use(require('chai-checkmark'))
const expect = chai.expect
const sinon = require('sinon')
const series = require('async/series')
const createNode = require('./utils/create-node')

describe('libp2p state machine (fsm)', () => {
  describe('starting and stopping', () => {
    let node
    beforeEach((done) => {
      createNode([], {
        config: {
          dht: {
            enabled: false
          }
        }
      }, (err, _node) => {
        node = _node
        done(err)
      })
    })
    afterEach(() => {
      node.removeAllListeners()
      sinon.restore()
    })
    after((done) => {
      node.stop(done)
      node = null
    })

    it('should be able to start and stop several times', (done) => {
      node.on('start', (err) => {
        expect(err).to.not.exist().mark()
      })
      node.on('stop', (err) => {
        expect(err).to.not.exist().mark()
      })

      expect(4).checks(done)

      series([
        (cb) => node.start(cb),
        (cb) => node.stop(cb),
        (cb) => node.start(cb),
        (cb) => node.stop(cb)
      ], () => {})
    })

    it('should noop when stopping a stopped node', (done) => {
      node.once('start', node.stop)
      node.once('stop', () => {
        node.state.on('STOPPING', () => {
          throw new Error('should not stop a stopped node')
        })
        node.once('stop', done)

        // stop the stopped node
        node.stop()
      })
      node.start()
    })

    it('should callback with an error when it occurs on stop', (done) => {
      const error = new Error('some error starting')
      node.once('start', () => {
        node.once('error', (err) => {
          expect(err).to.eql(error).mark()
        })
        node.stop((err) => {
          expect(err).to.eql(error).mark()
        })
      })

      expect(2).checks(done)

      sinon.stub(node._switch, 'stop').callsArgWith(0, error)
      node.start()
    })

    it('should noop when starting a started node', (done) => {
      node.once('start', () => {
        node.state.on('STARTING', () => {
          throw new Error('should not start a started node')
        })
        node.once('start', () => {
          node.once('stop', done)
          node.stop()
        })

        // start the started node
        node.start()
      })
      node.start()
    })

    it('should error on start with no transports', (done) => {
      let transports = node._modules.transport
      node._modules.transport = null

      node.on('stop', () => {
        node._modules.transport = transports
        expect(node._modules.transport).to.exist().mark()
      })
      node.on('error', (err) => {
        expect(err).to.exist().mark()
      })
      node.on('start', () => {
        throw new Error('should not start')
      })

      expect(2).checks(done)

      node.start()
    })

    it('should not start if the switch fails to start', (done) => {
      const error = new Error('switch didnt start')
      const stub = sinon.stub(node._switch, 'start')
        .callsArgWith(0, error)

      node.on('stop', () => {
        expect(stub.calledOnce).to.eql(true).mark()
        stub.restore()
      })
      node.on('error', (err) => {
        expect(err).to.eql(error).mark()
      })
      node.on('start', () => {
        throw new Error('should not start')
      })

      expect(3).checks(done)

      node.start((err) => {
        expect(err).to.eql(error).mark()
      })
    })

    it('should not dial when the node is stopped', (done) => {
      node.on('stop', () => {
        node.dial(null, (err) => {
          expect(err).to.exist()
          expect(err.code).to.eql('ERR_NODE_NOT_STARTED')
          done()
        })
      })

      node.stop()
    })

    it('should not dial (fsm) when the node is stopped', (done) => {
      node.on('stop', () => {
        node.dialFSM(null, null, (err) => {
          expect(err).to.exist()
          expect(err.code).to.eql('ERR_NODE_NOT_STARTED')
          done()
        })
      })

      node.stop()
    })
  })
})