'use strict'

const { childProcess } = require('./util-functions')
const { spawn } = require('child_process')
const fs = require('fs')
const path = require('path')

module.exports = class GitOp {
    constructor(settings) {
        this.settings = settings
        this.tmpCredStoreFile = path.resolve(require('os').tmpdir(), `.git-credentials-${process.pid}`)
    }
    // Get Merge Base

    run(args) {
        return new Promise((resolve, reject) => {
            const child = spawn('git', args, { cwd: this.settings.dir })
            let stdout = ''
            let stderr = ''
            child.stderr.on('data', data => {
                stderr += data
            })
            child.stdout.on('data', data => {
                stdout += data
            })
            child.on('exit', exitCode => {
                // console.log(`> git ${args.join(' ')} # ${exitCode}`)
                if (exitCode === 0) {
                    resolve({ stdout, stderr, exitCode })
                } else {
                    reject(
                        new Error({
                            stdout,
                            stderr,
                            exitCode,
                            cwd: this.settings.dir,
                        })
                    )
                }
            })
        })
    }

    /**
     * Set GIT credentials to be used by any git command tht needs remote access (e.g.: fetch)
     * @param {*} workDir
     * @param {*} remoteUrl
     * @param {*} cred
     * @todo https://alanedwardes.com/blog/posts/git-username-password-environment-variables/
     */
    _setupCredentials(workDir, remoteUrl, cred) {
        // git config --global credential.helper 'store --file ~/.git-credentials'
        // git credential-store --file ~/.git-credentials store < .gitcredentials
        // result: https://fakeUsername:fakePass@apps.nrs.gov.bc.ca
        const gitCredentialHelper = `store --file=${this.tmpCredStoreFile}`
        return (
            Promise.resolve(true)
                // Check if 'credential.helper' has been set
                .then(() => {
                    return childProcess('git', ['config', '--local', 'credential.helper'], { cwd: workDir })
                })
                // if 'credential.helper' has NOT been set, it wil exit with code 1
                .catch(proc => {
                    // anything other than exit 1 is an unexpected error
                    if (proc.exitCode !== 1) {
                        return Promise.reject(proc)
                    }
                    return Promise.resolve(true)
                        .then(() => {
                            const host = new URL(remoteUrl).host
                            fs.writeFileSync(
                                this.tmpCredStoreFile,
                                `https://${cred.username}:${cred.password}@${host}\n`,
                                { encoding: 'utf8' }
                            )
                        })
                        .then(() => {
                            return childProcess(
                                'git',
                                ['config', '--local', 'credential.helper', gitCredentialHelper],
                                { cwd: workDir }
                            )
                        })
                        .then(() => {
                            // register a exit hook to delete the temporary file
                            process.once('exit', code => {
                                if (fs.existsSync(this.tmpCredStoreFile)) {
                                    console.log(`Deleting ${this.tmpCredStoreFile}`)
                                    fs.unlinkSync(this.tmpCredStoreFile)
                                }
                            })
                        })
                })
        )
    } // _setupGitGlobalCredential

    clear() {
        if (fs.existsSync(this.tmpCredStoreFile)) {
            console.log(`Deleting ${this.tmpCredStoreFile}`)
            fs.unlinkSync(this.tmpCredStoreFile)
        }
    }

    getMergeBase(changeBranch, changeTarget) {
        return this.run(['merge-base', `remotes/origin/${changeBranch}`, `remotes/origin/${changeTarget}`]).then(
            output => {
                return output.stdout.split('\n')[0]
            }
        )
    }

    // Get latest Commit
    getLatestCommitOnTarget(changeTarget) {
        return this.run(['rev-parse', `remotes/origin/${changeTarget}`]).then(output => {
            return output.stdout.split('\n')[0]
        })
    }

    // Verify that latest commit is equal to merge-base commit
    isTargetBranchOutofSync() {
        return this._setupCredentials(this.settings.dir, this.settings.url, this.settings.credentials).then(() => {
            return this.getMergeBase(this.settings.branch.name, this.settings.change.target).then(commitHash1 => {
                return this.getLatestCommitOnTarget(this.settings.change.target).then(commitHash2 => {
                    return new Promise(resolve => {
                        console.log(`Source Branch Common Ancestor commit: ${commitHash1}`)
                        console.log(`Target Branch Last Commit - commitHash2: ${commitHash2}`)
                        if (commitHash1 === commitHash2) {
                            resolve(true)
                        } else {
                            throw new Error(
                                '\n --------------------------------------- \n Status: Cannot be Merged! \n Reason: Your branch is out of sync from target. \n Solution: Rebase, push and then run the Pipeline \n  ---------------------------------------'
                            )
                        }
                    })
                })
            })
        })
    }
} // end GitOp class
