'use strict'
const helper = require('./test-helper')
const utils = require('./../../lib/utils')
const defaults = require('./../../lib').defaults
const assert = require('assert')
const suite = new helper.Suite()
const test = suite.test.bind(suite)

test('ensure types is exported on root object', function () {
  const pg = require('../../lib')
  assert(pg.types)
  assert(pg.types.getTypeParser)
  assert(pg.types.setTypeParser)
})

test('normalizing query configs', function () {
  let config
  const callback = function () {}

  config = utils.normalizeQueryConfig({ text: 'TEXT' })
  assert.same(config, { text: 'TEXT' })

  config = utils.normalizeQueryConfig({ text: 'TEXT' }, [10])
  assert.deepEqual(config, { text: 'TEXT', values: [10] })

  config = utils.normalizeQueryConfig({ text: 'TEXT', values: [10] })
  assert.deepEqual(config, { text: 'TEXT', values: [10] })

  config = utils.normalizeQueryConfig('TEXT', [10], callback)
  assert.deepEqual(config, { text: 'TEXT', values: [10], callback: callback })

  config = utils.normalizeQueryConfig({ text: 'TEXT', values: [10] }, callback)
  assert.deepEqual(config, { text: 'TEXT', values: [10], callback: callback })
})

test('prepareValues: buffer prepared properly', function () {
  const buf = Buffer.from('quack')
  const out = utils.prepareValue(buf)
  assert.strictEqual(buf, out)
})

test('prepareValues: Uint8Array prepared properly', function () {
  const buf = new Uint8Array([1, 2, 3]).subarray(1, 2)
  const out = utils.prepareValue(buf)
  assert.ok(Buffer.isBuffer(out))
  assert.equal(out.length, 1)
  assert.deepEqual(out[0], 2)
})

test('prepareValues: date prepared properly', function () {
  helper.setTimezoneOffset(-330)

  const date = new Date(2014, 1, 1, 11, 11, 1, 7)
  const out = utils.prepareValue(date)
  assert.strictEqual(out, '2014-02-01T11:11:01.007+05:30')

  helper.resetTimezoneOffset()
})

test('prepareValues: date prepared properly as UTC', function () {
  defaults.parseInputDatesAsUTC = true

  // make a date in the local timezone that represents a specific UTC point in time
  const date = new Date(Date.UTC(2014, 1, 1, 11, 11, 1, 7))
  const out = utils.prepareValue(date)
  assert.strictEqual(out, '2014-02-01T11:11:01.007+00:00')

  defaults.parseInputDatesAsUTC = false
})

test('prepareValues: BC date prepared properly', function () {
  helper.setTimezoneOffset(-330)

  const date = new Date(-3245, 1, 1, 11, 11, 1, 7)
  const out = utils.prepareValue(date)
  assert.strictEqual(out, '3246-02-01T11:11:01.007+05:30 BC')

  helper.resetTimezoneOffset()
})

test('prepareValues: 1 BC date prepared properly', function () {
  helper.setTimezoneOffset(-330)

  // can't use the multi-argument constructor as year 0 would be interpreted as 1900
  const date = new Date('0000-02-01T11:11:01.007')
  const out = utils.prepareValue(date)
  assert.strictEqual(out, '0001-02-01T11:11:01.007+05:30 BC')

  helper.resetTimezoneOffset()
})

test('prepareValues: undefined prepared properly', function () {
  const out = utils.prepareValue(void 0)
  assert.strictEqual(out, null)
})

test('prepareValue: null prepared properly', function () {
  const out = utils.prepareValue(null)
  assert.strictEqual(out, null)
})

test('prepareValue: true prepared properly', function () {
  const out = utils.prepareValue(true)
  assert.strictEqual(out, 'true')
})

test('prepareValue: false prepared properly', function () {
  const out = utils.prepareValue(false)
  assert.strictEqual(out, 'false')
})

test('prepareValue: number prepared properly', function () {
  const out = utils.prepareValue(3.042)
  assert.strictEqual(out, '3.042')
})

test('prepareValue: string prepared properly', function () {
  const out = utils.prepareValue('big bad wolf')
  assert.strictEqual(out, 'big bad wolf')
})

test('prepareValue: simple array prepared properly', function () {
  const out = utils.prepareValue([1, null, 3, undefined, [5, 6, 'squ,awk']])
  assert.strictEqual(out, '{"1",NULL,"3",NULL,{"5","6","squ,awk"}}')
})

test('prepareValue: complex array prepared properly', function () {
  const out = utils.prepareValue([{ x: 42 }, { y: 84 }])
  assert.strictEqual(out, '{"{\\"x\\":42}","{\\"y\\":84}"}')
})

test('prepareValue: date array prepared properly', function () {
  helper.setTimezoneOffset(-330)

  const date = new Date(2014, 1, 1, 11, 11, 1, 7)
  const out = utils.prepareValue([date])
  assert.strictEqual(out, '{"2014-02-01T11:11:01.007+05:30"}')

  helper.resetTimezoneOffset()
})

test('prepareValue: arbitrary objects prepared properly', function () {
  const out = utils.prepareValue({ x: 42 })
  assert.strictEqual(out, '{"x":42}')
})

