import { createHash } from "node:crypto";
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";

import git from "@agentos-software/git";

const AOS_PACKAGE_MAGIC = Buffer.from([0x89, 0x41, 0x4f, 0x53]);
const TAR_BLOCK_SIZE = 512;
const TAR_CHECKSUM_OFFSET = 148;
const TAR_CHECKSUM_SIZE = 8;
const TAR_MODE_OFFSET = 100;
const TAR_MODE_SIZE = 8;
const TAR_NAME_SIZE = 100;
const TAR_SIZE_OFFSET = 124;
const TAR_SIZE_SIZE = 12;
const GIT_EXECUTABLES = new Set([
  "./bin/git",
  "./bin/git-remote-http",
  "./bin/git-remote-https",
]);
const UPSTREAM_GIT_PACKAGE_VERSION = Buffer.from("0.3.0-rc.2", "ascii");
const REPAIRED_GIT_PACKAGE_VERSION = Buffer.from("0.3.0-zp.1", "ascii");
const READ_ONLY_EXECUTABLE_MODE = Buffer.from([0xa4, 0x81, 0x00, 0x00]);
const RUNNABLE_EXECUTABLE_MODE = Buffer.from([0xed, 0x81, 0x00, 0x00]);

const parseTarOctal = (
  packageBytes: Buffer,
  offset: number,
  length: number
): number => {
  const value = packageBytes
    .subarray(offset, offset + length)
    .toString("ascii")
    .replaceAll("\0", "")
    .trim();

  return value.length === 0 ? 0 : Number.parseInt(value, 8);
};

const readTarName = (packageBytes: Buffer, headerOffset: number): string => {
  const [name = ""] = packageBytes
    .subarray(headerOffset, headerOffset + TAR_NAME_SIZE)
    .toString("utf-8")
    .split("\0");
  return name;
};

const isEmptyTarHeader = (
  packageBytes: Buffer,
  headerOffset: number
): boolean =>
  packageBytes
    .subarray(headerOffset, headerOffset + TAR_BLOCK_SIZE)
    .every((byte) => byte === 0);

const writeTarChecksum = (packageBytes: Buffer, headerOffset: number): void => {
  packageBytes.fill(
    0x20,
    headerOffset + TAR_CHECKSUM_OFFSET,
    headerOffset + TAR_CHECKSUM_OFFSET + TAR_CHECKSUM_SIZE
  );

  let checksum = 0;
  for (
    let index = headerOffset;
    index < headerOffset + TAR_BLOCK_SIZE;
    index += 1
  ) {
    checksum += packageBytes[index] ?? 0;
  }

  const encodedChecksum = `${checksum.toString(8).padStart(6, "0")}\0 `;
  packageBytes.write(
    encodedChecksum,
    headerOffset + TAR_CHECKSUM_OFFSET,
    TAR_CHECKSUM_SIZE,
    "ascii"
  );
};

const getTarOffset = (packageBytes: Buffer): number => {
  if (
    packageBytes.length < 16 ||
    !packageBytes
      .subarray(0, AOS_PACKAGE_MAGIC.length)
      .equals(AOS_PACKAGE_MAGIC)
  ) {
    throw new Error("Invalid AgentOS package header");
  }

  return 16 + packageBytes.readUInt32LE(8) + packageBytes.readUInt32LE(12);
};

export const makeGitExecutablesRunnable = (source: Uint8Array): Buffer => {
  const packageBytes = Buffer.from(source);
  const mountIndexOffset = 16 + packageBytes.readUInt32LE(8);
  const tarOffset = getTarOffset(packageBytes);
  const versionOffset = packageBytes.indexOf(
    UPSTREAM_GIT_PACKAGE_VERSION,
    0,
    "ascii"
  );
  if (versionOffset === -1 || versionOffset >= tarOffset) {
    throw new Error("Expected AgentOS Git package version was not found");
  }
  REPAIRED_GIT_PACKAGE_VERSION.copy(packageBytes, versionOffset);

  let mountModeOffset = mountIndexOffset;
  let repairedMountEntries = 0;
  while (mountModeOffset < tarOffset) {
    mountModeOffset = packageBytes.indexOf(
      READ_ONLY_EXECUTABLE_MODE,
      mountModeOffset
    );
    if (mountModeOffset < 0 || mountModeOffset >= tarOffset) {
      break;
    }

    RUNNABLE_EXECUTABLE_MODE.copy(packageBytes, mountModeOffset);
    repairedMountEntries += 1;
    mountModeOffset += RUNNABLE_EXECUTABLE_MODE.length;
  }

  if (repairedMountEntries !== GIT_EXECUTABLES.size) {
    throw new Error(
      `Expected ${GIT_EXECUTABLES.size} Git mount entries, repaired ${repairedMountEntries}`
    );
  }

  let headerOffset = tarOffset;
  let repairedExecutables = 0;

  while (
    headerOffset + TAR_BLOCK_SIZE <= packageBytes.length &&
    !isEmptyTarHeader(packageBytes, headerOffset)
  ) {
    const name = readTarName(packageBytes, headerOffset);
    const size = parseTarOctal(
      packageBytes,
      headerOffset + TAR_SIZE_OFFSET,
      TAR_SIZE_SIZE
    );

    if (GIT_EXECUTABLES.has(name)) {
      packageBytes.write(
        "0000755\0",
        headerOffset + TAR_MODE_OFFSET,
        TAR_MODE_SIZE,
        "ascii"
      );
      writeTarChecksum(packageBytes, headerOffset);
      repairedExecutables += 1;
    }

    headerOffset +=
      TAR_BLOCK_SIZE + Math.ceil(size / TAR_BLOCK_SIZE) * TAR_BLOCK_SIZE;
  }

  if (repairedExecutables !== GIT_EXECUTABLES.size) {
    throw new Error(
      `Expected ${GIT_EXECUTABLES.size} Git executables, repaired ${repairedExecutables}`
    );
  }

  return packageBytes;
};

let cachedGitSoftware: { readonly packagePath: string } | undefined;

export const getExecutableGitSoftware = (): {
  readonly packagePath: string;
} => {
  if (cachedGitSoftware) {
    return cachedGitSoftware;
  }

  const source = readFileSync(git.packagePath);
  const repaired = makeGitExecutablesRunnable(source);
  const digest = createHash("sha256")
    .update(repaired)
    .digest("hex")
    .slice(0, 16);
  const cacheDirectory = path.join(tmpdir(), "zopu-agentos-software");
  const packagePath = path.join(cacheDirectory, `git-${digest}.aospkg`);

  mkdirSync(cacheDirectory, { recursive: true });
  if (!existsSync(packagePath)) {
    writeFileSync(packagePath, repaired);
  }

  cachedGitSoftware = { packagePath };
  return cachedGitSoftware;
};
