import express from 'express'
import http from 'http'
import path from 'path'
import open from 'open'
import socketIO from 'socket.io'
import onDeath from 'death'
import { updateScreenshot } from 'screenie-core'

import { runTest } from './discover-tests'
import * as status from './status'

let app, server, io

export async function startAndWaitUntilClose() {
  app = express()
  server = http.createServer(app)
  io = socketIO(server)

  app.use(express.static(path.join(__dirname, '../resources/browser-reporter')))
  // app.use('/novnc', express.static(path.dirname(require.resolve('noVNC'))))

  app.use('/screenshots/:testId/:matrixId/reference', (req, res) => {
    const { matrix, snapshot } = status.getTestAndMatrixByIds(
      req.params.testId,
      req.params.matrixId,
      req.query.snapshot
    )

    if (!matrix) {
      return res.sendStatus(404)
    }

    const snapshotOrMatrix = snapshot || matrix

    res.sendFile(snapshotOrMatrix.referenceScreenshotPath)
  })

  app.use('/screenshots/:testId/:matrixId/new', (req, res) => {
    const { matrix, snapshot } = status.getTestAndMatrixByIds(
      req.params.testId,
      req.params.matrixId,
      req.query.snapshot
    )

    if (!matrix) {
      return res.sendStatus(404)
    }

    const snapshotOrMatrix = snapshot || matrix

    if (!snapshotOrMatrix.exportedPaths) {
      return res.sendStatus(404)
    }

    res.sendFile(snapshotOrMatrix.exportedPaths.new)
  })

  app.use('/screenshots/:testId/:matrixId/diff', (req, res) => {
    const { matrix, snapshot } = status.getTestAndMatrixByIds(
      req.params.testId,
      req.params.matrixId,
      req.query.snapshot
    )

    if (!matrix) {
      return res.sendStatus(404)
    }

    const snapshotOrMatrix = snapshot || matrix

    if (!snapshotOrMatrix.exportedPaths) {
      return res.sendStatus(404)
    }

    res.sendFile(snapshotOrMatrix.exportedPaths.diff)
  })

  const listener = server.listen(() => {
    console.log()
    console.log(
      ' ',
      `Test report running at http://localhost:${listener.address().port}/`
    )
    console.log()

    open(`http://localhost:${listener.address().port}/`)
  })

  // don't resolve promise until Ctrl+C is pressed
  const processExited = new Promise(resolve => onDeath(resolve))

  function changeCallback() {
    io.emit('status', status.getTests())
  }

  status.events.on('change', changeCallback)

  io.on('connection', socket => {
    // send initial payload
    socket.emit('status', status.getTests())

    socket.on('rerun', async ({ testId, matrixId }) => {
      const test = status.getTestDefinitionById(testId)
      const matrix = status.getMatrixDefinitionById(testId, matrixId)

      await runTest(test, matrix)
    })

    socket.on('rerunAllMatrices', async ({ testId, matrixId }) => {
      const test = status.getTestDefinitionById(testId)
      const testStatus = status.getTestById(testId)

      await Promise.all(
        testStatus.matrices.map(matrixStatus => runTest(test, matrixStatus.matrix))
      )
    })

    socket.on('updateReference', async ({ testId, matrixId, snapshot: snapshotName }) => {
      const testDefinition = status.getTestDefinitionById(testId)
      const matrixDefinition = status.getMatrixDefinitionById(testId, matrixId)

      const { matrix, snapshot } = status.getTestAndMatrixByIds(
        testId,
        matrixId,
        snapshotName
      )

      if (!matrix) return

      const snapshotOrMatrix = snapshot || matrix

      if (!snapshotOrMatrix.exportedPaths) return

      const newScreenshotPath = snapshotOrMatrix.exportedPaths.new
      const referenceScreenshotPath = snapshotOrMatrix.referenceScreenshotPath

      await updateScreenshot(newScreenshotPath, referenceScreenshotPath)

      await runTest(testDefinition, matrixDefinition)
    })

    socket.on('updateReferenceForAllSnapshots', async ({ testId, matrixId }) => {
      const testDefinition = status.getTestDefinitionById(testId)
      const matrixDefinition = status.getMatrixDefinitionById(testId, matrixId)

      const { matrix } = status.getTestAndMatrixByIds(testId, matrixId)

      if (!matrix) return

      if (!matrix.snapshots) {
        if (!matrix.exportedPaths) return

        const newScreenshotPath = matrix.exportedPaths.new
        const referenceScreenshotPath = matrix.referenceScreenshotPath

        await updateScreenshot(newScreenshotPath, referenceScreenshotPath)
        await runTest(testDefinition, matrixDefinition)

        return
      }

      await Promise.all(
        matrix.snapshots.map(async snapshot => {
          if (!snapshot.exportedPaths) return

          const newScreenshotPath = snapshot.exportedPaths.new
          const referenceScreenshotPath = snapshot.referenceScreenshotPath

          await updateScreenshot(newScreenshotPath, referenceScreenshotPath)
        })
      )

      await runTest(testDefinition, matrixDefinition)
    })
  })

  await processExited

  status.events.removeListener('change', changeCallback)

  io.close()
  server.close()
}
