const R = require("ramda");
const ssri = require("ssri");

const urlToName = require("./urlToName");
const { execFileSync } = require("child_process");

// fetchgit transforms
//
// "shell-quote@git+https://github.com/srghma/node-shell-quote.git#without_unlicenced_jsonify":
//   version "1.6.0"
//   resolved "git+https://github.com/srghma/node-shell-quote.git#1234commit"
//
// to
//
// fetchGit {
//   url = "https://github.com/srghma/node-shell-quote.git";
//   ref = "without_unlicenced_jsonify";
//   rev = "1234commit";
// }
//
// and transforms
//
// "@graphile/plugin-supporter@git+https://1234user:1234pass@git.graphile.com/git/users/1234user/postgraphile-supporter.git":
//   version "0.6.0"
//   resolved "git+https://1234user:1234pass@git.graphile.com/git/users/1234user/postgraphile-supporter.git#1234commit"
//
// to
//
// fetchGit {
//   url = "https://1234user:1234pass@git.graphile.com/git/users/1234user/postgraphile-supporter.git";
//   ref = "master";
//   rev = "1234commit";
// }

function prefetchgit(url, rev) {
  return JSON.parse(
    execFileSync(
      "nix-prefetch-git",
      ["--rev", rev, url, "--fetch-submodules"],
      {
        stdio: ["ignore", "pipe", "ignore"],
        timeout: 60000
      }
    )
  ).sha256;
}

function fetchgit(fileName, url, rev, branch, builtinFetchGit) {
  const repo = builtinFetchGit
    ? `fetchGit ({
         url = "${url}";
         ref = "${branch}";
         rev = "${rev}";
       } // (if builtins.compareVersions "2.4pre" builtins.nixVersion < 0 then {
         # workaround for https://github.com/NixOS/nix/issues/5128
         allRefs = true;
       } else {}))`
    : `fetchgit {
         url = "${url}";
         rev = "${rev}";
         sha256 = "${prefetchgit(url, rev)}";
       }`;

  return `    {
    name = "${fileName}";
    path =
      let repo = ${repo};
      in runCommand "${fileName}" { buildInputs = [gnutar]; } ''
        # Set u+w because tar-fs can't unpack archives with read-only dirs
        # https://github.com/mafintosh/tar-fs/issues/79
        tar cf $out --mode u+w -C \${repo} .
      '';
  }`;
}

/**
 * Parse an integrity hash out of an SSRI string.
 *
 * Provides a default and uses the "best" supported algorithm if there are multiple.
 */
function parseIntegrity(maybeIntegrity, fallbackHash) {
  if (!maybeIntegrity && fallbackHash) {
    return { algo: "sha1", hash: fallbackHash };
  }

  const integrities = ssri.parse(maybeIntegrity);
  for (const key in integrities) {
    if (!/^sha(1|256|512)$/.test(key)) {
      delete integrities[key];
    }
  }

  algo = integrities.pickAlgorithm();
  hash = integrities[algo][0].digest;
  return { algo, hash };
}

function fetchLockedDep(builtinFetchGit) {
  return function(pkg) {
    const { integrity, nameWithVersion, resolved } = pkg;

    if (!resolved) {
      console.error(
        `yarn2nix: can't find "resolved" field for package ${nameWithVersion}, you probably required it using "file:...", this feature is not supported, ignoring`
      );
      return "";
    }

    const [url, sha1OrRev] = resolved.split("#");

    const fileName = urlToName(url);

    if (resolved.startsWith("https://codeload.github.com/")) {
      const s = resolved.split("/");
      const githubUrl = `https://github.com/${s[3]}/${s[4]}.git`;
      const githubRev = s[6];

      const [_, branch] = nameWithVersion.split("#");

      return fetchgit(
        fileName,
        githubUrl,
        githubRev,
        branch || "master",
        builtinFetchGit
      );
    }

    if (url.startsWith("git+") || url.startsWith("git:")) {
      const rev = sha1OrRev;

      const [_, branch] = nameWithVersion.split("#");

      const urlForGit = url.replace(/^git\+/, "");

      return fetchgit(
        fileName,
        urlForGit,
        rev,
        branch || "master",
        builtinFetchGit
      );
    }

    const { algo, hash } = parseIntegrity(integrity, sha1OrRev);

    return `    {
      name = "${fileName}";
      path = fetchurl {
        name = "${fileName}";
        url  = "${url}";
        ${algo} = "${hash}";
      };
    }`;
  };
}

const HEAD = `
{ fetchurl, fetchgit, linkFarm, runCommand, gnutar }: rec {
  offline_cache = linkFarm "offline" packages;
  packages = [
`.trim();

// Object -> String
function generateNix(pkgs, builtinFetchGit) {
  const nameWithVersionAndPackageNix = R.map(
    fetchLockedDep(builtinFetchGit),
    pkgs
  );

  const packagesDefinition = R.join(
    "\n",
    R.values(nameWithVersionAndPackageNix)
  );

  return R.join("\n", [HEAD, packagesDefinition, "  ];", "}"]);
}

module.exports = generateNix;
