import { expect } from 'chai'
import sinon from 'sinon'
import state from '@/store/state'
import showHint, { getHints } from '@/components/SqlEditor/hint'
import CM from 'codemirror'

describe('hint.js', () => {
  afterEach(() => {
    sinon.restore()
  })

  it('Calculates table list for hint', () => {
    // mock store state
    const db = {
      schema: [
        {
          name: 'foo',
          columns: [
            { name: 'fooId', type: 'INTEGER' },
            { name: 'name', type: 'NVARCHAR(20)' }
          ]
        },
        {
          name: 'bar',
          columns: [{ name: 'barId', type: 'INTEGER' }]
        }
      ]
    }
    sinon.stub(state, 'db').value(db)

    // mock showHint and editor
    sinon.stub(CM, 'showHint')
    const editor = {
      getTokenAt() {
        return {
          string: 'SELECT',
          type: 'keyword'
        }
      },
      getCursor: sinon.stub()
    }

    showHint(editor)

    expect(CM.showHint.called).to.equal(true)
    expect(CM.showHint.firstCall.args[2].tables).to.eql({
      foo: ['fooId', 'name'],
      bar: ['barId']
    })
    expect(CM.showHint.firstCall.args[2].defaultTable).to.equal(null)
  })

  it('Add default table if there is only one table in schema', () => {
    // mock store state
    const db = {
      schema: [
        {
          name: 'foo',
          columns: [
            { name: 'fooId', type: 'INTEGER' },
            { name: 'name', type: 'NVARCHAR(20)' }
          ]
        }
      ]
    }
    sinon.stub(state, 'db').value(db)

    // mock showHint and editor
    sinon.stub(CM, 'showHint')
    const editor = {
      getTokenAt() {
        return {
          string: 'SELECT',
          type: 'keyword'
        }
      },
      getCursor: sinon.stub()
    }

    showHint(editor)
    expect(CM.showHint.firstCall.args[2].defaultTable).to.equal('foo')
  })

  it("Doesn't show hint when in string or space, or ';'", () => {
    // mock showHint and editor
    sinon.stub(CM, 'showHint')
    const editor = {
      getTokenAt() {
        return {
          string: 'foo',
          type: 'string'
        }
      },
      getCursor: sinon.stub()
    }

    showHint(editor)
    expect(CM.showHint.called).to.equal(false)
  })

  it("Doesn't show hint after space", () => {
    // mock showHint and editor
    sinon.stub(CM, 'showHint')
    const editor = {
      getTokenAt() {
        return {
          string: ' ',
          type: null
        }
      },
      getCursor: sinon.stub()
    }

    showHint(editor)
    expect(CM.showHint.called).to.equal(false)
  })

  it("Doesn't show hint after ';'", () => {
    // mock showHint and editor
    sinon.stub(CM, 'showHint')
    const editor = {
      getTokenAt() {
        return {
          string: ';',
          type: 'punctuation'
        }
      },
      getCursor: sinon.stub()
    }

    showHint(editor)
    expect(CM.showHint.called).to.equal(false)
  })

  it('getHints returns [ ] if there is only one option and token is completed with this option', () => {
    // mock CM.hint.sql and editor
    sinon.stub(CM.hint, 'sql').returns({
      list: [{ text: 'SELECT' }],
      from: null, // from/to doesn't metter because getRange is mocked
      to: null
    })

    const editor = {
      getRange() {
        return 'select'
      }
    }

    const hints = getHints(editor, {})
    expect(hints.list).to.eql([])
  })

  it(
    'getHints returns [ ] if there is only one string option and token ' +
      'is completed with this option',
    () => {
      // mock CM.hint.sql and editor
      sinon.stub(CM.hint, 'sql').returns({
        list: ['house.name'],
        from: null, // from/to doesn't metter because getRange is mocked
        to: null
      })
      const editor = {
        getRange() {
          return 'house.name'
        }
      }

      const hints = getHints(editor, {})
      expect(hints.list).to.eql([])
    }
  )

  it('getHints returns hints as is when there are more than one option', () => {
    // mock CM.hint.sql and editor
    const list = [{ text: 'SELECT' }, { text: 'ST' }]
    sinon.stub(CM.hint, 'sql').returns({ list, from: null, to: null })
    const editor = {
      getRange() {
        return 'se'
      }
    }

    const hints = getHints(editor, {})
    expect(hints.list).to.eql(list)

    sinon.restore()
  })

  it('getHints returns hints as is when there only one option but the token is not completed', () => {
    // mock CM.hint.sql and editor
    const list = [{ text: 'SELECT' }]
    sinon.stub(CM.hint, 'sql').returns({ list, from: null, to: null })
    const editor = {
      getRange() {
        return 'sele'
      }
    }

    const hints = getHints(editor, {})
    expect(hints.list).to.eql(list)
  })

  it('tables is empty object when schema is null', () => {
    // mock store state
    sinon.stub(state, 'db').value({ schema: null })

    // mock showHint and editor
    sinon.stub(CM, 'showHint')
    const editor = {
      getTokenAt() {
        return {
          string: 'SELECT',
          type: 'keyword'
        }
      },
      getCursor: sinon.stub()
    }

    showHint(editor)
    expect(CM.showHint.called).to.equal(true)
    expect(CM.showHint.firstCall.args[2].tables).to.eql({})
  })
})
