const generator = require('generate-password')
const CorelliumClient = require('@corellium/client-api')
const {
  readProfile,
  updateProfile
} = require('./profile')
const axios = require('axios').default
axios.defaults.headers.common['User-Agent'] = `corellium-cli v${require('../package.json').version}`
const superagent = require('superagent')
const https = require('https')

const waitForIntervalMilliSec = 5000

const planOptions = (planId) => {
  let usageBased = false
  let cores, licenseType
  let m = /^(individual)-cloud-(\d+)-device(?:-(\d+)-month){0,1}$/.exec(planId)
  if (m) {
    licenseType = m[1]
    const devicesStr = m[2]
    cores = parseInt(devicesStr) * 2
  } else if (m = /^(enterprise|standard)-cloud-(\d+)-core(?:-(\d+)-month){0,1}$/.exec(planId)) { /* eslint-disable-line no-cond-assign */
    licenseType = m[1]
    const coresStr = m[2]
    cores = parseInt(coresStr)
  } else if (m = /^(enterprise|individual)-cloud-usage-based$/.exec(planId)) { /* eslint-disable-line no-cond-assign */
    usageBased = true
    if (m[1] === 'enterprise') {
      licenseType = 'enterprise-usage'
      cores = 200
    } else if (m[1] === 'individual') {
      licenseType = 'individual-usage'
      cores = 100
    }
  }
  return {
    licenseType: /** @type {LicenseType} */ (licenseType),
    cores,
    usageBased
  }
}

const makePassword = () => {
  return generator.generate({
    length: 15,
    numbers: true,
    excludeSimilarCharacters: true
  })
}

const sleep = (milliseconds) => {
  return new Promise((resolve) => setTimeout(resolve, milliseconds))
}

const waitForState = async (instanceId, fn, state, attempts = 20, reporterFn = null) => {
  const response = await fn(instanceId)
  if (typeof reporterFn === 'function') {
    reporterFn(response)
  }
  if (response.state !== state && attempts > 0) {
    await sleep(waitForIntervalMilliSec)
    await waitForState(instanceId, fn, state, --attempts, reporterFn)
  }
}

async function getApi () {
  const profile = readProfile()
  const api = new CorelliumClient.CorelliumApi()
  api.apiClient.basePath = profile.endpoint + '/api'
  api.apiClient.defaultHeaders = {
    Authorization: `Bearer ${profile.token}`,
    'User-Agent': `corellium-cli v${require('../package.json').version}`
  }

  const CACert = await getCACert(profile)
  api.apiClient.requestAgent = new https.Agent({ ca: CACert })

  return api
}

async function getAxios () {
  const profile = readProfile()
  axios.defaults.headers.common.Authorization = `Bearer ${profile.token}`
  const CACert = await getCACert(profile)
  axios.defaults.httpsAgent = new https.Agent({ ca: CACert })
  return axios
}

// Read the CA Cert from profile, if present, test that it works, and return it so that we can set the request agent cert.
// If the cert is not present in the profile, or is corrupted for some reason:
// 1. Make a single insecure request,
// 2. Read the cert from the response TLS socket,
// 3. Write the cert to the profile file to cache it.
//
// This is using superagent because superagent is the underlying library @corellium/client-api and
// it's generator: https://openapi-generator.tech/docs/generators/javascript already use under the hood.
// So although this looks like it's adding yet *another* HTTP request library, it isn't.
async function getCACert (profile) {
  if (!profile.CACert) {
    // No cert present. Make a request (which we expect to fail) to check if the failure is due to the self-signed cert
    try {
      await superagent.get(`${profile.endpoint}/api/v1/ready`)
    } catch (e) {
      if (!e.response && e.code === 'DEPTH_ZERO_SELF_SIGNED_CERT') {
        return await reRequestInsecure(profile.endpoint)
      }

      throw e // rethrow other errors
    }
  }

  // load and try the existing cert:
  try {
    await superagent
      .get(`${profile.endpoint}/api/v1/ready`)
      .ca(profile.CACert)

    return profile.CACert
  } catch (e) {
    if (!e.response && e.code === 'DEPTH_ZERO_SELF_SIGNED_CERT') {
      return await reRequestInsecure(profile.endpoint)
    }

    throw e // rethrow other errors
  }
}

async function reRequestInsecure (endpoint) {
  // Make the request again, this time ignoring SSL errors so we can get the certificate from the response
  const res = await superagent
    .get(`${endpoint}/api/v1/ready`)
    .disableTLSCerts()

  // Take the received cert, format it properly and write to profile
  const certDERFormat = res.res.socket.getPeerCertificate(true).raw
  const certPEMFormat = '-----BEGIN CERTIFICATE-----\n' + certDERFormat.toString('base64').match(/.{0,64}/g).join('\n') + '-----END CERTIFICATE-----'
  await updateProfile({ CACert: certPEMFormat })

  return certPEMFormat
}

const validateNonEmpty = (input) => input !== ''

module.exports = {
  planOptions,
  makePassword,
  waitForState,
  getApi,
  getAxios,
  validateNonEmpty,
  getCACert,
  sleep,
  waitForIntervalMilliSec
}
