import { Eltro as t, assert, stub } from 'eltro'
import fs from 'fs/promises'
import Application from '../core/application.mjs'
import Util from '../core/util.mjs'
import lowdb from '../core/db.mjs'
import StaticProvider from '../core/providers/static.mjs'
import { createFakeContext } from './helpers.mjs'
import HttpServer from '../core/http.mjs'

const util = new Util(import.meta.url)


t.describe('#runVersion("version") cluster mode', function() {
  const assertPort = 22345
  const assertClusterSize = Math.floor(Math.random() * (8 - 2 + 1) + 2)
  const http = new HttpServer()
  let handler
  let cluster
  let workers
  let ctx
  let app

  t.beforeEach(function() {
    return createFakeContext({ testnoexisting: { cluster: assertClusterSize } }, util, null)
      .then(function(res) {
        workers = []
        ctx = res
        let provider = new StaticProvider()
        cluster = {
          on: stub(),
          off: stub(),
          fork: stub().returnWith(function() {
            let worker = {
              on: stub(),
              once: stub(),
              process: {
                kill: stub(),
              }
            }
            workers.push(worker)
            return worker
          }),
          isWorker: false,
        }
        app = new Application(ctx, provider, 'testnoexisting', { cluster })
        app.config.port = assertPort
        app.workerDied = stub()
        return app.ctx.db.write()
      })
  })

  t.afterEach(function() {
    return Promise.all([
      app.closeServer(),
    ])
  })

  t.before(function() {
    handler = function(req, res) {
      res.writeHead(200)
      res.end('{}')
    }
    let server = http.createServer(function(req, res) {
      req.on('error', function() { })
      res.on('error', function() { })
      res.on('finish', function() { })
      handler(req, res)
    })

    return server.listenAsync(assertPort)
  })

  t.after(function() {
    return http.closeServer()
  })

  t.test('should call fork for entire cluster and attach handlers correctly', async function() {
    let lastErr = null
    for (let i = 1; i <= assertClusterSize; i++) {
      assert.strictEqual(app.workers[i], null)
    }
    handler = function(req, res) {
      try {
        assert.notOk(cluster.on.called)
        assert.notOk(app.__clusterWorkerDied)
        lastErr = false
      } catch (err) {
        lastErr = err
      }
      res.writeHead(200)
      res.end('{}')
    }
    const assertVersion = 'v11.22.33'

    await app.runVersion(assertVersion)

    assert.strictEqual(cluster.fork.callCount, assertClusterSize)

    for (let i = 1; i <= assertClusterSize; i++) {
      assert.ok(app.workers[i])
      assert.strictEqual(app.workers[i], workers[i - 1])
      assert.strictEqual(cluster.fork.getCallN(i)[0].CLUSTER_APP_NAME, app.name)
      assert.strictEqual(cluster.fork.getCallN(i)[0].CLUSTER_APP_VERSION, assertVersion)
      assert.strictEqual(app.workers[i].w_id, i)
      assert.strictEqual(app.workers[i].listening, false)
      assert.ok(app.workers[i].started)
      assert.ok(new Date() - app.workers[i].started < 100 && new Date() - app.workers[i].started >= 0)
    }

    assert.strictEqual(lastErr, false)
    assert.strictEqual(cluster.on.callCount, 1)
    assert.ok(app.__clusterWorkerDied)
    assert.strictEqual(cluster.on.firstCall[0], 'exit')
    assert.strictEqual(cluster.on.firstCall[1], app.__clusterWorkerDied)
    assert.notStrictEqual(app.workerDied, app.__clusterWorkerDied)
    assert.notOk(app.workerDied.called)

    app.__clusterWorkerDied()

    assert.ok(app.workerDied.called)
  })
})
