import util from 'util'
import childProcess from 'child_process'
const exec = util.promisify(childProcess.exec)
import { getGitDiffsFromPatchText, readChunkedCommandOutput } from '../../helpers.ts'
import { type Repository } from '../../dataTypes.ts'

export const getFileList = async (
  branchName: string, repoLocation: string
) : Promise<Set<string>> => {
  const command = `git -C ${repoLocation} ls-tree -r --name-only ${branchName}`

  const result = await exec(command)
  let files = result.stdout.split("\n").filter(item => item.length > 0 && item != ".")
  const fileSet: Set<string> = new Set(files)
  
  return fileSet
}

export const addBranchToCommitsMap = async(branchName: string, repoLocation: string, commits: Repository['commits']): Promise<void> => {
  const totalPatchesCountRes = await exec(`git -C ${repoLocation} rev-list --count ${branchName}`)
  const totalPatchesCount = parseInt(totalPatchesCountRes.stdout)
  let gitLogSubsetCmd = null
  let gitLogSubset = []
  return addBranchToCommitsMapWithSkip(branchName, repoLocation, commits, totalPatchesCount)
}

export const addBranchToCommitsMapWithSkip = async(branchName: string, repoLocation: string, commits: Repository['commits'], totalPatchesCount: number, skip = 0): Promise<void> => {
  return new Promise((resolve) => {
    if (skip >= totalPatchesCount) {
      return resolve()
    }

    const gitLogSubsetCmd = childProcess.spawn('git', ['-C', repoLocation, 'log', branchName, '--format=raw', '-p', '-n', '10', '--skip', skip.toString()], {
      stdio: [0, "pipe", "inherit"],
    })

    let gitLogSubset = []
    gitLogSubsetCmd.stdout.on('data', (data) => {
      gitLogSubset.push(data)
    })

    gitLogSubsetCmd.on('close', () => {
      addGitLogSubsetToMap(gitLogSubset.join('').split('\n'), commits)
      return addBranchToCommitsMapWithSkip(branchName, repoLocation, commits, totalPatchesCount, skip + 10).then(() => {
        return resolve()
      })
    })
  })
}

const addGitLogSubsetToMap = (gitLogSubset: string[], commits: Repository['commits']) => {
  do {
    const nextPatchStart = gitLogSubset.findIndex((line, index) => {
      return (index > 0 && line.startsWith("commit ")) || index === gitLogSubset.length - 1
    })
    const isEndOfFile = nextPatchStart === gitLogSubset.length - 1
    const currentPatch = gitLogSubset.slice(0, isEndOfFile ? nextPatchStart : nextPatchStart - 1)
    gitLogSubset = gitLogSubset.slice(nextPatchStart)

    const hash = currentPatch[0].replace("commit ", "").trim()

    // exit early if the commits map already includes this hash
    if (commits.has(hash)) {
      continue
    }

    const nextBlankLine = currentPatch.findIndex(line => line === "") || currentPatch.length
    const commitInfoSection = currentPatch.slice(0, nextBlankLine)

    let author: string, date: string
    let isMerge = false
    let parent: string | null = null

    commitInfoSection.forEach((commitInfoLine) => {
      if (commitInfoLine.startsWith("author")) {
        const lineWords = commitInfoLine.replace("author ", "").trim().split(' ')
        author = lineWords.slice(0, lineWords.length - 2).join(' ')
        date = lineWords[lineWords.length - 2]
      }
      if (commitInfoLine.startsWith("parent")) {
        parent = commitInfoLine.replace("parent ", "").trim()
      }
    })

    // Mark as a merge if more than one line starts with "parent"
    if (commitInfoSection.filter(line => line.startsWith("parent")).length > 1) {
      isMerge = true
    }

    let diffStart = currentPatch.findIndex((line) => {
      return line.startsWith("diff ")
    })

    if (diffStart === -1) {
      diffStart = currentPatch.length
    }
    // Git log is indent four spaces by default -- remove those.
    const commitMessage = currentPatch.slice(nextBlankLine + 1, diffStart).map(str => str.replace("    ", "")).filter((line) => {
      // git log --porcelain output adds these "Ignore-this:" lines and I'm not sure what they are
      return !line.startsWith('Ignore-this: ')
    }).join("\n").trim()

    // TODO: this is very slow on commits that have a huge 20k line package-lock.json change
    const diffs = getGitDiffsFromPatchText(currentPatch.slice(diffStart).join("\n"))
    commits.set(hash, {
      hash,
      message: commitMessage,
      isMerge: isMerge,
      author,
      date: new Date(parseInt(date) * 1000), // git timestamp is number of seconds,
                                             // js Date() exects milliseconds
      diffs,
      parent,
    })
  } while (gitLogSubset.length > 1)
}

export type BlameInfo = {
  sha: string,
  author: string,
}

export const getFileLastTouchInfo = async (
  filename: string, sha: string, repoLocation: string
) : Promise<Array<BlameInfo>> => {
  const regex = RegExp(".* [0-9]+ [0-9]+")
  const output = await readChunkedCommandOutput('git', ['-C', repoLocation, 'blame', '--porcelain', sha, filename])
  const outputLines = output.split("\n")
  const initialValue: Array<Array<string>> = [[outputLines[0]]]
  const chunked = outputLines.reduce((accumulator, currentLine, currentIndex) => {
    // skip the first iteration since we already gave it initialValue
    if (currentIndex == 0) { return accumulator }

    if (currentLine.match(regex)) {
      accumulator.push([currentLine])
      return accumulator
    }
    else {
      accumulator[accumulator.length - 1].push(currentLine)
      return accumulator
    }
  }, initialValue)

  let currentAuthor = ''
  let authorsAndShasByLine: Array<BlameInfo> = []
  chunked.forEach((chunk) => {
    let shaAndLineNumberParts = chunk[0].split(' ')
    let line = parseInt(shaAndLineNumberParts[2])
    let sha = shaAndLineNumberParts[0]
    if (chunk[1] && chunk[1].startsWith('author ')) {
      currentAuthor = chunk[1].replace('author ', '')
    }
    authorsAndShasByLine[line - 1] = {sha, author: currentAuthor}
  })
  return authorsAndShasByLine
}
