import { Eltro as t, assert, stub } from 'eltro'
import fs from 'fs/promises'
import fsSync from 'fs'
import lowdb from '../core/db.mjs'
import Util from '../core/util.mjs'

var util = new Util(import.meta.url)
var logger = {
  info: stub(),
  warn: stub(),
  error: stub(),
}

t.before(function() {
  return Promise.all([
    fs.rm('./test/db_test.json', { force: true }),
    fs.rm('./test/null', { force: true }),
  ])
})

t.afterEach(function() {
  return Promise.all([
    fs.rm('./test/db_test.json', { force: true }),
    fs.rm('./test/null', { force: true }),
  ])
})

t.test('Should auto create file with some defaults', async function() {
  await assert.isRejected(fs.stat('./test/db_test.json'))

  let db = await lowdb({}, logger, util.getPathFromRoot('./db_test.json'))

  let stat = await fs.stat('./test/db_test.json')
  assert.ok(stat.size > 0)

  assert.ok(db.data.core)
  assert.ok(db.data.core.version)
  assert.notOk(db.data.core.app)
  assert.notOk(db.data.core.manager)
  assert.strictEqual(db.filename, util.getPathFromRoot('./db_test.json'))
})

t.test('#writeSync() Should support syncronous write', function() {
  const filename = util.getPathFromRoot('./db_test.json')
  const assertValue = 'Yume no Naka no Watashi no Yume'

  return lowdb({}, logger, filename)
  .then(function(db) {
    db.data.songtest = assertValue

    db.writeSync()

    let content = JSON.parse(fsSync.readFileSync(filename))
    assert.strictEqual(content.songtest, assertValue)
  })
})

t.test('#writeSync() Should not throw', async function() {
  let db = await lowdb({}, logger, util.getPathFromRoot('./db_test.json'))
  
  db.filename = util.getPathFromRoot('../test')

  assert.notOk(db.log.error.called)
  db.writeSync()
  assert.ok(db.log.error.called)
  assert.match(db.log.error.firstCall[0].message, /directory/i)
  assert.match(db.log.error.firstCall[1], /writ/)
  assert.match(db.log.error.firstCall[1], new RegExp(db.filename.replace(/\\/g, '\\\\')))
})

t.test('Should support in-memory db', async function() {
  await assert.isRejected(fs.stat('./test/db_test.json'))
  await assert.isRejected(fs.stat('./test/null'))

  let db = await lowdb({}, logger, null)

  await assert.isRejected(fs.stat('./test/db_test.json'))
  await assert.isRejected(fs.stat('./test/null'))

  assert.ok(db.data.core)
  assert.ok(db.data.core.version)
  assert.notOk(db.data.core.app)
  assert.notOk(db.data.core.manager)
})

t.test('Should map config to config', async function() {
  const assertConfig = { a: 1 }

  let db = await lowdb(assertConfig, logger, util.getPathFromRoot('./db_test.json'))

  assert.strictEqual(db.config, assertConfig)
})

t.test('Should apply defaults to existing file', async function() {
  await assert.isRejected(fs.stat('./test/db_test.json'))
  await fs.writeFile('./test/db_test.json', '{"test":true}')

  let db = await lowdb({}, logger, util.getPathFromRoot('./db_test.json'))

  let stat = await fs.stat('./test/db_test.json')
  assert.ok(stat.size > 0)

  assert.ok(db.data.core)
  assert.ok(db.data.core.version)
  assert.strictEqual(db.data.test, true)
  assert.notOk(db.data.core.app)
  assert.notOk(db.data.core.manager)
})

t.test('Should not fail if db is invalid', async function() {
  logger.warn.reset()
  await assert.isRejected(fs.stat('./test/db_test.json'))
  await fs.writeFile('./test/db_test.json', '[]')

  let db = await lowdb({}, logger, util.getPathFromRoot('./db_test.json'))

  let stat = await fs.stat('./test/db_test.json')
  assert.ok(stat.size > 0)

  assert.ok(db.data.core)
  assert.ok(db.data.core.version)
  assert.notOk(db.data.core.app)
  assert.notOk(db.data.core.manager)
  assert.ok(logger.warn.called)
  assert.match(logger.warn.firstCall[0], /db_test.json/i)
  assert.match(logger.warn.firstCall[0], /clear/i)
})

t.test('Should attach logger to log', async function() {
  let db = await lowdb({}, logger, util.getPathFromRoot('./db_test.json'))
  assert.strictEqual(db.log, logger)
})

