import { createHash } from 'node:crypto';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const SHA256_PATTERN = /^[a-f0-9]{64}$/;
const DEFAULT_ROOT = fileURLToPath(new URL('../runtime/native/', import.meta.url));

function digest(value, label) {
  const normalized = String(value || '').toLowerCase();
  if (!SHA256_PATTERN.test(normalized)) throw new TypeError(`${label} must be a SHA-256 digest`);
  return normalized;
}

function regularFile(file, label) {
  const stat = fs.lstatSync(file);
  if (!stat.isFile() || stat.isSymbolicLink()) throw new TypeError(`${label} must be a regular file`);
}

function sha256FileSync(file, label) {
  regularFile(file, label);
  const hash = createHash('sha256');
  const descriptor = fs.openSync(file, 'r');
  const buffer = Buffer.allocUnsafe(1024 * 1024);
  try {
    for (;;) {
      const bytesRead = fs.readSync(descriptor, buffer, 0, buffer.length, null);
      if (bytesRead === 0) break;
      hash.update(buffer.subarray(0, bytesRead));
    }
  } finally {
    fs.closeSync(descriptor);
  }
  return hash.digest('hex');
}

async function sha256File(file, label) {
  regularFile(file, label);
  const hash = createHash('sha256');
  for await (const chunk of fs.createReadStream(file)) hash.update(chunk);
  return hash.digest('hex');
}

function resolvedPaths({
  rootDir = DEFAULT_ROOT,
  dedicatedPath = path.join(rootDir, 'openQ4-ded_x64'),
  gameLibPath = path.join(rootDir, 'baseoq4', 'game-mp_x64.so'),
  buildInfoPath = path.join(rootDir, 'build-info', 'build.txt'),
} = {}) {
  return { dedicatedPath, gameLibPath, buildInfoPath };
}

export function normalizeNativeIdentity(value) {
  if (!value || typeof value !== 'object' || Array.isArray(value) || value.schema !== 1) {
    throw new TypeError('native identity must use schema 1');
  }
  const artifact = (entry, filename, label) => {
    if (!entry || typeof entry !== 'object' || Array.isArray(entry) || entry.filename !== filename) {
      throw new TypeError(`${label} identity filename must be ${filename}`);
    }
    return { filename, sha256: digest(entry.sha256, label) };
  };
  return {
    schema: 1,
    dedicated: artifact(value.dedicated, 'openQ4-ded_x64', 'native dedicated binary'),
    multiplayerGameLib: artifact(value.multiplayerGameLib, 'game-mp_x64.so', 'native multiplayer GameLib'),
    buildInfo: artifact(value.buildInfo, 'build.txt', 'native build info'),
  };
}

export function readNativeIdentitySync(options = {}) {
  const files = resolvedPaths(options);
  return normalizeNativeIdentity({
    schema: 1,
    dedicated: {
      filename: 'openQ4-ded_x64',
      sha256: sha256FileSync(files.dedicatedPath, 'native dedicated binary'),
    },
    multiplayerGameLib: {
      filename: 'game-mp_x64.so',
      sha256: sha256FileSync(files.gameLibPath, 'native multiplayer GameLib'),
    },
    buildInfo: {
      filename: 'build.txt',
      sha256: sha256FileSync(files.buildInfoPath, 'native build info'),
    },
  });
}

export async function readNativeIdentity(options = {}) {
  const files = resolvedPaths(options);
  const [dedicatedSha256, gameLibSha256, buildInfoSha256] = await Promise.all([
    sha256File(files.dedicatedPath, 'native dedicated binary'),
    sha256File(files.gameLibPath, 'native multiplayer GameLib'),
    sha256File(files.buildInfoPath, 'native build info'),
  ]);
  return normalizeNativeIdentity({
    schema: 1,
    dedicated: { filename: 'openQ4-ded_x64', sha256: dedicatedSha256 },
    multiplayerGameLib: { filename: 'game-mp_x64.so', sha256: gameLibSha256 },
    buildInfo: { filename: 'build.txt', sha256: buildInfoSha256 },
  });
}
