import { Eltro as t, assert, stub } from 'eltro'
import fs from 'fs/promises'
import getLog from '../core/log.mjs'

t.describe('#constructor', function() {
  t.afterEach(function() {
    process.env.NODE_ENV = null
  })


  t.after(function() {
    return Promise.all([
      fs.rm('./log.log', { recursive: true, force: true }),
    ])
  })

  t.test('should add name', function() {
    const assertName = 'Stray Cat'
    let logger = getLog(assertName)
    assert.strictEqual(logger.fields.name, assertName)

    process.env.NODE_ENV = 'production'
    logger = getLog(assertName)
    assert.strictEqual(logger.fields.name, assertName)
  })

  t.test('should add default stdout streams in normal environment', function() {
    let logger = getLog('app', null)
    assert.strictEqual(logger.streams.length, 4)
    assert.strictEqual(logger.streams[0].stream, process.stdout)
    assert.strictEqual(logger.streams[0].level, 20)
  })

  t.test('should add default file log stream in production environment', function() {
    process.env.NODE_ENV = 'production'

    let logger = getLog('app', null)
    assert.strictEqual(logger.streams.length, 4)
    assert.strictEqual(logger.streams[0].path, 'log.log')
    assert.strictEqual(logger.streams[0].level, 30)
  })

  t.test('should not add default stream if empty array', function() {
    let logger = getLog('app', [])
    assert.strictEqual(logger.streams.length, 3)

    process.env.NODE_ENV = 'production'
    logger = getLog('app', [])
    assert.strictEqual(logger.streams.length, 3)
  })

  t.test('should replace process.stdout with actual process', function() {
    let logger = getLog('app', [{ stream: 'process.stdout', level: 'info' }])
    assert.strictEqual(logger.streams.length, 4)
    assert.strictEqual(logger.streams[0].stream, process.stdout)

    process.env.NODE_ENV = 'production'
    logger = getLog('app', [{ stream: 'process.stdout', level: 'info' }])
    assert.strictEqual(logger.streams.length, 4)
    assert.strictEqual(logger.streams[0].stream, process.stdout)
  })
})

t.describe('ringbuffer', function() {
  let logger

  t.beforeEach(function() {
    logger = getLog('app', [])
  })
  
  t.test('should have ringbuffer for info', function() {
    const assertMessage = 'Oitachi'

    assert.strictEqual(logger.ringbuffer.records.length, 0)
    logger.info(assertMessage)
    assert.strictEqual(logger.ringbuffer.records.length, 1)
    assert.strictEqual(logger.ringbuffer.records[0].level, 30)
    assert.strictEqual(logger.ringbuffer.records[0].msg, assertMessage)
    logger.debug(assertMessage)
    assert.strictEqual(logger.ringbuffer.records.length, 1)
    logger.warn(assertMessage)
    assert.strictEqual(logger.ringbuffer.records.length, 2)
    assert.strictEqual(logger.ringbuffer.records[1].level, 40)
    assert.strictEqual(logger.ringbuffer.records[1].msg, assertMessage)
  })

  t.test('should keep it limited to max 100 records', function() {
    const assertPrefix = 'In memory of Keiten'

    for (let i = 1; i <= 101; i++) {
      logger.info(assertPrefix + i)
    }
    assert.strictEqual(logger.ringbuffer.records.length, 100)
    assert.strictEqual(logger.ringbuffer.records[0].msg, assertPrefix + '2')
    
    logger.info(assertPrefix)
    
    assert.strictEqual(logger.ringbuffer.records.length, 100)
    assert.strictEqual(logger.ringbuffer.records[0].msg, assertPrefix + '3')
  })
})

t.describe('ringbufferwarn', function() {
  let logger

  t.beforeEach(function() {
    logger = getLog('app', [])
  })
  
  t.test('should have ringbufferwarn for info', function() {
    const assertMessage = 'Oitachi'

    assert.strictEqual(logger.ringbufferwarn.records.length, 0)
    logger.warn(assertMessage)
    assert.strictEqual(logger.ringbufferwarn.records.length, 1)
    assert.strictEqual(logger.ringbufferwarn.records[0].level, 40)
    assert.strictEqual(logger.ringbufferwarn.records[0].msg, assertMessage)
    logger.info(assertMessage)
    assert.strictEqual(logger.ringbufferwarn.records.length, 1)
    logger.error(assertMessage)
    assert.strictEqual(logger.ringbufferwarn.records.length, 2)
    assert.strictEqual(logger.ringbufferwarn.records[1].level, 50)
    assert.strictEqual(logger.ringbufferwarn.records[1].msg, assertMessage)
  })

  t.test('should keep it limited to max 100 records', function() {
    const assertPrefix = 'In memory of Keiten'

    for (let i = 1; i <= 101; i++) {
      logger.warn(assertPrefix + i)
    }
    assert.strictEqual(logger.ringbufferwarn.records.length, 100)
    assert.strictEqual(logger.ringbufferwarn.records[0].msg, assertPrefix + '2')
    
    logger.warn(assertPrefix)
    
    assert.strictEqual(logger.ringbufferwarn.records.length, 100)
    assert.strictEqual(logger.ringbufferwarn.records[0].msg, assertPrefix + '3')
  })
})