t.test('Should support adding an application with defaults', async function() {
  await assert.isRejected(fs.stat('./test/db_test.json'))

  let db = await lowdb({}, logger, util.getPathFromRoot('./db_test.json'))

  let stat = await fs.stat('./test/db_test.json')
  assert.ok(stat.size > 0)

  assert.ok(db.data.core)
  assert.ok(db.data.core.version)
  assert.notOk(db.data.core.app)
  assert.notOk(db.data.core.manager)

  db.addApplication('app')

  assert.ok(db.data.core.app)
  assert.ok(db.data.core.app.versions)
  assert.strictEqual(db.data.core.app.active, '')
  assert.strictEqual(db.data.core.app.latestInstalled, '')
  
  assert.notOk(db.data.core.herpderp)
  
  db.addApplication('herpderp')

  assert.ok(db.data.core.herpderp)
  assert.ok(db.data.core.herpderp.versions)
  assert.strictEqual(db.data.core.herpderp.active, '')
  assert.strictEqual(db.data.core.herpderp.latestInstalled, '')
})

t.test('Should support reading from db', async function() {
  const assertValue = { a: 1 }
  await assert.isRejected(fs.stat('./test/db_test.json'))

  let db = await lowdb({}, logger, util.getPathFromRoot('./db_test.json'))


  db.data.test = assertValue
  await db.write()

  assert.strictEqual(db.data.test, assertValue)

  let dbSecondary = await lowdb({}, logger, util.getPathFromRoot('./db_test.json'))
  assert.notStrictEqual(dbSecondary.data.test, assertValue)
  assert.deepStrictEqual(dbSecondary.data.test, assertValue)
})

t.test('should throw if unable to write to file', function() {
  return assert.isRejected(lowdb({}, logger, util.getPathFromRoot('../test')))
})

t.test('should have basic database-like functions defined', async function() {
  const assertItem1 = { a: 1 }
  const assertItem2 = { a: 2 }
  const assertItem3 = { a: 3 }
  let db = await lowdb({}, logger, util.getPathFromRoot('./db_test.json'))
  assert.strictEqual(db.id, 'id')

  db.data.myarr = []
  db.upsert(db.data.myarr, assertItem1)
  db.upsert(db.data.myarr, assertItem2)

  assert.strictEqual(db.data.myarr.length, 2)
  assert.ok(assertItem1.id)
  assert.ok(assertItem2.id)
  assert.notStrictEqual(assertItem1.id, assertItem2.id)

  assert.strictEqual(db.get(db.data.myarr, assertItem1.id), assertItem1)
  assert.strictEqual(db.get(db.data.myarr, assertItem2.id), assertItem2)
  assert.strictEqual(db.get(db.data.myarr, assertItem2.id + 1), null)

  await db.write()

  let dbSec = await lowdb({}, logger, util.getPathFromRoot('./db_test.json'))
  
  assert.strictEqual(dbSec.data.myarr.length, 2)
  assert.notStrictEqual(dbSec.get(dbSec.data.myarr, assertItem1.id), assertItem1)
  assert.notStrictEqual(dbSec.get(dbSec.data.myarr, assertItem2.id), assertItem2)
  assert.deepEqual(dbSec.get(dbSec.data.myarr, assertItem1.id), assertItem1)
  assert.deepEqual(dbSec.get(dbSec.data.myarr, assertItem2.id), assertItem2)

  dbSec.upsert(dbSec.data.myarr, assertItem3)
  assert.strictEqual(dbSec.data.myarr.length, 3)
  assert.ok(assertItem3.id)
  assert.strictEqual(dbSec.get(dbSec.data.myarr, assertItem3.id), assertItem3)
  assert.strictEqual(assertItem2.id + 1, assertItem3.id)

  const assertItem4 = { a: 4, id: assertItem2.id }
  dbSec.upsert(dbSec.data.myarr, assertItem4)

  assert.strictEqual(dbSec.data.myarr.length, 3)
  assert.notDeepEqual(dbSec.get(dbSec.data.myarr, assertItem2.id), assertItem2)
  assert.deepEqual(dbSec.get(dbSec.data.myarr, assertItem2.id), assertItem4)
  
  const assertItem5 = { a: 5, id: assertItem1.id }
  dbSec.upsert(dbSec.data.myarr, assertItem5)

  assert.strictEqual(dbSec.data.myarr.length, 3)
  assert.notDeepEqual(dbSec.get(dbSec.data.myarr, assertItem1.id), assertItem1)
  assert.deepEqual(dbSec.get(dbSec.data.myarr, assertItem1.id), assertItem5)

  dbSec.remove(dbSec.data.myarr, assertItem2.id)
  assert.strictEqual(dbSec.data.myarr.length, 2)
  assert.strictEqual(dbSec.get(dbSec.data.myarr, assertItem2.id), null)
  
  dbSec.remove(dbSec.data.myarr, assertItem1.id)
  assert.strictEqual(dbSec.data.myarr.length, 1)
  assert.strictEqual(dbSec.get(dbSec.data.myarr, assertItem1.id), null)
})

