import { createWriteStream, readFileSync, writeFileSync } from 'fs';
import { get } from 'https';
import { join } from 'path';
import { pipeline } from 'stream';
import { compareVersion, IVersion, parseVersion } from './versions';

/**
 * Post-install script. Downloads the binary for the current Node.js version
 * from the Gitub releases page, if it's available.
 */

const builtPlatforms: { [K in NodeJS.Platform]?: string } = {
  win32: 'windows-latest',
  linux: 'ubuntu-latest',
  darwin: 'macos-latest',
};

const { version } = require('../../package.json');
const repoUrl = process.env.BLAKE3_REPO_URL || 'https://github.com/connor4312/blake3';
const targets = require('../../targets.json');
const bindingPath = join(__dirname, '..', 'native.node');

async function install() {
  const current = parseVersion(process.version);
  const api = getBestAbiVersion(current);
  if (!api) {
    console.error(
      'Your Node.js release is out of LTS and BLAKE3 bindings are not built for it. Update it to use native BLAKE3 bindings.',
    );
    return fallback();
  }

  const platform = builtPlatforms[process.platform];
  if (!platform) {
    console.error(`BLAKE3 bindings are not built for your platform (${process.platform})`);
    return fallback();
  }

  console.log(
    `Retrieving native BLAKE3 bindings for Node ${api.nodeVersion} on ${process.platform}...`,
  );
  await download(`${repoUrl}/releases/download/v${version}/${platform}-${api.abiVersion}.node`);

  try {
    require(bindingPath);
  } catch (e) {
    console.log(`Error trying to import bindings: ${e.message}`);
    return fallback();
  }

  useNativeImport();
  console.log('BLAKE3 bindings retrieved');
}

function getBestAbiVersion(current: IVersion) {
  for (const targetVersion of Object.keys(targets)) {
    const parsed = parseVersion(targetVersion);
    if (compareVersion(current, parsed) >= 0) {
      return { nodeVersion: targetVersion, abiVersion: targets[targetVersion] };
    }
  }

  return undefined;
}

function fallback() {
  console.error('BLAKE3 will use slower WebAssembly bindings when required in Node.js');
}

async function download(url: string): Promise<boolean> {
  return new Promise<boolean>(resolve => {
    const onError = (err: Error) => {
      console.error(`Could not download binding from ${url}: ${err.stack || err.message}`);
      resolve(false);
    };

    const req = get(url, res => {
      if (res.headers.location) {
        resolve(download(res.headers.location));
        return;
      }

      if (!res.statusCode || res.statusCode >= 300) {
        console.error(`Unexpected ${res.statusCode} from ${url}`);
        resolve(false);
        return;
      }

      pipeline(res, createWriteStream(bindingPath), err => (err ? onError(err) : resolve(true)));
    });

    req.on('error', onError);
  });
}

function useNativeImport() {
  const indexFile = join(__dirname, '..', 'index.js');
  const contents = readFileSync(indexFile, 'utf-8');
  writeFileSync(indexFile, contents.replace('"./node"', '"./node-native"'));
}

install().catch(err => {
  console.error(`There was an uncaught error installing native bindings: ${err.stack}`);
  fallback();
});