const fs = require('fs')
const https = require('https')
const crypto = require('crypto')
const {getExpectedBinHash, getBinHash, frpcVersion} = require('@applitools/eg-frpc')

const {FrpcDownloadError} = require('../src/utils')

module.exports = {
  downloadFrpc,
  getFrpcDownloadLink,
  getManualFrpcInstallingErrorMessage,
  getFrpcErrorMessage,
}

async function downloadFrpc({
  platform,
  arch,
  cacheDirectoryPath,
  frpcPath,
  shouldUpdate,
  egTunnelManagerUrl,
  logger,
}) {
  const expectedHash = getExpectedBinHash({platform, arch})
  const url = getFrpcDownloadLink({platform, arch, egTunnelManagerUrl})

  logger.info({
    action: 'start-frpc-download',
    expectedHash,
    url,
    shouldUpdate,
  })
  try {
    await _downloadBin({filename: frpcPath, url, logger})
    const actualHash = await getBinHash(frpcPath)
    if (actualHash !== expectedHash) {
      logger.info({
        action: 'frpc-download-unexpected-checksum',
        expectedHash,
        actualHash,
      })
      throw new Error(`Checksum failed: expected: ${expectedHash} actual: ${actualHash}`)
    }
    logger.info({
      action: 'finish-frpc-download',
      expectedHash,
      actualHash,
    })
  } catch (e) {
    console.log(e.message)
    let removeFrpcError
    try {
      fs.unlinkSync(frpcPath)
    } catch (e) {
      removeFrpcError = e
    } finally {
      logger.info({
        action: 'frpc-download-failed',
        error: e.stack || e.message || String(e),
        removeFrpcSuccess: removeFrpcError ? false : true,
        ...(removeFrpcError && {
          removeFrpcError:
            removeFrpcError.stack || removeFrpcError.message || String(removeFrpcError),
        }),
      })
    }

    throw new FrpcDownloadError(
      url,
      cacheDirectoryPath,
      frpcPath,
      getFrpcErrorMessage({url, cacheDirectoryPath, shouldUpdate}),
    )
  }

  logger.info({
    action: 'set-frpc-execute-permissions',
  })
  await fs.promises.chmod(frpcPath, 0o777)
}

function getFrpcDownloadLink({
  platform,
  arch,
  egTunnelManagerUrl = 'https://exec-wus.applitools.com',
}) {
  return `${egTunnelManagerUrl}/frpc/${frpcVersion}/${platform}/${arch}`
}

function getFrpcErrorMessage({url, cacheDirectoryPath, shouldUpdate}) {
  const prefix = shouldUpdate
    ? `Execution Cloud reverse proxy update failed.`
    : `Execution Cloud reverse proxy installation failed.`
  return `${prefix} Please download the binary from ${url} and put it in ${cacheDirectoryPath}.`
}

function getManualFrpcInstallingErrorMessage({
  platform,
  arch,
  shouldUpdate,
  cacheDirectoryPath,
  egTunnelManagerUrl,
  _frpcPath,
}) {
  const prefix = shouldUpdate
    ? `Please update the Execution Cloud reverse proxy binary.`
    : `Please Download Execution Cloud reverse proxy binary and put it in ${cacheDirectoryPath}.`

  return `${prefix}. You can download the file from ${getFrpcDownloadLink({
    platform,
    arch,
    egTunnelManagerUrl,
  })}`
}

const _downloadBin = function ({filename, url, logger}) {
  return new Promise((resolve, reject) => {
    const file = fs.createWriteStream(filename)

    https
      .get(url, function (response) {
        response.pipe(file)
        file.on('finish', function () {
          logger.info({action: 'frpc-write-stream-finish-event'})
          file.close((err) => {
            logger.info({
              action: 'frpc-write-stream-close-event',
              filename,
              success: err ? false : true,
              ...(err && {error: err.stack || err.message || String(err)}),
            })
            if (!err) resolve()
            else reject(err)
          })
        })
      })
      .on('error', function (err) {
        let unlinkErr
        try {
          fs.unlinkSync(filename)
        } catch (e) {
          unlinkErr = e
        } finally {
          logger.info({
            action: 'frpc-write-stream-error-event',
            filename,
            ...(err && {error: err.stack || err.message || String(err)}),
            removeFrpcSuccess: unlinkErr ? false : true,
            ...(unlinkErr && {
              unlinkErr: unlinkErr.stack || unlinkErr.message || String(unlinkErr),
            }),
          })
        }
        reject(err)
      })
  })
}