t.test('should have basic database-like functions with string-like name of collection', async function() {
  const assertItem1 = { a: 1 }
  const assertItem2 = { a: 2 }
  const assertItem3 = { a: 3 }
  let db = await lowdb({}, logger, util.getPathFromRoot('./db_test.json'))
  assert.strictEqual(db.id, 'id')

  db.data.myarr = []
  db.upsert('myarr', assertItem1)
  db.upsert('myarr', assertItem2)

  assert.strictEqual(db.data.myarr.length, 2)
  assert.ok(assertItem1.id)
  assert.ok(assertItem2.id)
  assert.notStrictEqual(assertItem1.id, assertItem2.id)

  assert.strictEqual(db.get('myarr', assertItem1.id), assertItem1)
  assert.strictEqual(db.get('myarr', assertItem2.id), assertItem2)
  assert.strictEqual(db.get('myarr', assertItem2.id + 1), null)

  await db.write()

  let dbSec = await lowdb({}, logger, util.getPathFromRoot('./db_test.json'))
  
  assert.strictEqual(dbSec.data.myarr.length, 2)
  assert.notStrictEqual(dbSec.get('myarr', assertItem1.id), assertItem1)
  assert.notStrictEqual(dbSec.get('myarr', assertItem2.id), assertItem2)
  assert.deepEqual(dbSec.get('myarr', assertItem1.id), assertItem1)
  assert.deepEqual(dbSec.get('myarr', assertItem2.id), assertItem2)

  dbSec.upsert('myarr', assertItem3)
  assert.strictEqual(dbSec.data.myarr.length, 3)
  assert.ok(assertItem3.id)
  assert.strictEqual(dbSec.get('myarr', assertItem3.id), assertItem3)
  assert.strictEqual(assertItem2.id + 1, assertItem3.id)

  const assertItem4 = { a: 4, id: assertItem2.id }
  dbSec.upsert('myarr', assertItem4)

  assert.strictEqual(dbSec.data.myarr.length, 3)
  assert.notDeepEqual(dbSec.get('myarr', assertItem2.id), assertItem2)
  assert.deepEqual(dbSec.get('myarr', assertItem2.id), assertItem4)
  
  const assertItem5 = { a: 5, id: assertItem1.id }
  dbSec.upsert('myarr', assertItem5)

  assert.strictEqual(dbSec.data.myarr.length, 3)
  assert.notDeepEqual(dbSec.get('myarr', assertItem1.id), assertItem1)
  assert.deepEqual(dbSec.get('myarr', assertItem1.id), assertItem5)

  dbSec.remove('myarr', assertItem2.id)
  assert.strictEqual(dbSec.data.myarr.length, 2)
  assert.strictEqual(dbSec.get('myarr', assertItem2.id), null)
  
  dbSec.remove('myarr', assertItem1.id)
  assert.strictEqual(dbSec.data.myarr.length, 1)
  assert.strictEqual(dbSec.get('myarr', assertItem1.id), null)
})

t.test('#upsert() should work properly', async function() {
  let db = await lowdb({}, logger, null)
  db.data.test = {
    items: []
  }

  db.upsert(db.data.test.items, { id: '1234', text: '1' })
  db.upsert(db.data.test.items, { id: '1234', text: '2' })

  assert.strictEqual(db.data.test.items.length, 1)
  assert.strictEqual(db.data.test.items[0].id, '1234')
  assert.strictEqual(db.data.test.items[0].text, '2')
})


t.test('#upsertFirst() should work properly', async function() {
  let db = await lowdb({}, logger, null)
  db.data.test = {
    items: []
  }

  db.upsertFirst(db.data.test.items, { id: '1234', text: '1' })
  db.upsertFirst(db.data.test.items, { id: '1234', text: '2' })
  db.upsertFirst(db.data.test.items, { id: '1', text: '3' })
  db.upsertFirst(db.data.test.items, { id: '2', text: '4' })

  assert.strictEqual(db.data.test.items.length, 3)
  assert.strictEqual(db.data.test.items[2].id, '1234')
  assert.strictEqual(db.data.test.items[2].text, '2')
  assert.strictEqual(db.data.test.items[1].text, '3')
  assert.strictEqual(db.data.test.items[0].text, '4')
})
