'use strict';

// GLOBAL
const execa = require('execa');
const path = require('path');
const semver = require('semver');

// LOCAL
const tools = require('./tools');
const configUtils = require('../csdr/config/config-utils');

// FETCH ARGS
const { dryRun, git, debug, debugGitUpdates, build, skipGitUpdates } = tools.getArgs();

const getLastTag = (folder) => {
  return Promise.resolve()
    .then(() => {
      var tag;
      try {
        tag = execa.sync('git', ['describe', '--tags', '--abbrev=0'], { cwd: folder }).stdout;
      } catch (e) {
        tag = null;
      }
      return tag;
    })
    .catch((e) => {
      throw new Error(`Error getting last tag on ${folder}`);
    });
}

const getRepoUrl = (folder) => {
  return Promise.resolve()
    .then(() => {
      const repoUrl = execa.sync('git', ['config', '--get', 'remote.origin.url'], { cwd: folder }).stdout;
      const atLocation = repoUrl.indexOf('@');
      if (atLocation > -1) {
        return repoUrl.substr(0, 8) + repoUrl.substr(atLocation + 1);
      } else {
        return repoUrl;
      }
    })
    .catch((e) => {
      throw new Error(`Error getting repo url on ${folder}`);
    })
}

const getShortHash = (folder) => {
  return Promise.resolve()
    .then(() => {
      return execa.sync('git', ['rev-parse', '--short', 'HEAD'], { cwd: folder }).stdout;
    })
    .catch((e) => {
      throw new Error(`Error get short hash on ${folder}`);
    })
}


const getTags = (folder) => {
  return Promise.resolve()
    .then(() => {
      const result = execa.sync('git', ['tag', '--sort', 'taggerdate'], { cwd: folder });
      return result.stdout.split('\n').filter(c => c.trim() !== '');
    })
    .catch((e) => {
      throw new Error(`Error getting tags on ${folder}`);
    })
}

const hasCommitsSinceLastTag = (folder, version) => {
  try {
    const result = execa.sync('git', ['log', '--oneline', `v${version}..HEAD`], { cwd: folder });
    const commits = result.stdout.split('\n').filter(c => c.trim() !== '');
    return commits.length > 0;
  } catch (e) {
    throw new Error(`Error trying to get commit log since last tag ${folder} since ${version}`);
  }
}

const commitAndPushCore = (branch, message, folder, standalone = false) => {
  if (standalone) {
    execa.sync('git', ['checkout', branch], { cwd: folder, stdio: 'inherit' });
    execa.sync('git', ['add', '.'], { cwd: folder, stdio: 'inherit' });
    execa.sync('git', ['commit', '-m', message], { cwd: folder, stdio: 'inherit' });
    execa.sync('git', ['pull', 'origin', branch], { cwd: folder, stdio: 'inherit' });
    execa.sync('git', ['push', 'origin', branch], { cwd: folder, stdio: 'inherit' });
  } else {
    execa.sync('git', ['add', '.'], { cwd: folder });
    execa.sync('git', ['commit', '-m', message], { cwd: folder });
    execa.sync('git', ['checkout', branch], { cwd: folder });
    execa.sync('git', ['pull', 'origin', branch], { cwd: folder });
    execa.sync('git', ['push', 'origin', branch], { cwd: folder });
  }
}

// double-run in case of race-condition of 2 pkg built and trying to push as the same time
const commitAndPush = (branch, message, folder, standalone = false) => {
  tools.logTitle('Commit and pushing files');
  tools.logInfo(`on branch : ${branch} / folder provided : ${folder}`);

  if (dryRun || skipGitUpdates) {
    tools.logInfo('DRY-RUN: skipping commit and push files');

  } else {

    try {
      tools.logInfo('commit and push - first try');
      commitAndPushCore(branch, message, folder, standalone);

    } catch (e2) {
      tools.logError('ERROR');
      console.log('ERROR!!!');
      if (debugGitUpdates) {
        console.log(e2);
      }

      try {
        tools.logInfo('Commit and push - second try - retrying pull & push');
        execa.sync('git', ['pull', 'origin', branch, '--rebase'], { cwd: folder });
        execa.sync('git', ['push', 'origin', branch], { cwd: folder });

      } catch (e3) {
        if (debugGitUpdates) {
          console.log(e3);
        }
        throw new Error(`Failed to push ${message} to ${folder}`);
      }
    }
  }
}



