const {
  tunnelId,
  stringifyConfig,
  socks5ProxyPort,
  stringifyPortRange,
  configFileRootPath,
  binPath,
  binArgs = '-c',
  logFilePath,
  heartbeatTimeout = 15000,
  startTunnelTimeoutThreshold = 10000,
  stopProcessTimeout = 5000,
} = process.env

const os = require('os')
const path = require('path')
const {promises: fs, unlinkSync} = require('fs')
const {SubProcess} = require('teen_process')
const nodeCleanup = require('node-cleanup')

const {createIniFileFromJson, findFreePort} = require('../utils')
const TUNNEL_STATUS = require('./tunnel-status')
const {convertFrpcOutputToTunnelStatus} = require('./convert-frpc-output-to-tunnel-status.js')

let selfKillTimeoutId
let initTimeoutId

const _startTunnel = async () => {
  const config = JSON.parse(stringifyConfig)
  const {min, max} = JSON.parse(stringifyPortRange)

  if (typeof socks5ProxyPort !== 'undefined') {
    config[tunnelId].local_port = socks5ProxyPort
    delete config[tunnelId].plugin
  } else {
    const localPort = await findFreePort(min, max)
    config[tunnelId].local_port = localPort
  }

  const configFilePath = path.resolve(configFileRootPath, `${tunnelId}.ini`)
  await createIniFileFromJson({path: configFilePath, content: config})

  const proc = new SubProcess(binPath, [binArgs, configFilePath], {
    stdio: 'pipe',
    //shell: os.platform() !== 'win32', // TODO: understand why it causes to problems
    detached: false,
    windowsHide: true
  })

  proc.on('stream-line', (line) => {
    if (!process.send) return

    const status = convertFrpcOutputToTunnelStatus(line)
    if (status) {
      const data = {
        status,
        ...(status === TUNNEL_STATUS.INIT_ERROR ? {error: line} : {}),
      }
      process.send(data)
    }

    // TODO: Run tunnels in many case!!!!!
    if (status === TUNNEL_STATUS.RUNNING || status === TUNNEL_STATUS.INIT_ERROR) {
      initTimeoutId && clearTimeout(initTimeoutId)
      initTimeoutId = undefined

      // set timeout to heartbeatTimeout after init
      clearTimeout(selfKillTimeoutId)
      startSelfKillTimeout(heartbeatTimeout)
    }

    logFilePath && fs.appendFile(logFilePath, `${line}\r\n`).catch(console.log)
  })

  proc.on('die', () => {
    process.send && process.send({status: TUNNEL_STATUS.STOPPED})

    initTimeoutId && clearTimeout(initTimeoutId)
    initTimeoutId = undefined

    process.exit()
  })

  // Closing frpc if doesn't get heart bit from parent
  const startSelfKillTimeout = (timeout) => {
    selfKillTimeoutId = setTimeout(async () => {
      logFilePath &&
        fs
          .appendFile(logFilePath, `TunnelId ${tunnelId}: self-kill is called \r\n`)
          .catch(console.log)
      console.log(`TunnelId ${tunnelId}: self-kill is called`)

      proc.isRunning && (await proc.stop())
      process.exit(1)
    }, timeout)
  }

  process.on('message', ({status}) => {
    if (status !== 'ok') {
      return
    }
    if (selfKillTimeoutId !== undefined) {
      clearTimeout(selfKillTimeoutId)
    }
    startSelfKillTimeout(heartbeatTimeout)
  })

  // In some cases when frpc connection fails, it tries to reconnect forever.
  // In that line we send error to frpc controller after startTunnelTimeoutThreshold
  initTimeoutId = setTimeout(() => {
    process.send({status: TUNNEL_STATUS.INIT_TIMEOUT_ERROR})
  }, startTunnelTimeoutThreshold)

  nodeCleanup((exitCode, signal) => {
    process.send({status: TUNNEL_STATUS.STOPPED})
    selfKillTimeoutId && clearTimeout(selfKillTimeoutId)
    initTimeoutId && clearTimeout(initTimeoutId)

    //TODO: should we check if proc.isRunning before run proc.stop
    proc.stop(signal, stopProcessTimeout).catch(console.log)
    unlinkSync(configFilePath)
  })

  await proc.start()

  // set selfkill in case execution-grid-tunnel crashes before init
  startSelfKillTimeout(parseInt(startTunnelTimeoutThreshold) + parseInt(heartbeatTimeout))
}

_startTunnel()
