const path = require('path')

const createTunnelController = require('./frpc-tunnel-controller.js')

const TUNNEL_STATUS = require('./tunnel-status')
class TunnelProcessManager {
  constructor({
    logFileDirectory,
    tunnelConfigFileDirectory,
    runTunnelBinPath,
    socks5Proxies,
    portRange,
  }) {
    this._tunnelsMap = new Map()
    this._logFileDirectory = logFileDirectory
    this._tunnelConfigFileDirectory = tunnelConfigFileDirectory
    this._runTunnelBinPath = runTunnelBinPath
    this._portRange = portRange
    this._socks5Proxies = socks5Proxies

    // Define proposal-class-properties (because eslint throw error)
    this._shouldRunTunnel = async (tunnelId) => {
      const {isStopTunnelCalled} = this._tunnelsMap.get(tunnelId) || {}
      return !!isStopTunnelCalled
    }

    this._updateTunnelMap = (tunnelId, fields) => {
      const tunnel = this._tunnelsMap.get(tunnelId) || {}
      this._tunnelsMap.set(tunnelId, {...tunnel, ...fields})
    }

    this._getTunnelByStatus = (states) => Array.from(this._tunnelsMap.keys()).filter((id) => {
      const {processController} = this._tunnelsMap.get(id)
      return processController && states.includes(processController.status)
    })

    this.getActiveTunnels = () => this._getTunnelByStatus([TUNNEL_STATUS.RUNNING, TUNNEL_STATUS.RECONNECT])
    this.getRunningTunnels = () => this._getTunnelByStatus([TUNNEL_STATUS.RUNNING])
  }

  async start({tunnelId, config}) {
    try {
      const logFilePath = path.resolve(this._logFileDirectory, `${tunnelId}.log`)

      if (!this._shouldRunTunnel(tunnelId)) {
        return null
      }

      this._updateTunnelMap(tunnelId, {config})

      const tunnelController = createTunnelController({
        tunnelId,
        tunnelConfig: config,
        configFileRootPath: this._tunnelConfigFileDirectory,
        logFilePath,
        binPath: this._runTunnelBinPath,
        socks5Proxy: this._socks5Proxies[0],
        portRange: this._portRange,
      })

      await tunnelController.start()

      this._updateTunnelMap(tunnelId, {processController: tunnelController})

      if (!this._shouldRunTunnel(tunnelId)) {
        await this.stop(tunnelId)
        return null
      }

      return tunnelId
    } catch (error) {
      this._updateTunnelMap(tunnelId, {error})
      throw error
    }
  }

  async stop(tunnelId) {
    if (!this._tunnelsMap.has(tunnelId)) {
      return
    }

    // Update that user want to stop the tunnel
    this._updateTunnelMap(tunnelId, {isStopTunnelCalled: true})

    const {processController} = this._tunnelsMap.get(tunnelId)

    if (!processController) {
      return
    }

    await processController.stop()
    this._tunnelsMap.delete(tunnelId)
  }

  async stopAll() {
    const promises = Object.entries(this.getTunnels())
      .filter(([_tunnelId, {isStopTunnelCalled}]) => !isStopTunnelCalled)
      .map(([tunnelId]) => this.stop(tunnelId))

    const _result = await Promise.allSettled(promises)

    //TODO: add log if there is an error

    return
  }

  getTunnels() {
    return Array.from(this._tunnelsMap.entries()).reduce(
      (pv, [key, value]) => ({...pv, [key]: value}),
      {},
    )
  }

  getStatus(tunnelId) {
    const process = this._tunnelsMap.get(tunnelId)
    if (!process || !process.processController) {
      return null
    }

    return process.processController.status
  }
}

module.exports = function createTunnelProcessManager({
  logFileDirectory,
  tunnelConfigFileDirectory,
  runTunnelBinPath,
  egTunnelManager,
  socks5Proxies,
  portRange,
}) {
  return new TunnelProcessManager({
    logFileDirectory,
    tunnelConfigFileDirectory,
    runTunnelBinPath,
    egTunnelManager,
    socks5Proxies,
    portRange,
  })
}