const tagVersion = (version, branch, message, folder) => {
  tools.logTitle('tagging the release');
  tools.logInfo(`Pushing changes to origin/${branch}...`);
  tools.logInfo(`Tagging MASTER branch with v${version}...`);

  if (dryRun || skipGitUpdates) {
    tools.logInfo('DRY-RUN: skipping tagging of version');
    return Promise.resolve();

  } else {
    return Promise.resolve()
      .then(() => {
        execa.sync('git', ['tag', '-a', `v${version}`, '-m', message], { cwd: folder });
        execa.sync('git', ['push', '--follow-tags', 'origin', branch], { cwd: folder });
      })
      .then(() => {
        tools.logSuccess();
      })
      .catch((e) => {
        return Promise.resolve()
        .then(() => {
          execa.sync('git', ['tag', '-a', `v${version}b`, '-m', message], { cwd: folder });
          execa.sync('git', ['push', '--follow-tags', 'origin', branch], { cwd: folder });
        })
        .then(() => {
          tools.logSuccess();
        })
        .catch((e) => {
          throw new Error(`Error trying to tag ${version} on ${folder}`);
        })
      })
  }
}

const mergeMasterToDevelop = (pkg, folder, version) => {
  tools.logTitle('Merge back on develop');
  tools.logInfo(`Merging release changes into "develop" branch...`);

  if (dryRun || skipGitUpdates) {
    tools.logInfo('DRY-RUN: skipping merge master to develop');
    return;

  } else {

    if (pkg && pkg.build && pkg.build.masterBranchOnly) {
      tools.logInfo('Merge skipped project config set to master branch only');
      return Promise.resolve();

    } else {
      return Promise.resolve()
        .then(() => {
          execa.shellSync('git fetch origin', { cwd: folder });
          execa.shellSync('git checkout develop', { cwd: folder });
          execa.shellSync('git merge origin/develop', { cwd: folder });
          execa.shellSync('git merge -X theirs master', { cwd: folder });
          execa.shellSync('git push origin develop', { cwd: folder });
          execa.shellSync('git checkout master', { cwd: folder });
        })
        .then(() => {
          tools.logSuccess();
        })
        .catch((e) => {
          throw new Error(`Error trying merging master to develop on ${folder}`);
        })
    }
  }
}


const mergeSupportToSupportDevelop = (pkg, folder, supportBranch, supportSnapshotBranch) => {
  if (!supportSnapshotBranch) {
    tools.logWarning('supportSnapshotBranch not found in package config...skipping');
    return Promise.resolve();
  }

  tools.logTitle(`Merge back on ${supportSnapshotBranch}`);
  tools.logInfo(`Merging release changes into ${supportSnapshotBranch} branch...`);

  if (dryRun || skipGitUpdates) {
    tools.logInfo('DRY-RUN: skipping merge support to supportDevelop');
    return Promise.resolve();
  }

  const supportBranchVersion = supportBranch.split('/')[1];
  const supportSnapshotBranchVersion = supportSnapshotBranch.split('-')[1];

  if (supportBranchVersion !== supportSnapshotBranchVersion) {
    tools.logInfo('skipping... support version and support snapshot versions are different');
    tools.logInfo(`[${supportBranchVersion}] vs [${supportSnapshotBranchVersion}]`);
    return Promise.resolve();
  }

  if (pkg && pkg.build && pkg.build.masterBranchOnly) {
    tools.logInfo('Merge skipped project config set to master branch only');
    return Promise.resolve();

  } else {
    return Promise.resolve()
      .then(() => {
        execa.shellSync(`git fetch origin`, { cwd: folder });
        execa.shellSync(`git checkout ${supportSnapshotBranch}`, { cwd: folder });
        execa.shellSync(`git merge origin/${supportSnapshotBranch}`, { cwd: folder });
        execa.shellSync(`git merge -X theirs ${supportBranch}`, { cwd: folder });
        execa.shellSync(`git push origin ${supportSnapshotBranch}`, { cwd: folder });
        execa.shellSync(`git checkout ${supportBranch}`, { cwd: folder });
      })
      .then(() => {
        tools.logSuccess();
      })
      .catch((e) => {
        throw new Error(`Error trying merging ${supportBranch} to ${supportSnapshotBranch} on ${folder}`);
      })
  }
}



