require('colors')
const fs = require('fs')
const YAML = require('yamljs')
const commitAndTagVersion = require('commit-and-tag-version')
const { execShell, getNormalizedBranchName } = require('./utils')

const sourceBranch = 'origin/main'

module.exports = async (options, logger) => {
  const git_directory = options.git_directory || process.env.BITBUCKET_CLONE_DIR || process.cwd();
  const version_file = `${git_directory}/version.yaml`;

  let version_info = getVersionInfo(version_file);
  if (!version_info)
    version_info = await deriveVersionInfo(git_directory);

  if (!version_info) return {}
  logger('Successfully derived version.'.green)

  const response = prepareResponse(options, version_info);
  writeVersionInfo(version_file, version_info, response);

  logger(JSON.stringify(response, null, 2).green)
  return response
}

function getVersionInfo(version_file) {
  if (fs.existsSync(version_file)) {
    return YAML.load(version_file);
  }
  return null;
}

async function deriveVersionInfo(git_directory) {
  const tracker = setupVersionTracker(git_directory);

  try {
    const currentVersion = await getCurrentVersion(sourceBranch);
    await createVersionFile(tracker, currentVersion);

    const gitBranchExec = await getCurrentBranchName();
    const isMain = checkIfMainBranch(gitBranchExec);
    const isPrerelease = !isMain;

    await incrementVersion(tracker, isPrerelease);
    const metadata = await buildGitMetadata(tracker, gitBranchExec, isPrerelease);

    await cleanupVersionTracker(tracker);  // Cleanup before returning
    return {
      apiVersion: "view.do/v1",
      kind: "Version",
      metadata: {
        name: metadata.BranchName
      },
      spec: {
        metadata: metadata
      }
    }
  }
  catch (err) {
    await cleanupVersionTracker(tracker);
    console.error(err)
    return process.exit(1);
  }
}

function setupVersionTracker(git_directory) {
  return {
    filename: `${git_directory}/.versionrc.json`,
    type: "json",
    // cleanup tmp file when CLI exits - 0: exit shell; 2: interrupt; 3: quit; 15: terminate
    cleanupScript: `trap "rm -f ${git_directory}/.versionrc.json" 0 2 3 15`
  };
}

async function getCurrentVersion(sourceBranch) {
  const gitVersionScript = `git describe ${sourceBranch} --tag --abbrev=0`;

  try {
    const version = await execShell(gitVersionScript, { silent: true });
    return version.trim();
  } catch (err) {
    if (err.includes('fatal: No names found, cannot describe anything.')) {
      // This specific error usually means no tags are found, likely in new repos
      console.log('No version tags found, defaulting to 0.1.0 for new repository.');
      return '0.1.0';
    } else {
      // For other errors, rethrow them
      console.error(`Error fetching version from ${sourceBranch}:`, err.message);
      throw err;
    }
  }
}

async function createVersionFile(tracker, currentVersion) {
  const tmpVersionScript = `printf '{"version":"%s"}' "${currentVersion}" > ${tracker.filename}`;
  await execShell(tmpVersionScript, { silent: true });
}

async function getCurrentBranchName() {
  const gitBranchScript = 'git rev-parse --abbrev-ref HEAD';
  const branchName = await execShell(gitBranchScript, { silent: true });
  return branchName.trim();
}

function checkIfMainBranch(gitBranch) {
  return /(^main$|^origin\/main$)/.test(gitBranch);
}

async function incrementVersion(tracker, isPrerelease) {
  const prereleaseSuffix = 'beta';
  await commitAndTagVersion({
    noVerify: true,
    skip: {
      changelog: true,
      commit: true,
      tag: true
    },
    prerelease: !isPrerelease ? false : prereleaseSuffix,
    bumpFiles: [tracker],
    packageFiles: [tracker],
    tagPrefix: ''
  });
}

async function buildGitMetadata(tracker, gitBranchExec, isPrerelease) {
  const commitsAhead = await getCommitsAheadOfMain();
  const semVer = await require(`${tracker.filename}`).version;
  let newSemVer = isPrerelease ? semVer.replace(/\.0$/, `.${commitsAhead}`) : semVer;
  const gitShaScript = "git rev-parse --short HEAD";
  const gitShaExec = await execShell(gitShaScript, { silent: true });

  return {
    ShortSha: gitShaExec.trim(),
    SemVer: newSemVer,
    BranchName: gitBranchExec,
    AssemblySemVer: `${semVer.replace(`-beta`, '')}` + (isPrerelease ? '' : '.0'),
    IsPrerelease: isPrerelease
  };
}

async function getCommitsAheadOfMain() {
  const countCommitsScript = `git rev-list --count ${sourceBranch}..HEAD`;
  try {
    const commitCount = await execShell(countCommitsScript, { silent: true });
    return commitCount.trim();
  } catch (err) {
    console.error('Error counting commits:', err);
    return '0';
  }
}

async function cleanupVersionTracker(tracker) {
  await execShell(tracker.cleanupScript, { silent: true });
}

function prepareResponse(options, version_info) {
  const branch = options.branch_slug
    || process.env.BITBUCKET_BRANCH
    || version_info.spec.metadata.BranchName;
  const namespace = options.namespace || getNormalizedBranchName(branch);
  const build_id = options.build_id || `${getNormalizedBranchName(branch)}-${version_info.spec.metadata.ShortSha}`;
  const version = options.version || version_info.spec.metadata.SemVer;
  const [major, minor] = version.split('.').map(num => (num !== undefined ? parseInt(num, 10) : undefined));
  const assembly_version = version_info.spec.metadata.AssemblySemVer || version;
  const is_prerelease = version_info.spec.metadata.IsPrerelease || false;

  // for any branch 'branch-type/topic', slugify topic portion as BRANCH_SLUG='topic'
  // this value should be safe to use as part of a domain segment
  const branch_slug = getNormalizedBranchName(branch
    .replace(/^.*\//i, '') // remove prefix, eg ('release', or any other branch-type)
    .replace(/[^0-9a-zA-Z\-]/, '')) // remove non-hyphenated-alpha-numerics

  return {
    namespace,
    build_id,
    branch,
    branch_slug,
    version,
    assembly_version,
    major,
    minor,
    is_prerelease
  }
}

function writeVersionInfo(version_file, version_info, response) {
  version_info.spec.data = response
  fs.writeFileSync(version_file, YAML.stringify(version_info, 8, 2))
  fs.appendFileSync(version_file, "---\n")
}