test('prepareValue: objects with simple toPostgres prepared properly', function () {
  const customType = {
    toPostgres: function () {
      return 'zomgcustom!'
    },
  }
  const out = utils.prepareValue(customType)
  assert.strictEqual(out, 'zomgcustom!')
})

test('prepareValue: buffer array prepared properly', function () {
  const buffer1 = Buffer.from('dead', 'hex')
  const buffer2 = Buffer.from('beef', 'hex')
  const out = utils.prepareValue([buffer1, buffer2])
  assert.strictEqual(out, '{\\\\xdead,\\\\xbeef}')
})

test('prepareValue: Uint8Array array prepared properly', function () {
  const buffer1 = Uint8Array.from(Buffer.from('dead', 'hex'))
  const buffer2 = Uint8Array.from(Buffer.from('beef', 'hex'))
  const out = utils.prepareValue([buffer1, buffer2])
  assert.strictEqual(out, '{\\\\xdead,\\\\xbeef}')
})

test('prepareValue: objects with complex toPostgres prepared properly', function () {
  const customType = {
    toPostgres: function () {
      return [1, 2]
    },
  }
  const out = utils.prepareValue(customType)
  assert.strictEqual(out, '{"1","2"}')
})

test('prepareValue: objects with toPostgres receive prepareValue', function () {
  const customRange = {
    lower: {
      toPostgres: function () {
        return 5
      },
    },
    upper: {
      toPostgres: function () {
        return 10
      },
    },
    toPostgres: function (prepare) {
      return '[' + prepare(this.lower) + ',' + prepare(this.upper) + ']'
    },
  }
  const out = utils.prepareValue(customRange)
  assert.strictEqual(out, '[5,10]')
})

test('prepareValue: objects with circular toPostgres rejected', function () {
  const customType = {
    toPostgres: function () {
      return {
        toPostgres: function () {
          return customType
        },
      }
    },
  }

  // can't use `assert.throws` since we need to distinguish circular reference
  // errors from call stack exceeded errors
  try {
    utils.prepareValue(customType)
  } catch (e) {
    assert.ok(e.message.match(/circular/), 'Expected circular reference error but got ' + e)
    return
  }
  throw new Error('Expected prepareValue to throw exception')
})

test('prepareValue: can safely be used to map an array of values including those with toPostgres functions', function () {
  const customType = {
    toPostgres: function () {
      return 'zomgcustom!'
    },
  }
  const values = [1, 'test', customType]
  const out = values.map(utils.prepareValue)
  assert.deepEqual(out, [1, 'test', 'zomgcustom!'])
})

const testEscapeLiteral = function (testName, input, expected) {
  test(`escapeLiteral: ${testName}`, function () {
    const actual = utils.escapeLiteral(input)
    assert.equal(expected, actual)
  })
}

testEscapeLiteral('no special characters', 'hello world', "'hello world'")

testEscapeLiteral('contains double quotes only', 'hello " world', "'hello \" world'")

testEscapeLiteral('contains single quotes only', "hello ' world", "'hello '' world'")

testEscapeLiteral('contains backslashes only', 'hello \\ world', " E'hello \\\\ world'")

testEscapeLiteral('contains single quotes and double quotes', 'hello \' " world', "'hello '' \" world'")

testEscapeLiteral('date', new Date(), "''")

testEscapeLiteral('null', null, "''")

testEscapeLiteral('undefined', undefined, "''")

testEscapeLiteral('boolean', false, "''")

testEscapeLiteral('number', 1, "''")

testEscapeLiteral('number', 1, "''")

testEscapeLiteral('boolean', true, "''")

testEscapeLiteral('array', [1, 2, 3], "''")

testEscapeLiteral('object', { x: 42 }, "''")

testEscapeLiteral('contains double quotes and backslashes', 'hello \\ " world', " E'hello \\\\ \" world'")

testEscapeLiteral('contains single quotes and backslashes', "hello \\ ' world", " E'hello \\\\ '' world'")

testEscapeLiteral(
  'contains single quotes, double quotes, and backslashes',
  'hello \\ \' " world',
  " E'hello \\\\ '' \" world'"
)

const testEscapeIdentifier = function (testName, input, expected) {
  test(testName, function () {
    const actual = utils.escapeIdentifier(input)
    assert.equal(expected, actual)
  })
}

testEscapeIdentifier('escapeIdentifier: no special characters', 'hello world', '"hello world"')

testEscapeIdentifier('escapeIdentifier: contains double quotes only', 'hello " world', '"hello "" world"')

testEscapeIdentifier('escapeIdentifier: contains single quotes only', "hello ' world", '"hello \' world"')

testEscapeIdentifier('escapeIdentifier: contains backslashes only', 'hello \\ world', '"hello \\ world"')

testEscapeIdentifier(
  'escapeIdentifier: contains single quotes and double quotes',
  'hello \' " world',
  '"hello \' "" world"'
)

testEscapeIdentifier(
  'escapeIdentifier: contains double quotes and backslashes',
  'hello \\ " world',
  '"hello \\ "" world"'
)

testEscapeIdentifier(
  'escapeIdentifier: contains single quotes and backslashes',
  "hello \\ ' world",
  '"hello \\ \' world"'
)

testEscapeIdentifier(
  'escapeIdentifier: contains single quotes, double quotes, and backslashes',
  'hello \\ \' " world',
  '"hello \\ \' "" world"'
)