const getGitHost = (externalRepo) => {
  const config = configUtils.global.getConfig();

  if (config.git) {
    if (git && git === 'local') {
      if (externalRepo) {
        return config.git.externals.local;
      } else {
        return config.git.local;
      }

    } else {
      if (externalRepo) {
        return config.git.externals.remote;
      } else {
        return config.git.remote;
      }
    }
  }

  return '';
}

const setRepoUrlStandalone = (pkg, folder) => {
  tools.logTitle('Set remote url stanadlone repo');

  const auth = process.env.gituser && process.env.gitpassword ? `${process.env.gituser}:${process.env.gitpassword}@` : '';
  const gitHost = configUtils.global.getConfigOptionsStandalone().GIT_HOST_REMOTE;

  if (skipGitUpdates) {
    tools.logInfo('DRY RUN - skipping');
    tools.logInfo(`executing : for host: ${gitHost} - auth: ${auth} - pkgName: ${pkg.name}`);
  } else {
    execa.sync('git', ['remote', 'set-url', '--push', 'origin', `https://${auth}${gitHost}${pkg.name}.git`], { cwd: folder, stdio: 'inherit'});
  }
}


const cloneRepoStandalone = (repoName, destinationFolder) => {
  tools.logTitle('Cloning standalone repo');

  const folder = process.cwd();
  const auth = process.env.gituser && process.env.gitpassword ? `${process.env.gituser}:${process.env.gitpassword}@` : '';
  const gitHost = configUtils.global.getConfigOptionsStandalone().GIT_HOST_REMOTE;

  tools.logInfo(`executing : for host: ${gitHost} - repoName: ${repoName} - destination folder:  ${destinationFolder}`);

  if (!tools.isDirExists(destinationFolder)) {

    if (skipGitUpdates) {
      tools.logInfo('DRY RUN - skipping');
    } else {
      execa.sync('git', ['clone', `https://${auth}${gitHost}${repoName}.git`, destinationFolder], { cwd: folder, stdio: 'inherit'});
    }
  }

  tools.logInfo(`pulling folder:  ${destinationFolder}`);

  execa.shellSync(`git pull`, { cwd: destinationFolder, stdio: 'inherit' });
}





const cloneRepo = (repoUri, folder, forced = false, shallow = false, externalRepo = false) => {
  if (!repoUri) {
    tools.logWarning('Repository not set -- bypassing clone...');
    return;
  }

  return Promise.resolve()
    .then(() => {
      if (tools.isDirExists(folder) && forced) {
        tools.logInfo('Forcing delete of folder...');
        return tools.rimraf(folder);
      }
    })

    .then(() => {
      if (!tools.isDirExists(folder)) {
        tools.logInfo(`Cloning ${repoUri} in folder "${folder}" ...`);

        const auth = process.env.gituser && process.env.gitpassword ? `${process.env.gituser}:${process.env.gitpassword}@` : '';
        const gitHost = getGitHost(externalRepo);

        if (shallow) {
          execa.shellSync(`git clone --depth 1 https://${auth}${gitHost}${repoUri} ${folder}`);
        } else {
          execa.shellSync(`git clone https://${auth}${gitHost}${repoUri} ${folder}`);
        }
        tools.logSuccess();
      }
    })

    .then(() => {
      tools.logInfo(`Pulling ${repoUri} in folder "${folder}" ...`);
      execa.shellSync(`git pull`, { cwd: folder, stdio: 'inherit' });
      tools.logSuccess();
    })
    .catch((e) => {
      if (!build) {
        console.log(e);
      }
      throw new Error(`Error trying to clone ${repoUri} into ${folder}`);
    })
}



