import { Eltro as t, assert, stub } from 'eltro'
import fs from 'fs/promises'
import Core from '../core/core.mjs'
import Util from '../core/util.mjs'
import { createFakeLog } from './helpers.mjs'
import StaticProvider from '../core/providers/static.mjs'
import lowdb from '../core/db.mjs'

const util = new Util(import.meta.url)
const log = createFakeLog()
let db

t.before(function() {
  return lowdb({}, log, null).then(function(res) {
    db = res
  })
})

t.describe('#init()', function() {
  const assertProviderName = 'Kyousuu Gakku Gogyou Kikan'
  let core
  let fakeUtil
  let fakeProvider
  let fakeProviderConfig

  function FakeProvider(config) {
    fakeProvider(config)
    this.static = true
    this.checkConfig = fakeProviderConfig
  }

  t.beforeEach(function() {
    log.error.reset()
    core = new Core(db, util, log)
    core.util = fakeUtil = {
      verifyConfig: stub(),
      getAppNames: stub().returns([]),
    }
    fakeProvider = stub()
    fakeProviderConfig = stub()
    Core.providers.set(assertProviderName, FakeProvider)
    core.isSlave = true
  })

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

  t.test('should not call provider checkConfig if isSlave', async function() {
    const assertAppName = 'Zetsubou'
    const assertConfig = {
      [assertAppName]: {
        provider: assertProviderName,
      }
    }
    db.config = assertConfig
    process.env.CLUSTER_APP_NAME = assertAppName

    const assertError = new Error('Shousou')
    fakeProviderConfig.rejects(assertError)
    fakeUtil.getAppNames.returns([assertAppName])
    
    await core.init()
    assert.notOk(fakeProviderConfig.called)
  })

  t.test('should only create an application with the environment app name', async function() {
    const assertAppName = 'Yasashii Ketsumatsu'
    const assertAppSecondaryName = 'Big Time'
    const assertTestString = 'Serozore no Omoi'
    const assertConfig = {
      [assertAppName]: {
        provider: assertProviderName,
        teststring: assertTestString,
      },
      [assertAppSecondaryName]: {
        provider: assertProviderName,
        teststring: assertTestString,
      },
    }
    db.config = assertConfig
    process.env.CLUSTER_APP_NAME = assertAppName

    fakeUtil.getAppNames.returns([assertAppSecondaryName, assertAppName])
    assert.strictEqual(core.applications.length, 0)

    await core.init()

    assert.strictEqual(core.applications.length, 1)
    assert.ok(core.getApplication(assertAppName))
    assert.notOk(core.getApplication(assertAppSecondaryName))
    
    let application = core.getApplication(assertAppName)
    assert.strictEqual(core.applications[0], application)
    assert.strictEqual(application.name, assertAppName)
    assert.strictEqual(application.ctx.db, core.db)
    assert.strictEqual(application.ctx.util, core.util)
    assert.notStrictEqual(application.ctx.log, core.log)
    assert.strictEqual(application.ctx.core, core)
    assert.strictEqual(application.config.teststring, assertTestString)
    assert.ok(application.fresh)
    assert.ok(application.provider instanceof FakeProvider)
  })
})

t.describe('#run()', function() {
  let core
  let testAppOneName
  let testAppTwoName
  let stubRunApplication

  t.beforeEach(function() {
    testAppOneName = 'Tenshi'
    testAppTwoName = 'no CLOVER'
    db.data.core = {
      [testAppOneName]: {
        versions: []
      },
      [testAppTwoName]: {
        versions: []
      },
    }
    core = new Core(db, util, log)
    core.runApplication = stubRunApplication = stub().resolves()
    core.isSlave = true
    db.write = stub().resolves()
    log.info.reset()
    log.warn.reset()
    log.error.reset()
    for (let name of [testAppOneName, testAppTwoName]) {
      let onOrOnce = stub()
      let app = {
        name: name,
        fresh: false,
        on: onOrOnce,
        once: onOrOnce,
        ctx: {
          log: {
            info: stub(),
            warn: stub(),
            error: stub(),
            fatal: stub(),
          },
        },
        runVersion: stub().resolves(),
        update: stub().resolves(),
        startAutoupdater: stub(),
      }
      core.applicationMap.set(name, app)
      core.applications.push(app)
    }
    process.env.CLUSTER_APP_NAME = testAppTwoName
  })

  t.test('should not call update', async function() {
    const assertFirstError = new Error('Manatsu')
    const assertSecondError = new Error('no Photograph')
    core.applicationMap.get(testAppOneName).update.rejects(assertFirstError)
    core.applicationMap.get(testAppTwoName).update.rejects(assertSecondError)

    await core.run()
    
    assert.notOk(core.applications[0].update.called)
    assert.notOk(core.applications[1].update.called)
  })
  

  t.test('app.on updated should not be hooked', async function() {
    assert.notOk(core.applicationMap.get(testAppOneName).once.called)
    assert.notOk(core.applicationMap.get(testAppTwoName).once.called)
    assert.notOk(core.applicationMap.get(testAppOneName).on.called)
    assert.notOk(core.applicationMap.get(testAppTwoName).on.called)

    core.runApplication.returnWith(function(app) {
      assert.notOk(app.once.called)
      assert.notOk(app.on.called)
      return Promise.resolve()
    })

    await core.run()

    assert.notOk(core.applicationMap.get(testAppOneName).once.called)
    assert.notOk(core.applicationMap.get(testAppTwoName).once.called)
    assert.notOk(core.applicationMap.get(testAppOneName).on.called)
    assert.notOk(core.applicationMap.get(testAppTwoName).on.called)
  })

  t.test('should not call startAutoupdater', async function() {
    stubRunApplication.rejects(new Error('not seen'))
    assert.notOk(core.applications[0].startAutoupdater.called)
    assert.notOk(core.applications[1].startAutoupdater.called)

    await core.run()
    
    assert.notOk(core.applications[0].startAutoupdater.called)
    assert.notOk(core.applications[1].startAutoupdater.called)
  })

  t.test('should be safe to call multiple times', async function() {
    await core.run()

    assert.strictEqual(core.applicationMap.get(testAppOneName).runVersion.callCount, 0)
    assert.strictEqual(core.applicationMap.get(testAppTwoName).runVersion.callCount, 1)

    await core.run()
    await core.run()
    await core.run()
    await core.run()

    assert.strictEqual(core.applicationMap.get(testAppOneName).runVersion.callCount, 0)
    assert.strictEqual(core.applicationMap.get(testAppTwoName).runVersion.callCount, 1)
  })

  t.test('should call runVersion on selected application', async function() {
    const assertFirstError = new Error('Manatsu')
    const assertSecondError = new Error('no Photograph')
    const assertVersion = 'v11.22.33'
    core.applicationMap.get(testAppOneName).runVersion.rejects(assertFirstError)
    core.applicationMap.get(testAppTwoName).runVersion.rejects(assertSecondError)
    
    process.env.CLUSTER_APP_NAME = testAppTwoName
    process.env.CLUSTER_APP_VERSION = assertVersion

    let err = await assert.isRejected(core.run())
    
    let app = core.applicationMap.get(testAppTwoName)
    assert.strictEqual(core.applicationMap.get(testAppOneName).runVersion.callCount, 0)
    assert.strictEqual(app.runVersion.callCount, 1)
    assert.strictEqual(app.runVersion.firstCall[0], assertVersion)
    assert.strictEqual(app.ctx.log.fatal.callCount, 1)
    assert.strictEqual(app.ctx.log.fatal.firstCall[0], assertSecondError)
    assert.strictEqual(err, assertSecondError)
  })
})
