/* eslint-disable camelcase */

import https from 'node:https'

import Aigle from 'aigle'

import githubClient from './github-client.js'
import { createPrComment } from './github-comment.js'
import { Owners } from './node-owners.js'

const fiveSeconds = 5 * 1000

const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms))

export async function removeLabelFromPR (options, label) {
  // no need to request github if we didn't resolve a label
  if (!label) {
    return
  }

  options.logger.debug('Trying to remove label: ' + label)

  try {
    await githubClient.issues.removeLabel({
      owner: options.owner,
      repo: options.repo,
      issue_number: options.prId,
      name: label
    })
  } catch (err) {
    if (err.code === 404) {
      options.logger.info('Label to remove did not exist, bailing ' + label)
      throw err
    }
    options.logger.error(err, 'Error while removing a label')
    throw err
  }

  options.logger.info('Removed a label ' + label)
  return label
}

export function getBotPrLabels (options, cb) {
  githubClient.issues.listEvents({
    owner: options.owner,
    repo: options.repo,
    page: 1,
    per_page: 100, // we probably won't hit this
    issue_number: options.prId
  }).then(res => {
    const events = res.data || []
    const ourLabels = []

    for (const event of events) {
      if (event.event === 'unlabeled') {
        const index = ourLabels.indexOf(event.label.name)
        if (index === -1) continue

        ourLabels.splice(index, 1)
      } else if (event.event === 'labeled') {
        const index = ourLabels.indexOf(event.label.name)
        if (index > -1) continue

        if (event.actor.login === 'nodejs-github-bot') {
          ourLabels.push(event.label.name)
        }
      }
    }

    cb(null, ourLabels)
  }, cb)
}

async function deferredResolveOwnersThenPingPr (options) {
  const timeoutMillis = (options.timeoutInSec || 0) * 1000
  await sleep(timeoutMillis)
  return resolveOwnersThenPingPr(options)
}

function getCodeOwnersUrl (owner, repo, defaultBranch) {
  const base = 'raw.githubusercontent.com'
  const filepath = '.github/CODEOWNERS'
  return `https://${base}/${owner}/${repo}/${defaultBranch}/${filepath}`
}

async function listFiles ({ owner, repo, prId, logger }) {
  try {
    const response = await githubClient.pulls.listFiles({
      owner,
      repo,
      pull_number: prId
    })
    return response.data.map(({ filename }) => filename)
  } catch (err) {
    logger.error(err, 'Error retrieving files from GitHub')
    throw err
  }
}

async function getDefaultBranch ({ owner, repo, logger }) {
  try {
    const data = (await githubClient.repos.get({ owner, repo })).data || { }

    if (!data.default_branch) {
      logger.error(null, 'Could not determine default branch')
      throw new Error('unknown default branch')
    }

    return data.default_branch
  } catch (err) {
    logger.error(err, 'Error retrieving repository data')
    throw err
  }
}

function getCodeOwnersFile (url, { logger }) {
  return new Promise((resolve, reject) => {
    https.get(url, (res) => {
      if (res.statusCode !== 200) {
        logger.error(`Status code: ${res.statusCode}`, 'Error retrieving OWNERS')
        return reject(res.statusCode)
      }
      let body = ''
      res.on('data', (chunk) => {
        body += chunk
      })
      res.on('end', () => {
        return resolve(body)
      })
    }).on('err', (err) => {
      logger.error(err, 'Error retrieving OWNERS')
      return reject(err)
    })
  })
}

async function resolveOwnersThenPingPr (options) {
  const { owner, repo } = options
  const times = options.retries || 5
  const interval = options.retryInterval || fiveSeconds
  const retry = fn => Aigle.retry({ times, interval }, fn)

  options.logger.debug('Getting file paths')
  const filepathsChanged = await retry(() => listFiles(options))
  options.logger = options.logger.child({ filepathsChanged })

  options.logger.debug('Getting default branch')
  const defaultBranch = await retry(() => getDefaultBranch(options))

  const url = getCodeOwnersUrl(owner, repo, defaultBranch)
  options.logger = options.logger.child({ codeownersUrl: url })

  options.logger.debug('Fetching OWNERS')
  const file = await retry(() => getCodeOwnersFile(url, options))

  const owners = Owners.fromFile(file)
  const selectedOwners = owners.getOwnersForPaths(filepathsChanged)
  options.logger = options.logger.child({ owners: selectedOwners })

  options.logger.debug('Codeowners file parsed')
  if (selectedOwners.length > 0) {
    await pingOwners(options, selectedOwners)
  }
}

function getCommentForOwners (owners) {
  return `Review requested:\n\n${owners.map(i => `- [ ] ${i}`).join('\n')}`
}

async function pingOwners (options, owners) {
  options.logger.debug('Pinging codeowners')
  try {
    await createPrComment({
      owner: options.owner,
      repo: options.repo,
      issue_number: options.prId,
      logger: options.logger
    }, getCommentForOwners(owners))
  } catch (err) {
    options.logger.error(err, 'Error while pinging owners')
    throw err
  }
  options.logger.debug('Owners pinged successfully')
}

export { deferredResolveOwnersThenPingPr as resolveOwnersThenPingPr }

// exposed for testability
export const _testExports = {
  pingOwners, getCodeOwnersFile, getCodeOwnersUrl, getDefaultBranch, listFiles, getCommentForOwners
}