const checkout = (branch, folder, shallow = false) => {
  tools.logInfo(`Checking out branch ${branch} in folder "${folder}"...`)

  return Promise.resolve()
    .then(() => {
      if (shallow) {
        tools.logInfo(`As shallow cloning: set ${branch} as remote branch tracking...`)
        return Promise.resolve()
          .then(() => {
            return execa.shell(`git remote set-branches --add origin ${branch}`, { cwd: folder, stdio: 'inherit' });
          })
          .then(() => {
            return execa.shell(`git fetch --depth 1 origin ${branch}`, { cwd: folder, stdio: 'inherit' });
          })
          .catch((e) => {
            throw e;
          })
      }
    })
    .then(() => {
      return execa.shell(`git checkout ${branch}`, { cwd: folder, stdio: 'inherit' })
        .then(() => {
          tools.logInfo(`Making local ${branch} branch track origin...`)
          return execa.shell(`git branch -u origin/${branch}`, { cwd: folder, stdio: 'inherit' });
        })
        .then(() => {
          tools.logInfo(`Pulling latest changes...`)
          return execa.shell('git pull', { cwd: folder, stdio: 'inherit' });
        });
    })
    .catch((e) => {
      // if (!build) {
      //   console.log(e);
      // }
      throw new Error(`Error trying to checkout ${branch} on ${folder}`);
    })
}


const cloneAndCheckout = (repoUri, folder, branch, forced, shallow) => {
  if (!repoUri) {
    tools.logWarning('Repository set to false -- clone deactivated');
    return;
  }

  tools.logInfo(`cloning ${repoUri} in ${folder} on branch ${branch}`);

  return Promise.resolve()
    .then(() => {
      return cloneRepo(repoUri, folder, forced, shallow);
    })

    .then(() => {
      if (!tools.isDirExists(folder)) {
        return;
      }

      return Promise.resolve()
        .then(() => {
          return checkout(branch, folder, shallow);
        })
        .catch((e) => {
          tools.logError(`Branch ${branch} NOT FOUND, retrying on develop`);

          return Promise.resolve()
            .then(() => {
              return checkout('develop', folder);
            })
            .catch((e2) => {
              tools.logError(`Branch develop NOT FOUND, retrying on master`);

              return Promise.resolve()
                .then(() => {
                  return checkout('master', folder);
                })
                .catch((e3) => {
                  throw e3;
                })
            })
        })
    })

    .catch((e) => {
      throw e;
    })
}



const getLastCommitAuthor = (folder) => {
  return Promise.resolve()
    .then(() => {
      return execa.shell(`git log -1 --format="%aN <%aE>" --reverse`, { cwd: folder, stdio: 'inherit' });
    })
    .catch((e) => {
      throw new Error(`Error trying to get last commit author from ${folder}`);
    })
}


const isLastCommitAuthorValid = (author) => {
  return author !== 'eUI BuildBot <DIGIT-EUI-SUPPORT@ec.europa.eu>';
}



// EXPORTS

module.exports.getShortHash = getShortHash;
module.exports.getRepoUrl = getRepoUrl;
module.exports.getGitHost = getGitHost;
module.exports.getLastTag = getLastTag;
module.exports.getTags = getTags;
module.exports.hasCommitsSinceLastTag = hasCommitsSinceLastTag;
module.exports.commitAndPush = commitAndPush;
module.exports.tagVersion = tagVersion;
module.exports.mergeMasterToDevelop = mergeMasterToDevelop;
module.exports.mergeSupportToSupportDevelop = mergeSupportToSupportDevelop;
module.exports.cloneRepo = cloneRepo;
module.exports.setRepoUrlStandalone = setRepoUrlStandalone;
module.exports.cloneRepoStandalone = cloneRepoStandalone;
module.exports.checkout = checkout;
module.exports.cloneAndCheckout = cloneAndCheckout;
module.exports.getLastCommitAuthor = getLastCommitAuthor;
module.exports.isLastCommitAuthorValid = isLastCommitAuthorValid;