t.describe('event', function() {
  t.test('should call import if not in production', async function() {
    let stubImport = stub()
    stubImport.rejects(new Error('should not be seen'))

    let logger = getLog('app', [], { import: stubImport })

    let first = new Promise(function(res, rej) {
      setImmediate(function() { logger.event.warn('text message here').then(res, rej) })
    })
    let second = new Promise(function(res, rej) {
      setImmediate(function() { logger.event.warn('new message here').then(res, rej) })
    })

    await Promise.all([
      first, second,
    ])

    assert.notOk(stubImport.called)
  })

  t.test('should call import correctly if in production and fail only once', async function() {
    process.env.NODE_ENV = 'production'

    let stubImport = stub()
    stubImport.rejects(new Error('should not be seen'))

    let logger = getLog('app', [], { import: stubImport })

    let first = new Promise(function(res, rej) {
      setImmediate(function() { try { logger.event.warn('first').then(res, rej) } catch (err) { rej(err) } })
    })
    let second = new Promise(function(res, rej) {
      setImmediate(function() { try { logger.event.warn('second').then(res, rej) } catch (err) { rej(err) } })
    })

    await Promise.all([
      first, second,
    ])

    assert.ok(stubImport.called)
    assert.strictEqual(stubImport.callCount, 1)
    assert.strictEqual(stubImport.firstCall[0], 'node-windows')

    await new Promise(function(res, rej) {
      setImmediate(function() { try { logger.event.warn('third').then(res, rej) } catch (err) { rej(err) } })
    })
  })

  t.test('should call event on imported object correctly', async function() {
    const assertName = 'It is going to be The Special'
    let checkName = ''
    let stubInfo = stub().returnWith(function(msg, code, cb) { setTimeout(cb, 20) })
    let stubWarn = stub().returnWith(function(msg, code, cb) { setTimeout(cb, 20) })
    let stubError = stub().returnWith(function(msg, code, cb) { setTimeout(cb, 20) })
    process.env.NODE_ENV = 'production'

    let stubImport = stub().resolves({
      default: {
        EventLogger: function(name) {
          checkName = name
          this.info = stubInfo
          this.warn = stubWarn
          this.error = stubError
        },
      },
    })

    let logger = getLog(assertName, [], { import: stubImport })

    let first = new Promise(function(res, rej) {
      setImmediate(function() { try { logger.event.info('first', 1010).then(res, rej) } catch (err) { rej(err) } })
    })
    let second = new Promise(function(res, rej) {
      setImmediate(function() { try { logger.event.warn('second', 1020).then(res, rej) } catch (err) { rej(err) } })
    })

    await Promise.all([
      first, second,
    ])

    assert.strictEqual(checkName, assertName)
    assert.ok(stubInfo.called)
    assert.strictEqual(stubInfo.firstCall[0], 'first')
    assert.strictEqual(stubInfo.firstCall[1], 1010)
    assert.ok(stubWarn.called)
    assert.strictEqual(stubWarn.firstCall[0], 'second')
    assert.strictEqual(stubWarn.firstCall[1], 1020)
    assert.notOk(stubError.called)

    await new Promise(function(res, rej) {
      setImmediate(function() { try { logger.event.error('third', 1030).then(res, rej) } catch (err) { rej(err) } })
    })

    assert.ok(stubError.called)
    assert.strictEqual(stubError.firstCall[0], 'third')
    assert.strictEqual(stubError.firstCall[1], 1030)
  })

  t.test('should work even if it were to throw', async function() {
    const assertName = 'It is going to be The Special'
    let checkName = ''
    let stubInfo = stub().returnWith(function() { throw new Error('not to be seen') })
    let stubWarn = stub().returnWith(function() { throw new Error('not to be seen') })
    let stubError = stub().returnWith(function() { throw new Error('not to be seen') })
    process.env.NODE_ENV = 'production'

    let stubImport = stub().resolves({
      default: {
        EventLogger: function(name) {
          checkName = name
          this.info = stubInfo
          this.warn = stubWarn
          this.error = stubError
        },
      },
    })

    let logger = getLog(assertName, [], { import: stubImport })

    let first = new Promise(function(res, rej) {
      setImmediate(function() { try { logger.event.info().then(res, rej) } catch (err) { rej(err) } })
    })
    let second = new Promise(function(res, rej) {
      setImmediate(function() { try { logger.event.warn().then(res, rej) } catch (err) { rej(err) } })
    })

    await Promise.all([
      first, second,
    ])

    assert.strictEqual(checkName, assertName)
    assert.ok(stubInfo.called)
    assert.ok(stubWarn.called)
    assert.notOk(stubError.called)

    await new Promise(function(res, rej) {
      setImmediate(function() { try { logger.event.error().then(res, rej) } catch (err) { rej(err) } })
    })

    assert.ok(stubError.called)
  })

  t.test('should work without stub', async function() {
    let res = await import('node-windows').catch(function() {})
    if (!res) { return }

    process.env.NODE_ENV = 'production'
    let logger = getLog('service-core-unit-test', [])

    await logger.event.info('Hello from service-core log.event unit test')
  })
})
