






import {
  access,
  copyFile,
  cp,
  lstat,
  mkdir,
  mkdtemp,
  readFile,
  readdir,
  realpath,
  rm,
  stat,
  statfs,
  writeFile,
} from "node:fs/promises";
import { constants as fsConstants, createReadStream } from "node:fs";
import { createHash, createHmac, timingSafeEqual } from "node:crypto";
import { dirname, isAbsolute, join, relative } from "node:path";
import { tmpdir } from "node:os";
import { spawn } from "node:child_process";





const EXCLUDED_APP_DIRECTORIES = new Set(["tmp", "routetu", "narzedzia", "node"]);
const MAX_EMPLOYEE_SEED_DATABASE_BYTES = 128 * 1024 * 1024;
const MAX_EMPLOYEE_SEED_ATTACHMENTS = 20_000;
const MAX_EMPLOYEE_SEED_ATTACHMENT_BYTES = 25 * 1024 * 1024;
const MAX_EMPLOYEE_SEED_TOTAL_ATTACHMENT_BYTES = 2 * 1024 * 1024 * 1024;

const EMPLOYEE_README = `Sekagent - baza pracownika
==========================

Jak zacząć:
1. Rozpakowany folder "Sekagent" umieść w wygodnym miejscu (np. na Pulpicie).
2. Uruchom plik "Uruchom Baza Polis w tle.vbs" - przy zegarku pojawi się ikonka Sekagent.
3. Przy pierwszym uruchomieniu ustaw swój PIN (4-8 cyfr). Nazwa konta jest już nadana.
4. Do kalkulacji składek potrzebna jest przeglądarka Google Chrome.

Jak przekazać swoje polisy centrali:
Ustawienia (ikona koła zębatego) -> zakładka "Agencja" -> "Paczka dla centrali".
Powstanie jeden plik ZIP - wyślij go centrali.
`;

function runCompressArchive(environmentVariables, timeoutMs = 300000) {
  return new Promise((resolveCompress, rejectCompress) => {
    
    const child = spawn("powershell.exe", [
      "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command",
      "$sources = $env:BP_SOURCES -split '\\|'; Compress-Archive -LiteralPath $sources -DestinationPath $env:BP_ZIP -CompressionLevel Optimal -Force",
    ], {
      env: { ...process.env, ...environmentVariables },
      stdio: ["ignore", "ignore", "pipe"],
      windowsHide: true,
    });
    let stderrText = "";
    child.stderr.on("data", (chunk) => { stderrText += String(chunk); });
    const timeout = globalThis.setTimeout(() => {
      child.kill();
      rejectCompress(new Error("Pakowanie trwało zbyt długo."));
    }, timeoutMs);
    child.on("error", (error) => {
      globalThis.clearTimeout(timeout);
      rejectCompress(error);
    });
    child.on("exit", (code) => {
      globalThis.clearTimeout(timeout);
      if (code === 0) {
        resolveCompress();
      } else {
        console.error(`Compress-Archive zakończył się kodem ${code}: ${stderrText.slice(0, 500)}`);
        rejectCompress(new Error("Nie udało się zbudować paczki ZIP."));
      }
    });
  });
}

export function compressPathsToZip(sourcePaths, zipPath) {
  return runCompressArchive({
    BP_SOURCES: sourcePaths.join("|"),
    BP_ZIP: zipPath,
  });
}

async function pathExists(candidatePath) {
  try {
    await access(candidatePath, fsConstants.R_OK);
    return true;
  } catch {
    return false;
  }
}

function normalizePackageAccountId(value) {
  const compact = String(value ?? "").toUpperCase().replace(/[^A-Z0-9]/g, "");
  if (!/^[A-Z2-9]{12}$/.test(compact)) return "";
  return `${compact.slice(0, 4)}-${compact.slice(4, 8)}-${compact.slice(8, 12)}`;
}

export function prepareEmployeeSeedDatabase(databaseContents, employee) {
  if (!databaseContents) {
    return {
      version: 6,
      updatedAt: new Date().toISOString(),
      accountId: employee.id,
      accountName: employee.label,
      policies: [],
      deletedPolicies: [],
      calendarTasks: [],
    };
  }
  let serializedDatabase;
  try {
    serializedDatabase = typeof databaseContents === "string"
      ? databaseContents
      : JSON.stringify(databaseContents);
  } catch {
    throw new Error("Wybrane dane polis nie są poprawnym plikiem JSON.");
  }
  if (Buffer.byteLength(serializedDatabase, "utf8") > MAX_EMPLOYEE_SEED_DATABASE_BYTES) {
    throw new Error("Wybrane dane polis przekraczają limit 128 MB.");
  }
  let parsed;
  try {
    parsed = JSON.parse(serializedDatabase);
  } catch {
    throw new Error("Wybrane dane polis nie są poprawnym plikiem JSON.");
  }
  if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.policies)) {
    throw new Error("Wybrane dane nie wyglądają na bazę polis.");
  }
  const inputVersion = Number(parsed.version);
  if (!Number.isInteger(inputVersion) || inputVersion < 1 || inputVersion > 6) {
    throw new Error("Baza polis ma brakującą lub nieobsługiwaną wersję.");
  }
  if (parsed.policies.length > 10_000) {
    throw new Error("Jedna paczka pracownika może zawierać maksymalnie 10 000 polis.");
  }
  const policies = parsed.policies.map((policy) => {
    if (!policy || typeof policy !== "object" || Array.isArray(policy)) {
      throw new Error("Paczka zawiera nieprawidłowy rekord polisy.");
    }
    const ownerId = normalizePackageAccountId(policy.ownerId);
    if (!ownerId) {
      throw new Error("Każda przekazywana polisa musi mieć zapisanego właściciela rekordu.");
    }
    return { ...policy, ownerId };
  });
  validateEmployeeSeedAttachments(policies);
  return {
    version: 6,
    updatedAt: new Date().toISOString(),
    accountId: employee.id,
    accountName: employee.label,
    policies,
    deletedPolicies: [],
    calendarTasks: [],
  };
}

function getInlineAttachmentBytes(attachment) {
  const dataUrl = String(attachment?.dataUrl ?? "");
  const match = dataUrl.match(
    /^data:([^;,]+);base64,((?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?)$/i,
  );
  if (!match) {
    throw new Error("Załącznik osadzony w bazie ma nieprawidłowy format.");
  }
  const base64 = match[2];
  const paddingBytes = base64.endsWith("==") ? 2 : (base64.endsWith("=") ? 1 : 0);
  return Math.floor((base64.length * 3) / 4) - paddingBytes;
}

function validateEmployeeSeedAttachments(policies) {
  let attachmentCount = 0;
  let inlineAttachmentBytes = 0;
  for (const policy of policies) {
    for (const attachment of Array.isArray(policy?.attachments) ? policy.attachments : []) {
      attachmentCount += 1;
      if (attachmentCount > MAX_EMPLOYEE_SEED_ATTACHMENTS) {
        throw new Error(`Paczka może zawierać maksymalnie ${MAX_EMPLOYEE_SEED_ATTACHMENTS} załączników.`);
      }
      if (!attachment || typeof attachment !== "object" || Array.isArray(attachment)) {
        throw new Error("Paczka zawiera nieprawidłowy rekord załącznika.");
      }
      if (attachment.storage === "file") continue;
      const attachmentBytes = getInlineAttachmentBytes(attachment);
      if (attachmentBytes > MAX_EMPLOYEE_SEED_ATTACHMENT_BYTES) {
        throw new Error("Pojedynczy załącznik w paczce nie może przekraczać 25 MB.");
      }
      inlineAttachmentBytes += attachmentBytes;
      if (inlineAttachmentBytes > MAX_EMPLOYEE_SEED_TOTAL_ATTACHMENT_BYTES) {
        throw new Error("Łączny rozmiar załączników w paczce jest zbyt duży.");
      }
    }
  }
  return inlineAttachmentBytes;
}

function collectEmployeeSeedAttachmentPaths(policies) {
  const paths = [];
  const seenPaths = new Set();
  for (const policy of policies) {
    for (const attachment of Array.isArray(policy?.attachments) ? policy.attachments : []) {
      if (attachment?.storage !== "file") continue;
      const safePath = sanitizeAttachmentRelativePath(attachment.relativePath);
      if (!safePath) throw new Error("Jedna z wybranych polis ma nieprawidłową ścieżkę załącznika.");
      const pathKey = packagePathKey(safePath.join("\\"));
      if (seenPaths.has(pathKey)) continue;
      seenPaths.add(pathKey);
      paths.push(safePath);
    }
  }
  return paths;
}

async function stageEmployeeSeedAttachments({
  rootDirectory,
  applicationDirectory,
  policies,
  inlineAttachmentBytes = 0,
}) {
  const attachmentPaths = collectEmployeeSeedAttachmentPaths(policies);
  if (attachmentPaths.length > MAX_EMPLOYEE_SEED_ATTACHMENTS) {
    throw new Error(`Paczka może zawierać maksymalnie ${MAX_EMPLOYEE_SEED_ATTACHMENTS} załączników.`);
  }
  let canonicalAttachmentRoot = "";
  if (attachmentPaths.length > 0) {
    try {
      canonicalAttachmentRoot = await realpath(join(rootDirectory, "Zalaczniki"));
    } catch {
      throw new Error("Nie znaleziono folderu Zalaczniki dla wybranych polis.");
    }
  }
  const preparedAttachments = [];
  let totalAttachmentBytes = inlineAttachmentBytes;
  for (const safePath of attachmentPaths) {
    const sourcePath = join(rootDirectory, ...safePath);
    const targetPath = join(applicationDirectory, ...safePath);
    try {
      
      
      const canonicalSourcePath = await realpath(sourcePath);
      const relativeToAttachmentRoot = relative(canonicalAttachmentRoot, canonicalSourcePath);
      if (
        !relativeToAttachmentRoot
        || isAbsolute(relativeToAttachmentRoot)
        || relativeToAttachmentRoot === ".."
        || relativeToAttachmentRoot.startsWith(`..\\`)
        || relativeToAttachmentRoot.startsWith("../")
      ) {
        throw new Error("unsafe-attachment-path");
      }
      const sourceStats = await stat(canonicalSourcePath);
      if (!sourceStats.isFile() || sourceStats.size > MAX_EMPLOYEE_SEED_ATTACHMENT_BYTES) {
        throw new Error("unsafe-attachment-file");
      }
      totalAttachmentBytes += sourceStats.size;
      if (totalAttachmentBytes > MAX_EMPLOYEE_SEED_TOTAL_ATTACHMENT_BYTES) {
        throw new Error("attachment-quota-exceeded");
      }
      preparedAttachments.push({ canonicalSourcePath, targetPath });
    } catch {
      throw new Error("Nie udało się dołączyć wszystkich wybranych załączników. Sprawdź folder Zalaczniki.");
    }
  }
  for (const { canonicalSourcePath, targetPath } of preparedAttachments) {
    try {
      await mkdir(dirname(targetPath), { recursive: true });
      await copyFile(canonicalSourcePath, targetPath);
    } catch {
      throw new Error("Nie udało się dołączyć wszystkich wybranych załączników. Sprawdź folder Zalaczniki.");
    }
  }
  return attachmentPaths.length;
}



export async function buildEmployeePackage({
  rootDirectory,
  filesDirectory,
  employee,
  servedInsurers = null,
  allowInsurerImports = false,
  nodeExecutablePath = "",
  databaseContents = "",
}) {
  
  
  const stagingRoot = await mkdtemp(join(tmpdir(), "sekagent-paczka-pracownika-"));
  try {
    const applicationDirectory = join(stagingRoot, "Sekagent");
    await mkdir(applicationDirectory, { recursive: true });

    
    await cp(filesDirectory, join(applicationDirectory, "pliki"), {
      recursive: true,
      filter: (sourcePath) => {
        const relativePath = sourcePath.slice(filesDirectory.length).replace(/^[\\/]+/, "");
        if (!relativePath) return true;
        const topSegment = relativePath.split(/[\\/]/)[0];
        return !EXCLUDED_APP_DIRECTORIES.has(topSegment);
      },
    });

    
    for (const starterName of ["Uruchom Baza Polis w tle.vbs", "Uruchom Baza Polis.cmd"]) {
      const sourcePath = join(rootDirectory, starterName);
      if (await pathExists(sourcePath)) {
        await copyFile(sourcePath, join(applicationDirectory, starterName));
      }
    }

    
    if (nodeExecutablePath && await pathExists(nodeExecutablePath)) {
      await mkdir(join(applicationDirectory, "pliki", "node"), { recursive: true });
      await copyFile(nodeExecutablePath, join(applicationDirectory, "pliki", "node", "node.exe"));
    }

    
    await writeFile(join(applicationDirectory, "polisy-konto.json"), `${JSON.stringify({
      version: 2,
      account: {
        id: employee.id,
        name: employee.label,
        mode: "employee",
        pin: null,
        createdAt: new Date().toISOString(),
        pinChangedAt: "",
        servedInsurers: Array.isArray(servedInsurers) ? servedInsurers : null,
        
        
        allowInsurerImports: allowInsurerImports === true,
        ...(employee.centralAccountId && employee.transferKey && employee.keyId
          ? {
            centralAccountId: employee.centralAccountId,
            transferKey: employee.transferKey,
            keyId: employee.keyId,
          }
          : {}),
      },
      members: [],
    }, null, 2)}\n`, "utf8");

    
    
    const employeeDatabase = prepareEmployeeSeedDatabase(databaseContents, employee);
    await stageEmployeeSeedAttachments({
      rootDirectory,
      applicationDirectory,
      policies: employeeDatabase.policies,
      inlineAttachmentBytes: validateEmployeeSeedAttachments(employeeDatabase.policies),
    });
    await writeFile(
      join(applicationDirectory, "polisy-dane.json"),
      `${JSON.stringify(employeeDatabase, null, 2)}\n`,
      "utf8",
    );

    
    
    try {
      const settingsRaw = await readFile(join(rootDirectory, "polisy-ustawienia.json"), "utf8");
      const settings = JSON.parse(settingsRaw);
      if (settings && typeof settings === "object" && !Array.isArray(settings)) {
        settings.calculatorAccounts = {};
        delete settings.idleRpg;
        settings.updatedAt = new Date().toISOString();
        await writeFile(
          join(applicationDirectory, "polisy-ustawienia.json"),
          `${JSON.stringify(settings, null, 2)}\n`,
          "utf8",
        );
      }
    } catch {
      
    }

    await writeFile(join(applicationDirectory, "Przeczytaj mnie.txt"), EMPLOYEE_README, "utf8");

    const zipPath = join(stagingRoot, "paczka.zip");
    await compressPathsToZip([applicationDirectory], zipPath);
    return {
      zipPath,
      cleanup: () => rm(stagingRoot, { recursive: true, force: true }).catch(() => {}),
    };
  } catch (error) {
    await rm(stagingRoot, { recursive: true, force: true }).catch(() => {});
    throw error;
  }
}




export function sanitizeAttachmentRelativePath(relativePath) {
  if (
    !Array.isArray(relativePath)
    || relativePath.length < 2
    || relativePath.some((segment) => (
      typeof segment !== "string" || segment.includes("/")
    ))
  ) {
    return null;
  }
  const rawPath = relativePath.join("/");
  try {
    const validatedPath = normalizeManifestAttachmentPath(rawPath, "");
    if (validatedPath !== rawPath) return null;
    return validatedPath.split("/");
  } catch {
    return null;
  }
}

export const AGENCY_PACKAGE_MANIFEST_FORMAT = "sekagent-agency-package";
export const AGENCY_PACKAGE_MANIFEST_VERSION = 1;
export const CENTRAL_PACKAGE_KIND = "employee-to-central";
export const EMPLOYEE_ASSIGNMENT_PACKAGE_MANIFEST_VERSION = 2;
export const EMPLOYEE_ASSIGNMENT_PACKAGE_KIND = "central-to-employee";
export const CENTRAL_PACKAGE_DATABASE_PATH = "polisy-dane.json";
export const CENTRAL_PACKAGE_MANIFEST_PATH = "paczka-manifest.json";
export const PACKAGE_MAX_UPLOAD_BYTES = 1024 * 1024 * 1024;
export const PACKAGE_MAX_ZIP_ENTRIES = 65_000;
export const PACKAGE_MAX_REGULAR_FILES = 20_002;
export const PACKAGE_MAX_ATTACHMENT_BYTES = 25 * 1024 * 1024;
export const PACKAGE_MAX_DATABASE_BYTES = 128 * 1024 * 1024;
export const PACKAGE_MAX_MANIFEST_BYTES = 16 * 1024 * 1024;
export const PACKAGE_MAX_EXTERNAL_BYTES = 2 * 1024 * 1024 * 1024;
export const PACKAGE_MAX_EXPANDED_BYTES = (
  PACKAGE_MAX_EXTERNAL_BYTES
  + PACKAGE_MAX_DATABASE_BYTES
  + PACKAGE_MAX_MANIFEST_BYTES
);
export const PACKAGE_EXTRACTION_DISK_RESERVE_BYTES = 256 * 1024 * 1024;
export const PACKAGE_MAX_SEGMENT_LENGTH = 120;
export const PACKAGE_MAX_RELATIVE_PATH_LENGTH = 180;
export const PACKAGE_MAX_TARGET_PATH_LENGTH = 240;
export const PACKAGE_MAX_LOGICAL_DEPTH = 4;

const PACKAGE_METADATA_STDOUT_LIMIT = 32 * 1024 * 1024;
const PACKAGE_METADATA_STDERR_LIMIT = 16 * 1024;
const PACKAGE_METADATA_TIMEOUT_MS = 60_000;
const PACKAGE_SHA256_PATTERN = /^[a-f0-9]{64}$/;
const PACKAGE_TRANSFER_KEY_PATTERN = /^[A-Za-z0-9_-]{43}$/;
const PACKAGE_TRANSFER_KEY_ID_PATTERN = /^[a-f0-9]{32}$/;
const PACKAGE_RESERVED_WINDOWS_NAME_PATTERN = /^(?:CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(?:\.|$)/i;
const PACKAGE_INVALID_WINDOWS_SEGMENT_PATTERN = /[<>:"\\|?*\u0000-\u001f]/;

const READ_ZIP_METADATA_SCRIPT = String.raw`
$ErrorActionPreference = "Stop"
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)
Add-Type -AssemblyName System.IO.Compression.FileSystem
$archive = $null
try {
  $archive = [System.IO.Compression.ZipFile]::OpenRead($env:BP_ZIP)
  if ($archive.Entries.Count -gt 65000) { exit 65 }
  foreach ($entry in $archive.Entries) {
    $record = [ordered]@{
      path = [string]$entry.FullName
      size = [int64]$entry.Length
      compressedSize = [int64]$entry.CompressedLength
      externalAttributes = [int64]$entry.ExternalAttributes
      directory = (([string]$entry.FullName).EndsWith("/") -or ([string]$entry.FullName).EndsWith([string][char]92))
    }
    [Console]::Out.WriteLine(($record | ConvertTo-Json -Compress))
  }
} catch {
  exit 66
} finally {
  if ($null -ne $archive) { $archive.Dispose() }
}
`;

const CHECK_REPARSE_POINTS_SCRIPT = String.raw`
$ErrorActionPreference = "Stop"
try {
  $root = [System.IO.DirectoryInfo]::new($env:BP_DEST)
  if (($root.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0) { exit 67 }
  $pending = [System.Collections.Generic.Stack[System.IO.DirectoryInfo]]::new()
  $pending.Push($root)
  while ($pending.Count -gt 0) {
    $directory = $pending.Pop()
    foreach ($entry in $directory.EnumerateFileSystemInfos()) {
      if (($entry.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0) { exit 67 }
      if (($entry.Attributes -band [System.IO.FileAttributes]::Directory) -ne 0) {
        $pending.Push([System.IO.DirectoryInfo]$entry)
      }
    }
  }
} catch {
  exit 68
}
`;

const PACKAGE_ERROR_MESSAGES = Object.freeze({
  PACKAGE_ARCHIVE_INVALID: "Nie udało się bezpiecznie odczytać paczki ZIP.",
  PACKAGE_ENTRY_LIMIT: "Paczka zawiera zbyt wiele elementów.",
  PACKAGE_FILE_LIMIT: "Paczka zawiera zbyt wiele plików.",
  PACKAGE_PATH_INVALID: "Paczka zawiera nieprawidłową ścieżkę pliku.",
  PACKAGE_PATH_COLLISION: "Paczka zawiera kolidujące ścieżki plików.",
  PACKAGE_LAYOUT_INVALID: "Paczka ma nieprawidłowy układ plików.",
  PACKAGE_ENTRY_TYPE: "Paczka zawiera niedozwolony typ pliku.",
  PACKAGE_SIZE_LIMIT: "Paczka przekracza bezpieczny limit rozmiaru.",
  PACKAGE_DISK_SPACE: "Brak miejsca na bezpieczne rozpakowanie paczki.",
  PACKAGE_MANIFEST_INVALID: "Manifest paczki jest nieprawidłowy.",
  PACKAGE_DATABASE_INVALID: "Baza polis w paczce jest nieprawidłowa.",
  PACKAGE_HASH_MISMATCH: "Nie udało się potwierdzić integralności paczki.",
  PACKAGE_ACCOUNT_MISMATCH: "Paczka nie należy do wybranego pracownika.",
  PACKAGE_ATTACHMENT_MISMATCH: "Załączniki paczki nie odpowiadają rekordom bazy.",
  PACKAGE_AUTHENTICATION: "Nie udało się potwierdzić pochodzenia paczki.",
  PACKAGE_PAIRING_REQUIRED: "Najpierw bezpiecznie sparuj konto pracownika z centralą.",
});

export class PackageValidationError extends Error {
  constructor(code, { statusCode = 400 } = {}) {
    super(PACKAGE_ERROR_MESSAGES[code] || "Paczka jest nieprawidłowa.");
    this.name = "PackageValidationError";
    this.code = /^PACKAGE_[A-Z0-9_]+$/.test(String(code || ""))
      ? code
      : "PACKAGE_ARCHIVE_INVALID";
    this.statusCode = statusCode;
  }
}

function throwPackageValidationError(code, options) {
  throw new PackageValidationError(code, options);
}

function packagePathKey(value) {
  return String(value ?? "").normalize("NFC").toUpperCase();
}

function hasCaseAliasedPackagePathPrefixes(entries) {
  const prefixesByKey = new Map();
  for (const entry of entries) {
    for (let prefixLength = 1; prefixLength <= entry.segments.length; prefixLength += 1) {
      const exactPrefix = entry.segments.slice(0, prefixLength).join("/");
      const prefixKey = packagePathKey(exactPrefix);
      const existingPrefix = prefixesByKey.get(prefixKey);
      if (existingPrefix && existingPrefix !== exactPrefix) return true;
      prefixesByKey.set(prefixKey, exactPrefix);
    }
  }
  return false;
}

function calculateSha256(contents) {
  return createHash("sha256").update(contents).digest("hex");
}

export function isSuccessfulPackageProcessExitCode(code) {
  return code === 0;
}

function prepareCentralPackageDatabase(databaseContents) {
  if (typeof databaseContents !== "string") {
    throw new Error("Przesłane dane nie są poprawnym plikiem bazy polis.");
  }
  const databaseSize = Buffer.byteLength(databaseContents, "utf8");
  if (databaseSize > MAX_EMPLOYEE_SEED_DATABASE_BYTES) {
    throw new Error("Baza polis przekracza limit 128 MB.");
  }
  let database;
  try {
    database = JSON.parse(databaseContents.replace(/^\uFEFF/, ""));
  } catch {
    throw new Error("Przesłane dane nie są poprawnym plikiem JSON.");
  }
  if (!database || typeof database !== "object" || Array.isArray(database) || !Array.isArray(database.policies)) {
    throw new Error("Przesłane dane nie wyglądają na bazę polis.");
  }
  const dataVersion = Number(database.version);
  if (!Number.isInteger(dataVersion) || dataVersion < 1 || dataVersion > 6) {
    throw new Error("Baza polis ma brakującą lub nieobsługiwaną wersję.");
  }
  if (database.policies.length > 10_000) {
    throw new Error("Paczka dla centrali może zawierać maksymalnie 10 000 polis.");
  }
  const sourceAccountId = normalizePackageAccountId(database.accountId);
  if (!sourceAccountId) {
    throw new Error("Baza polis nie ma prawidłowego identyfikatora konta źródłowego.");
  }
  return {
    database,
    databaseSize,
    dataVersion,
    sourceAccountId,
  };
}

function collectCentralPackageAttachmentPaths(policies, {
  rootDirectory,
  stagingRoot,
}) {
  const pathsByCaseInsensitiveKey = new Map();
  let attachmentRecordCount = 0;
  for (const policy of policies) {
    if (!policy || typeof policy !== "object" || Array.isArray(policy)) {
      throw new Error("Paczka zawiera nieprawidłowy rekord polisy.");
    }
    if (!normalizePackageAccountId(policy.ownerId)) {
      throw new Error("Każda eksportowana polisa musi mieć prawidłowy identyfikator właściciela.");
    }
    if (policy.attachments !== undefined && !Array.isArray(policy.attachments)) {
      throw new Error("Polisa zawiera nieprawidłową listę załączników.");
    }
    for (const attachment of Array.isArray(policy.attachments) ? policy.attachments : []) {
      attachmentRecordCount += 1;
      if (attachmentRecordCount > MAX_EMPLOYEE_SEED_ATTACHMENTS) {
        throw new Error(`Paczka może zawierać maksymalnie ${MAX_EMPLOYEE_SEED_ATTACHMENTS} rekordów załączników.`);
      }
      if (!attachment || typeof attachment !== "object" || Array.isArray(attachment)) {
        throw new Error("Polisa zawiera nieprawidłowy rekord załącznika.");
      }
      if (attachment.storage !== "file") continue;
      if (
        !Array.isArray(attachment.relativePath)
        || attachment.relativePath.some((segment) => (
          typeof segment !== "string" || segment.includes("/")
        ))
      ) {
        throw new Error("Jedna z eksportowanych polis ma nieprawidłową ścieżkę załącznika.");
      }
      const rawPath = attachment.relativePath.join("/");
      let exactPath;
      try {
        exactPath = normalizeManifestAttachmentPath(rawPath, stagingRoot);
        normalizeManifestAttachmentPath(rawPath, rootDirectory);
      } catch {
        throw new Error("Jedna z eksportowanych polis ma nieprawidłową ścieżkę załącznika.");
      }
      if (exactPath !== rawPath) {
        throw new Error("Jedna z eksportowanych polis ma nieprawidłową ścieżkę załącznika.");
      }
      const safeSegments = exactPath.split("/");
      const pathKey = packagePathKey(exactPath);
      const existingPath = pathsByCaseInsensitiveKey.get(pathKey);
      if (existingPath && existingPath.exactPath !== exactPath) {
        throw new Error("Paczka zawiera dwie różne ścieżki załączników, które kolidują wielkością liter.");
      }
      if (!existingPath) pathsByCaseInsensitiveKey.set(pathKey, { exactPath, safeSegments });
    }
  }
  const uniquePaths = [...pathsByCaseInsensitiveKey.values()];
  if (hasCaseAliasedPackagePathPrefixes(uniquePaths.map(({ exactPath, safeSegments }) => ({
    path: exactPath,
    segments: safeSegments,
  })))) {
    throw new Error("Paczka zawiera ścieżki załączników, których foldery kolidują wielkością liter.");
  }
  return uniquePaths.sort((left, right) => (
    left.exactPath < right.exactPath ? -1 : (left.exactPath > right.exactPath ? 1 : 0)
  ));
}

async function assertCentralPackagePathHasNoLinks(rootDirectory, safeSegments) {
  let currentPath = rootDirectory;
  for (const segment of safeSegments) {
    currentPath = join(currentPath, segment);
    const pathStats = await lstat(currentPath);
    if (pathStats.isSymbolicLink()) {
      throw new Error("Paczka nie może zawierać załącznika dostępnego przez dowiązanie lub junction.");
    }
  }
}

async function stageCentralPackageAttachments({
  rootDirectory,
  stagingRoot,
  attachmentPaths,
}) {
  if (attachmentPaths.length === 0) return [];
  const attachmentRoot = join(rootDirectory, "Zalaczniki");
  let canonicalAttachmentRoot;
  try {
    const rootStats = await lstat(attachmentRoot);
    if (rootStats.isSymbolicLink()) throw new Error("unsafe-attachment-root");
    canonicalAttachmentRoot = await realpath(attachmentRoot);
  } catch {
    throw new Error("Nie znaleziono bezpiecznego folderu Zalaczniki dla eksportowanych polis.");
  }

  const manifestAttachments = [];
  let totalAttachmentBytes = 0;
  for (const { exactPath, safeSegments } of attachmentPaths) {
    try {
      await assertCentralPackagePathHasNoLinks(rootDirectory, safeSegments);
      const sourcePath = join(rootDirectory, ...safeSegments);
      const canonicalSourcePath = await realpath(sourcePath);
      const relativeToAttachmentRoot = relative(canonicalAttachmentRoot, canonicalSourcePath);
      if (
        !relativeToAttachmentRoot
        || isAbsolute(relativeToAttachmentRoot)
        || relativeToAttachmentRoot === ".."
        || relativeToAttachmentRoot.startsWith(`..\\`)
        || relativeToAttachmentRoot.startsWith("../")
      ) {
        throw new Error("unsafe-attachment-path");
      }
      const sourceStats = await stat(canonicalSourcePath);
      if (!sourceStats.isFile() || sourceStats.size > MAX_EMPLOYEE_SEED_ATTACHMENT_BYTES) {
        throw new Error("unsafe-attachment-file");
      }
      totalAttachmentBytes += sourceStats.size;
      if (totalAttachmentBytes > MAX_EMPLOYEE_SEED_TOTAL_ATTACHMENT_BYTES) {
        throw new Error("attachment-quota-exceeded");
      }

      const targetPath = join(stagingRoot, ...safeSegments);
      await mkdir(dirname(targetPath), { recursive: true });
      await copyFile(canonicalSourcePath, targetPath);
      const stagedStats = await stat(targetPath);
      if (!stagedStats.isFile() || stagedStats.size !== sourceStats.size) {
        throw new Error("attachment-changed-during-copy");
      }
      manifestAttachments.push({
        path: exactPath,
        size: stagedStats.size,
        sha256: calculateSha256(await readFile(targetPath)),
      });
    } catch {
      throw new Error("Nie udało się bezpiecznie dołączyć wszystkich załączników eksportowanych polis.");
    }
  }
  return manifestAttachments;
}



export async function buildCentralPackage({
  rootDirectory,
  filesDirectory,
  databaseContents,
}) {
  const stagingRoot = await mkdtemp(join(tmpdir(), "sekagent-paczka-centrali-"));
  try {
    const {
      database,
      databaseSize,
      dataVersion,
      sourceAccountId,
    } = prepareCentralPackageDatabase(databaseContents);
    const attachmentPaths = collectCentralPackageAttachmentPaths(database.policies, {
      rootDirectory,
      stagingRoot,
    });
    const databasePath = join(stagingRoot, CENTRAL_PACKAGE_DATABASE_PATH);
    await writeFile(databasePath, databaseContents, "utf8");
    const manifestAttachments = await stageCentralPackageAttachments({
      rootDirectory,
      stagingRoot,
      attachmentPaths,
    });
    const manifest = {
      format: AGENCY_PACKAGE_MANIFEST_FORMAT,
      version: AGENCY_PACKAGE_MANIFEST_VERSION,
      kind: CENTRAL_PACKAGE_KIND,
      sourceAccountId,
      createdAt: new Date().toISOString(),
      database: {
        path: CENTRAL_PACKAGE_DATABASE_PATH,
        size: databaseSize,
        sha256: calculateSha256(Buffer.from(databaseContents, "utf8")),
        dataVersion,
        policyCount: database.policies.length,
      },
      attachments: manifestAttachments,
    };
    const manifestPath = join(stagingRoot, CENTRAL_PACKAGE_MANIFEST_PATH);
    await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
    const sources = [databasePath, manifestPath];
    if (manifestAttachments.length > 0) sources.push(join(stagingRoot, "Zalaczniki"));
    const zipPath = join(stagingRoot, "paczka.zip");
    await compressPathsToZip(sources, zipPath);
    const zipStats = await stat(zipPath);
    if (!zipStats.isFile() || zipStats.size > PACKAGE_MAX_UPLOAD_BYTES) {
      throw new Error("Paczka ZIP przekracza bezpieczny limit 1 GB.");
    }
    return {
      zipPath,
      policyCount: database.policies.length,
      cleanup: () => rm(stagingRoot, { recursive: true, force: true }).catch(() => {}),
    };
  } catch (error) {
    await rm(stagingRoot, { recursive: true, force: true }).catch(() => {});
    throw error;
  }
}

function decodePackageTransferKey(value) {
  const transferKey = String(value || "");
  if (!PACKAGE_TRANSFER_KEY_PATTERN.test(transferKey)) return null;
  try {
    const bytes = Buffer.from(transferKey, "base64url");
    return bytes.length === 32 && bytes.toString("base64url") === transferKey
      ? bytes
      : null;
  } catch {
    return null;
  }
}

function packageTransferKeyId(transferKey) {
  const keyBytes = decodePackageTransferKey(transferKey);
  return keyBytes
    ? createHash("sha256").update(keyBytes).digest("hex").slice(0, 32)
    : "";
}

function canonicalEmployeeAssignmentAuthenticationValue(manifest) {
  return {
    format: manifest?.format,
    version: manifest?.version,
    kind: manifest?.kind,
    sourceAccountId: manifest?.sourceAccountId,
    targetAccountId: manifest?.targetAccountId,
    createdAt: manifest?.createdAt,
    database: manifest?.database,
    attachments: manifest?.attachments,
    authentication: {
      algorithm: manifest?.authentication?.algorithm,
      keyId: manifest?.authentication?.keyId,
    },
  };
}

export function calculateEmployeeAssignmentHmac(manifest, transferKey) {
  const keyBytes = decodePackageTransferKey(transferKey);
  if (!keyBytes) {
    throw new PackageValidationError("PACKAGE_PAIRING_REQUIRED", { statusCode: 403 });
  }
  return createHmac("sha256", keyBytes)
    .update(JSON.stringify(canonicalEmployeeAssignmentAuthenticationValue(manifest)), "utf8")
    .digest("hex");
}

function prepareEmployeeAssignmentDatabase({
  databaseContents,
  sourceAccountId,
  targetAccountId,
}) {
  if (typeof databaseContents !== "string") {
    throw new Error("Przesłane dane nie są poprawnym plikiem bazy polis.");
  }
  if (Buffer.byteLength(databaseContents, "utf8") > PACKAGE_MAX_DATABASE_BYTES) {
    throw new Error("Wybrane dane polis przekraczają limit 128 MB.");
  }
  let sourceDatabase;
  try {
    sourceDatabase = JSON.parse(databaseContents.replace(/^\uFEFF/, ""));
  } catch {
    throw new Error("Przesłane dane nie są poprawnym plikiem JSON.");
  }
  if (
    !sourceDatabase
    || typeof sourceDatabase !== "object"
    || Array.isArray(sourceDatabase)
    || !Array.isArray(sourceDatabase.policies)
  ) {
    throw new Error("Przesłane dane nie wyglądają na bazę polis.");
  }
  const sourceVersion = Number(sourceDatabase.version);
  if (!Number.isInteger(sourceVersion) || sourceVersion < 1 || sourceVersion > 6) {
    throw new Error("Baza polis ma brakującą lub nieobsługiwaną wersję.");
  }
  if (sourceDatabase.policies.length < 1 || sourceDatabase.policies.length > 10_000) {
    throw new Error("Wybierz od 1 do 10 000 polis dla pracownika.");
  }
  if (normalizePackageAccountId(sourceDatabase.accountId) !== sourceAccountId) {
    throw new PackageValidationError("PACKAGE_ACCOUNT_MISMATCH", { statusCode: 403 });
  }
  const policies = sourceDatabase.policies.map((policy) => {
    if (!policy || typeof policy !== "object" || Array.isArray(policy)) {
      throw new Error("Paczka zawiera nieprawidłowy rekord polisy.");
    }
    if (!normalizePackageAccountId(policy.ownerId)) {
      throw new Error("Każda przekazywana polisa musi mieć zapisane ownerId.");
    }
    return policy;
  });
  validateEmployeeSeedAttachments(policies);
  return {
    version: 6,
    accountId: targetAccountId,
    centralAccountId: sourceAccountId,
    policies,
    deletedPolicies: [],
    calendarTasks: [],
  };
}




export async function buildEmployeeAssignmentPackage({
  rootDirectory,
  databaseContents,
  sourceAccountId,
  targetAccountId,
  transferKey,
  keyId,
}) {
  const normalizedSourceAccountId = normalizePackageAccountId(sourceAccountId);
  const normalizedTargetAccountId = normalizePackageAccountId(targetAccountId);
  const normalizedKeyId = String(keyId || "");
  if (
    !normalizedSourceAccountId
    || normalizedSourceAccountId !== sourceAccountId
    || !normalizedTargetAccountId
    || normalizedTargetAccountId !== targetAccountId
  ) {
    throw new PackageValidationError("PACKAGE_ACCOUNT_MISMATCH", { statusCode: 403 });
  }
  if (
    !PACKAGE_TRANSFER_KEY_ID_PATTERN.test(normalizedKeyId)
    || normalizedKeyId !== packageTransferKeyId(transferKey)
  ) {
    throw new PackageValidationError("PACKAGE_PAIRING_REQUIRED", { statusCode: 403 });
  }

  const stagingRoot = await mkdtemp(join(tmpdir(), "sekagent-przydzial-polis-"));
  try {
    const database = prepareEmployeeAssignmentDatabase({
      databaseContents,
      sourceAccountId: normalizedSourceAccountId,
      targetAccountId: normalizedTargetAccountId,
    });
    const attachmentPaths = collectCentralPackageAttachmentPaths(database.policies, {
      rootDirectory,
      stagingRoot,
    });
    const manifestAttachments = await stageCentralPackageAttachments({
      rootDirectory,
      stagingRoot,
      attachmentPaths,
    });
    const serializedDatabase = `${JSON.stringify(database, null, 2)}\n`;
    const databaseSize = Buffer.byteLength(serializedDatabase, "utf8");
    if (databaseSize > PACKAGE_MAX_DATABASE_BYTES) {
      throw new Error("Wybrane dane polis przekraczają limit 128 MB.");
    }
    const databasePath = join(stagingRoot, CENTRAL_PACKAGE_DATABASE_PATH);
    await writeFile(databasePath, serializedDatabase, "utf8");
    const manifest = {
      format: AGENCY_PACKAGE_MANIFEST_FORMAT,
      version: EMPLOYEE_ASSIGNMENT_PACKAGE_MANIFEST_VERSION,
      kind: EMPLOYEE_ASSIGNMENT_PACKAGE_KIND,
      sourceAccountId: normalizedSourceAccountId,
      targetAccountId: normalizedTargetAccountId,
      createdAt: new Date().toISOString(),
      database: {
        path: CENTRAL_PACKAGE_DATABASE_PATH,
        size: databaseSize,
        sha256: calculateSha256(Buffer.from(serializedDatabase, "utf8")),
        dataVersion: database.version,
        policyCount: database.policies.length,
      },
      attachments: manifestAttachments,
      authentication: {
        algorithm: "hmac-sha256",
        keyId: normalizedKeyId,
        sha256: "",
      },
    };
    manifest.authentication.sha256 = calculateEmployeeAssignmentHmac(manifest, transferKey);
    const manifestPath = join(stagingRoot, CENTRAL_PACKAGE_MANIFEST_PATH);
    await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
    const sources = [databasePath, manifestPath];
    if (manifestAttachments.length > 0) sources.push(join(stagingRoot, "Zalaczniki"));
    const zipPath = join(stagingRoot, "paczka.zip");
    await compressPathsToZip(sources, zipPath);
    const zipStats = await stat(zipPath);
    if (!zipStats.isFile() || zipStats.size > PACKAGE_MAX_UPLOAD_BYTES) {
      throw new Error("Paczka ZIP przekracza bezpieczny limit 1 GB.");
    }
    return {
      zipPath,
      policyCount: database.policies.length,
      sourceAccountId: normalizedSourceAccountId,
      targetAccountId: normalizedTargetAccountId,
      cleanup: () => rm(stagingRoot, { recursive: true, force: true }).catch(() => {}),
    };
  } catch (error) {
    await rm(stagingRoot, { recursive: true, force: true }).catch(() => {});
    throw error;
  }
}

function runPackagePowerShellScript(script, environmentVariables, {
  timeoutMs = PACKAGE_METADATA_TIMEOUT_MS,
  stdoutLimit = PACKAGE_METADATA_STDOUT_LIMIT,
  stderrLimit = PACKAGE_METADATA_STDERR_LIMIT,
} = {}) {
  return new Promise((resolveScript, rejectScript) => {
    const child = spawn("powershell.exe", [
      "-NoProfile",
      "-NonInteractive",
      "-ExecutionPolicy",
      "Bypass",
      "-Command",
      script,
    ], {
      env: { ...process.env, ...environmentVariables },
      stdio: ["ignore", "pipe", "pipe"],
      windowsHide: true,
    });
    const stdoutChunks = [];
    let stdoutBytes = 0;
    let stderrBytes = 0;
    let settled = false;
    const finishWithError = (error) => {
      if (settled) return;
      settled = true;
      child.kill();
      rejectScript(error);
    };
    child.stdout.on("data", (chunk) => {
      stdoutBytes += chunk.length;
      if (stdoutBytes > stdoutLimit) {
        finishWithError(new PackageValidationError("PACKAGE_ENTRY_LIMIT"));
        return;
      }
      stdoutChunks.push(chunk);
    });
    child.stderr.on("data", (chunk) => {
      stderrBytes += chunk.length;
      if (stderrBytes > stderrLimit) {
        finishWithError(new PackageValidationError("PACKAGE_ARCHIVE_INVALID"));
      }
    });
    const timeout = globalThis.setTimeout(() => {
      finishWithError(new PackageValidationError("PACKAGE_ARCHIVE_INVALID"));
    }, timeoutMs);
    child.on("error", () => {
      globalThis.clearTimeout(timeout);
      finishWithError(new PackageValidationError("PACKAGE_ARCHIVE_INVALID"));
    });
    child.on("exit", (code) => {
      globalThis.clearTimeout(timeout);
      if (settled) return;
      settled = true;
      resolveScript({
        code,
        stdout: Buffer.concat(stdoutChunks).toString("utf8"),
      });
    });
  });
}

async function readCentralPackageZipMetadata(zipPath) {
  const result = await runPackagePowerShellScript(
    READ_ZIP_METADATA_SCRIPT,
    { BP_ZIP: zipPath },
  );
  if (result.code === 65) throwPackageValidationError("PACKAGE_ENTRY_LIMIT");
  if (!isSuccessfulPackageProcessExitCode(result.code)) {
    throwPackageValidationError("PACKAGE_ARCHIVE_INVALID");
  }
  if (!result.stdout.trim()) throwPackageValidationError("PACKAGE_ARCHIVE_INVALID");
  const entries = [];
  try {
    for (const line of result.stdout.split(/\r?\n/)) {
      if (!line.trim()) continue;
      entries.push(JSON.parse(line));
      if (entries.length > PACKAGE_MAX_ZIP_ENTRIES) {
        throw new PackageValidationError("PACKAGE_ENTRY_LIMIT");
      }
    }
  } catch (error) {
    if (error instanceof PackageValidationError) throw error;
    throwPackageValidationError("PACKAGE_ARCHIVE_INVALID");
  }
  return entries;
}

function validatePackagePath(pathValue, {
  extractionDirectory,
  maximumDepth = PACKAGE_MAX_LOGICAL_DEPTH + 1,
  directory = false,
} = {}) {
  const rawPath = String(pathValue ?? "");
  const hasForwardSeparators = rawPath.includes("/");
  const hasBackwardSeparators = rawPath.includes("\\");
  if (
    !rawPath
    || rawPath !== rawPath.normalize("NFC")
    || (hasForwardSeparators && hasBackwardSeparators)
    || rawPath.startsWith("/")
    || rawPath.startsWith("\\")
  ) {
    throwPackageValidationError("PACKAGE_PATH_INVALID");
  }
  const normalizedSeparators = rawPath.replace(/\\/g, "/");
  if (normalizedSeparators.startsWith("//") || normalizedSeparators.includes("//")) {
    throwPackageValidationError("PACKAGE_PATH_INVALID");
  }
  const hasDirectorySuffix = normalizedSeparators.endsWith("/");
  if (hasDirectorySuffix !== directory) throwPackageValidationError("PACKAGE_PATH_INVALID");
  const pathWithoutSuffix = hasDirectorySuffix
    ? normalizedSeparators.slice(0, -1)
    : normalizedSeparators;
  if (
    !pathWithoutSuffix
    || pathWithoutSuffix.length > PACKAGE_MAX_RELATIVE_PATH_LENGTH
    || pathWithoutSuffix.endsWith("/")
  ) {
    throwPackageValidationError("PACKAGE_PATH_INVALID");
  }
  const segments = pathWithoutSuffix.split("/");
  if (segments.length > maximumDepth) throwPackageValidationError("PACKAGE_PATH_INVALID");
  for (const segment of segments) {
    if (
      !segment
      || segment === "."
      || segment === ".."
      || segment.length > PACKAGE_MAX_SEGMENT_LENGTH
      || segment.endsWith(".")
      || segment.endsWith(" ")
      || PACKAGE_INVALID_WINDOWS_SEGMENT_PATTERN.test(segment)
      || PACKAGE_RESERVED_WINDOWS_NAME_PATTERN.test(segment)
    ) {
      throwPackageValidationError("PACKAGE_PATH_INVALID");
    }
  }
  if (
    extractionDirectory
    && join(extractionDirectory, ...segments).length > PACKAGE_MAX_TARGET_PATH_LENGTH
  ) {
    throwPackageValidationError("PACKAGE_PATH_INVALID");
  }
  return {
    path: pathWithoutSuffix,
    segments,
    directory,
  };
}

function validatePackageEntryType(entry) {
  const attributes = Number(entry.externalAttributes);
  if (!Number.isInteger(attributes)) throwPackageValidationError("PACKAGE_ARCHIVE_INVALID");
  const unsignedAttributes = attributes >>> 0;
  const unixType = (unsignedAttributes >>> 16) & 0xf000;
  const windowsAttributes = unsignedAttributes & 0xffff;
  if (unixType === 0xa000 || (windowsAttributes & 0x0400) !== 0) {
    throwPackageValidationError("PACKAGE_ENTRY_TYPE");
  }
  if (
    unixType !== 0
    && unixType !== 0x8000
    && unixType !== 0x4000
  ) {
    throwPackageValidationError("PACKAGE_ENTRY_TYPE");
  }
  if (
    (entry.directory && unixType === 0x8000)
    || (!entry.directory && unixType === 0x4000)
  ) {
    throwPackageValidationError("PACKAGE_ENTRY_TYPE");
  }
}

function assertNoPackagePathCollisions(entries) {
  const entriesByKey = new Map();
  for (const entry of entries) {
    const key = packagePathKey(entry.path);
    if (entriesByKey.has(key)) throwPackageValidationError("PACKAGE_PATH_COLLISION");
    entriesByKey.set(key, entry);
  }
  if (hasCaseAliasedPackagePathPrefixes(entries)) {
    throwPackageValidationError("PACKAGE_PATH_COLLISION");
  }
  const orderedEntries = [...entries].sort((left, right) => (
    left.segments.length - right.segments.length
  ));
  const fileKeys = new Set();
  for (const entry of orderedEntries) {
    for (let index = 1; index < entry.segments.length; index += 1) {
      const ancestorKey = packagePathKey(entry.segments.slice(0, index).join("/"));
      if (fileKeys.has(ancestorKey)) throwPackageValidationError("PACKAGE_PATH_COLLISION");
    }
    if (!entry.directory) fileKeys.add(packagePathKey(entry.path));
  }
}

function determineCentralPackageLayout(entries) {
  const manifestCandidates = entries.filter((entry) => (
    !entry.directory && entry.segments.at(-1) === CENTRAL_PACKAGE_MANIFEST_PATH
  ));
  if (manifestCandidates.length !== 1) throwPackageValidationError("PACKAGE_LAYOUT_INVALID");
  const manifestSegments = manifestCandidates[0].segments;
  if (manifestSegments.length < 1 || manifestSegments.length > 2) {
    throwPackageValidationError("PACKAGE_LAYOUT_INVALID");
  }
  const wrapperName = manifestSegments.length === 2 ? manifestSegments[0] : "";
  const logicalEntries = [];
  for (const entry of entries) {
    let logicalSegments = entry.segments;
    if (wrapperName) {
      if (logicalSegments.length === 1 && logicalSegments[0] === wrapperName && entry.directory) continue;
      if (logicalSegments[0] !== wrapperName) throwPackageValidationError("PACKAGE_LAYOUT_INVALID");
      logicalSegments = logicalSegments.slice(1);
    }
    if (logicalSegments.length === 0 || logicalSegments.length > PACKAGE_MAX_LOGICAL_DEPTH) {
      throwPackageValidationError("PACKAGE_LAYOUT_INVALID");
    }
    const logicalPath = logicalSegments.join("/");
    if (entry.directory) {
      if (
        logicalSegments[0] !== "Zalaczniki"
        || (logicalPath !== "Zalaczniki" && !logicalPath.startsWith("Zalaczniki/"))
      ) {
        throwPackageValidationError("PACKAGE_LAYOUT_INVALID");
      }
    } else if (
      logicalPath !== CENTRAL_PACKAGE_MANIFEST_PATH
      && logicalPath !== CENTRAL_PACKAGE_DATABASE_PATH
      && !(logicalSegments[0] === "Zalaczniki" && logicalSegments.length >= 2)
    ) {
      throwPackageValidationError("PACKAGE_LAYOUT_INVALID");
    }
    logicalEntries.push({ ...entry, logicalPath, logicalSegments });
  }

  const regularFiles = logicalEntries.filter((entry) => !entry.directory);
  if (regularFiles.length > PACKAGE_MAX_REGULAR_FILES) {
    throwPackageValidationError("PACKAGE_FILE_LIMIT");
  }
  if (
    regularFiles.filter((entry) => entry.logicalPath === CENTRAL_PACKAGE_MANIFEST_PATH).length !== 1
    || regularFiles.filter((entry) => entry.logicalPath === CENTRAL_PACKAGE_DATABASE_PATH).length !== 1
  ) {
    throwPackageValidationError("PACKAGE_LAYOUT_INVALID");
  }
  const attachmentFiles = regularFiles.filter((entry) => entry.logicalSegments[0] === "Zalaczniki");
  const attachmentPaths = attachmentFiles.map((entry) => entry.logicalPath);
  for (const directoryEntry of logicalEntries.filter((entry) => entry.directory)) {
    if (!attachmentPaths.some((path) => path.startsWith(`${directoryEntry.logicalPath}/`))) {
      throwPackageValidationError("PACKAGE_LAYOUT_INVALID");
    }
  }
  return {
    wrapperName,
    logicalEntries,
    regularFiles,
    attachmentFiles,
  };
}

export function validateCentralPackageArchiveEntries(rawEntries, {
  extractionDirectory = "",
} = {}) {
  if (!Array.isArray(rawEntries) || rawEntries.length === 0) {
    throwPackageValidationError("PACKAGE_ARCHIVE_INVALID");
  }
  if (rawEntries.length > PACKAGE_MAX_ZIP_ENTRIES) {
    throwPackageValidationError("PACKAGE_ENTRY_LIMIT");
  }
  const rawRegularFileCount = rawEntries.reduce(
    (count, entry) => count + (entry?.directory === true ? 0 : 1),
    0,
  );
  if (rawRegularFileCount > PACKAGE_MAX_REGULAR_FILES) {
    throwPackageValidationError("PACKAGE_FILE_LIMIT");
  }

  const entries = rawEntries.map((rawEntry) => {
    const directory = rawEntry?.directory === true;
    const entry = validatePackagePath(rawEntry?.path, {
      extractionDirectory,
      maximumDepth: PACKAGE_MAX_LOGICAL_DEPTH + 1,
      directory,
    });
    const size = Number(rawEntry?.size);
    const compressedSize = Number(rawEntry?.compressedSize);
    if (
      !Number.isSafeInteger(size)
      || size < 0
      || !Number.isSafeInteger(compressedSize)
      || compressedSize < 0
      || (directory && size !== 0)
    ) {
      throwPackageValidationError("PACKAGE_ARCHIVE_INVALID");
    }
    const normalizedEntry = {
      ...entry,
      archivePath: entry.path,
      size,
      compressedSize,
      externalAttributes: Number(rawEntry?.externalAttributes),
    };
    validatePackageEntryType(normalizedEntry);
    return normalizedEntry;
  });
  assertNoPackagePathCollisions(entries);
  const layout = determineCentralPackageLayout(entries);

  let totalExpandedBytes = 0;
  let totalExternalBytes = 0;
  for (const entry of layout.regularFiles) {
    totalExpandedBytes += entry.size;
    if (!Number.isSafeInteger(totalExpandedBytes) || totalExpandedBytes > PACKAGE_MAX_EXPANDED_BYTES) {
      throwPackageValidationError("PACKAGE_SIZE_LIMIT");
    }
    if (
      entry.logicalPath === CENTRAL_PACKAGE_MANIFEST_PATH
      && entry.size > PACKAGE_MAX_MANIFEST_BYTES
    ) {
      throwPackageValidationError("PACKAGE_SIZE_LIMIT");
    }
    if (
      entry.logicalPath === CENTRAL_PACKAGE_DATABASE_PATH
      && entry.size > PACKAGE_MAX_DATABASE_BYTES
    ) {
      throwPackageValidationError("PACKAGE_SIZE_LIMIT");
    }
    if (entry.logicalSegments[0] === "Zalaczniki") {
      if (entry.size > PACKAGE_MAX_ATTACHMENT_BYTES) {
        throwPackageValidationError("PACKAGE_SIZE_LIMIT");
      }
      totalExternalBytes += entry.size;
      if (!Number.isSafeInteger(totalExternalBytes) || totalExternalBytes > PACKAGE_MAX_EXTERNAL_BYTES) {
        throwPackageValidationError("PACKAGE_SIZE_LIMIT");
      }
    }
  }
  return {
    ...layout,
    entries,
    totalExpandedBytes,
    totalExternalBytes,
  };
}

async function getAvailablePackageDiskBytes(extractionDirectory) {
  try {
    const filesystemStats = await statfs(dirname(extractionDirectory), { bigint: true });
    return (filesystemStats.bavail ?? filesystemStats.bfree) * filesystemStats.bsize;
  } catch {
    throwPackageValidationError("PACKAGE_DISK_SPACE", { statusCode: 507 });
  }
}

export async function preflightCentralPackageArchive({
  zipPath,
  extractionDirectory,
  availableDiskBytes = null,
}) {
  let zipSize;
  try {
    const zipStats = await stat(zipPath);
    if (!zipStats.isFile()) throw new Error("not-a-file");
    zipSize = zipStats.size;
  } catch {
    throwPackageValidationError("PACKAGE_ARCHIVE_INVALID");
  }
  if (zipSize > PACKAGE_MAX_UPLOAD_BYTES) {
    throwPackageValidationError("PACKAGE_SIZE_LIMIT");
  }
  const rawEntries = await readCentralPackageZipMetadata(zipPath);
  const validatedEntries = validateCentralPackageArchiveEntries(rawEntries, {
    extractionDirectory,
  });
  const availableBytes = availableDiskBytes === null
    ? await getAvailablePackageDiskBytes(extractionDirectory)
    : BigInt(availableDiskBytes);
  const requiredBytes = BigInt(zipSize)
    + BigInt(validatedEntries.totalExpandedBytes)
    + BigInt(PACKAGE_EXTRACTION_DISK_RESERVE_BYTES);
  if (availableBytes < requiredBytes) {
    throwPackageValidationError("PACKAGE_DISK_SPACE", { statusCode: 507 });
  }
  return {
    ...validatedEntries,
    zipSize,
    requiredDiskBytes: requiredBytes,
  };
}

async function assertExtractedPackageHasNoReparsePoints(extractionDirectory) {
  const result = await runPackagePowerShellScript(
    CHECK_REPARSE_POINTS_SCRIPT,
    { BP_DEST: extractionDirectory },
    {
      stdoutLimit: 1024,
      stderrLimit: 1024,
      timeoutMs: PACKAGE_METADATA_TIMEOUT_MS,
    },
  );
  if (result.code === 67) throwPackageValidationError("PACKAGE_ENTRY_TYPE");
  if (!isSuccessfulPackageProcessExitCode(result.code)) {
    throwPackageValidationError("PACKAGE_ARCHIVE_INVALID");
  }
}

function isPathInsideRoot(rootPath, candidatePath) {
  const relativePath = relative(rootPath, candidatePath);
  return Boolean(relativePath)
    && !isAbsolute(relativePath)
    && relativePath !== ".."
    && !relativePath.startsWith(`..\\`)
    && !relativePath.startsWith("../");
}

async function collectExtractedPackageEntries(extractionDirectory) {
  let canonicalRoot;
  try {
    const rootStats = await lstat(extractionDirectory);
    if (!rootStats.isDirectory() || rootStats.isSymbolicLink()) {
      throw new Error("invalid-extraction-root");
    }
    canonicalRoot = await realpath(extractionDirectory);
  } catch {
    throwPackageValidationError("PACKAGE_ARCHIVE_INVALID");
  }
  const entries = [];
  const visitDirectory = async (directoryPath) => {
    let children;
    try {
      children = await readdir(directoryPath, { withFileTypes: true });
    } catch {
      throwPackageValidationError("PACKAGE_ARCHIVE_INVALID");
    }
    for (const child of children) {
      const childPath = join(directoryPath, child.name);
      let childStats;
      let canonicalChildPath;
      try {
        childStats = await lstat(childPath);
        if (childStats.isSymbolicLink()) throw new Error("link");
        canonicalChildPath = await realpath(childPath);
      } catch {
        throwPackageValidationError("PACKAGE_ENTRY_TYPE");
      }
      if (!isPathInsideRoot(canonicalRoot, canonicalChildPath)) {
        throwPackageValidationError("PACKAGE_ENTRY_TYPE");
      }
      const archivePath = relative(extractionDirectory, childPath).split("\\").join("/");
      if (childStats.isDirectory()) {
        validatePackagePath(`${archivePath}/`, {
          extractionDirectory,
          maximumDepth: PACKAGE_MAX_LOGICAL_DEPTH + 1,
          directory: true,
        });
        entries.push({
          path: `${archivePath}/`,
          size: 0,
          compressedSize: 0,
          externalAttributes: 0,
          directory: true,
        });
        await visitDirectory(childPath);
      } else if (childStats.isFile()) {
        validatePackagePath(archivePath, {
          extractionDirectory,
          maximumDepth: PACKAGE_MAX_LOGICAL_DEPTH + 1,
          directory: false,
        });
        entries.push({
          path: archivePath,
          size: childStats.size,
          compressedSize: childStats.size,
          externalAttributes: 0,
          directory: false,
        });
      } else {
        throwPackageValidationError("PACKAGE_ENTRY_TYPE");
      }
      if (entries.length > PACKAGE_MAX_ZIP_ENTRIES) {
        throwPackageValidationError("PACKAGE_ENTRY_LIMIT");
      }
    }
  };
  await visitDirectory(extractionDirectory);
  return entries;
}

function assertExactObjectKeys(value, expectedKeys, errorCode) {
  if (!value || typeof value !== "object" || Array.isArray(value)) {
    throwPackageValidationError(errorCode);
  }
  const actualKeys = Object.keys(value).sort();
  const sortedExpectedKeys = [...expectedKeys].sort();
  if (
    actualKeys.length !== sortedExpectedKeys.length
    || actualKeys.some((key, index) => key !== sortedExpectedKeys[index])
  ) {
    throwPackageValidationError(errorCode);
  }
}

function normalizeManifestAttachmentPath(pathValue, packageRoot) {
  const validatedPath = validatePackagePath(pathValue, {
    extractionDirectory: packageRoot,
    maximumDepth: PACKAGE_MAX_LOGICAL_DEPTH,
    directory: false,
  });
  if (
    validatedPath.segments[0] !== "Zalaczniki"
    || validatedPath.segments.length < 2
  ) {
    throwPackageValidationError("PACKAGE_MANIFEST_INVALID");
  }
  return validatedPath.path;
}

function validateCentralPackageManifest(manifest, {
  expectedAccountId,
  packageRoot,
}) {
  assertExactObjectKeys(manifest, [
    "format",
    "version",
    "kind",
    "sourceAccountId",
    "createdAt",
    "database",
    "attachments",
  ], "PACKAGE_MANIFEST_INVALID");
  const normalizedExpectedAccountId = normalizePackageAccountId(expectedAccountId);
  const sourceAccountId = normalizePackageAccountId(manifest.sourceAccountId);
  if (
    manifest.format !== AGENCY_PACKAGE_MANIFEST_FORMAT
    || manifest.version !== AGENCY_PACKAGE_MANIFEST_VERSION
    || manifest.kind !== CENTRAL_PACKAGE_KIND
    || !sourceAccountId
    || sourceAccountId !== normalizedExpectedAccountId
    || manifest.sourceAccountId !== sourceAccountId
  ) {
    if (sourceAccountId && normalizedExpectedAccountId && sourceAccountId !== normalizedExpectedAccountId) {
      throwPackageValidationError("PACKAGE_ACCOUNT_MISMATCH");
    }
    throwPackageValidationError("PACKAGE_MANIFEST_INVALID");
  }
  const createdAt = String(manifest.createdAt || "");
  const createdDate = new Date(createdAt);
  if (!createdAt || Number.isNaN(createdDate.getTime()) || createdDate.toISOString() !== createdAt) {
    throwPackageValidationError("PACKAGE_MANIFEST_INVALID");
  }

  assertExactObjectKeys(manifest.database, [
    "path",
    "size",
    "sha256",
    "dataVersion",
    "policyCount",
  ], "PACKAGE_MANIFEST_INVALID");
  if (
    manifest.database.path !== CENTRAL_PACKAGE_DATABASE_PATH
    || !Number.isSafeInteger(manifest.database.size)
    || manifest.database.size < 0
    || manifest.database.size > PACKAGE_MAX_DATABASE_BYTES
    || !PACKAGE_SHA256_PATTERN.test(String(manifest.database.sha256 || ""))
    || !Number.isInteger(manifest.database.dataVersion)
    || manifest.database.dataVersion < 1
    || manifest.database.dataVersion > 6
    || !Number.isInteger(manifest.database.policyCount)
    || manifest.database.policyCount < 0
    || manifest.database.policyCount > 10_000
  ) {
    throwPackageValidationError("PACKAGE_MANIFEST_INVALID");
  }
  if (!Array.isArray(manifest.attachments) || manifest.attachments.length > MAX_EMPLOYEE_SEED_ATTACHMENTS) {
    throwPackageValidationError("PACKAGE_MANIFEST_INVALID");
  }
  const attachments = [];
  const pathsByKey = new Map();
  let totalAttachmentBytes = 0;
  let previousPath = "";
  for (const attachment of manifest.attachments) {
    assertExactObjectKeys(attachment, ["path", "size", "sha256"], "PACKAGE_MANIFEST_INVALID");
    const path = normalizeManifestAttachmentPath(attachment.path, packageRoot);
    if (
      attachment.path !== path
      || !Number.isSafeInteger(attachment.size)
      || attachment.size < 0
      || attachment.size > PACKAGE_MAX_ATTACHMENT_BYTES
      || !PACKAGE_SHA256_PATTERN.test(String(attachment.sha256 || ""))
      || (previousPath && path <= previousPath)
    ) {
      throwPackageValidationError("PACKAGE_MANIFEST_INVALID");
    }
    const pathKey = packagePathKey(path);
    if (pathsByKey.has(pathKey)) throwPackageValidationError("PACKAGE_PATH_COLLISION");
    pathsByKey.set(pathKey, attachment);
    totalAttachmentBytes += attachment.size;
    if (totalAttachmentBytes > PACKAGE_MAX_EXTERNAL_BYTES) {
      throwPackageValidationError("PACKAGE_SIZE_LIMIT");
    }
    previousPath = path;
    attachments.push({ ...attachment, path });
  }
  return {
    sourceAccountId,
    database: { ...manifest.database },
    attachments,
  };
}

function validateCentralPackageDatabase(database, manifestContract) {
  if (
    !database
    || typeof database !== "object"
    || Array.isArray(database)
    || !Array.isArray(database.policies)
    || database.policies.length > 10_000
  ) {
    throwPackageValidationError("PACKAGE_DATABASE_INVALID");
  }
  const dataVersion = Number(database.version);
  const databaseAccountId = normalizePackageAccountId(database.accountId);
  if (
    !Number.isInteger(dataVersion)
    || dataVersion < 1
    || dataVersion > 6
    || dataVersion !== manifestContract.database.dataVersion
    || database.policies.length !== manifestContract.database.policyCount
    || !databaseAccountId
    || databaseAccountId !== manifestContract.sourceAccountId
  ) {
    if (databaseAccountId && databaseAccountId !== manifestContract.sourceAccountId) {
      throwPackageValidationError("PACKAGE_ACCOUNT_MISMATCH");
    }
    throwPackageValidationError("PACKAGE_DATABASE_INVALID");
  }

  const manifestPathsByKey = new Map(manifestContract.attachments.map((attachment) => (
    [packagePathKey(attachment.path), attachment.path]
  )));
  const referencedPathsByKey = new Map();
  let attachmentRecordCount = 0;
  for (const policy of database.policies) {
    if (!policy || typeof policy !== "object" || Array.isArray(policy)) {
      throwPackageValidationError("PACKAGE_DATABASE_INVALID");
    }
    if (!normalizePackageAccountId(policy.ownerId)) {
      throwPackageValidationError("PACKAGE_DATABASE_INVALID");
    }
    if (policy.attachments !== undefined && !Array.isArray(policy.attachments)) {
      throwPackageValidationError("PACKAGE_DATABASE_INVALID");
    }
    for (const attachment of Array.isArray(policy.attachments) ? policy.attachments : []) {
      attachmentRecordCount += 1;
      if (attachmentRecordCount > MAX_EMPLOYEE_SEED_ATTACHMENTS) {
        throwPackageValidationError("PACKAGE_DATABASE_INVALID");
      }
      if (!attachment || typeof attachment !== "object" || Array.isArray(attachment)) {
        throwPackageValidationError("PACKAGE_DATABASE_INVALID");
      }
      if (attachment.storage !== "file") continue;
      if (
        !Array.isArray(attachment.relativePath)
        || attachment.relativePath.some((segment) => typeof segment !== "string" || segment.includes("/"))
      ) {
        throwPackageValidationError("PACKAGE_DATABASE_INVALID");
      }
      const rawPath = attachment.relativePath.join("/");
      const validatedPath = normalizeManifestAttachmentPath(rawPath, "");
      if (rawPath !== validatedPath) throwPackageValidationError("PACKAGE_DATABASE_INVALID");
      const pathKey = packagePathKey(validatedPath);
      const existingPath = referencedPathsByKey.get(pathKey);
      if (existingPath && existingPath !== validatedPath) {
        throwPackageValidationError("PACKAGE_PATH_COLLISION");
      }
      if (!manifestPathsByKey.has(pathKey) || manifestPathsByKey.get(pathKey) !== validatedPath) {
        throwPackageValidationError("PACKAGE_ATTACHMENT_MISMATCH");
      }
      referencedPathsByKey.set(pathKey, validatedPath);
    }
  }
  if (
    referencedPathsByKey.size !== manifestPathsByKey.size
    || [...manifestPathsByKey.keys()].some((pathKey) => !referencedPathsByKey.has(pathKey))
  ) {
    throwPackageValidationError("PACKAGE_ATTACHMENT_MISMATCH");
  }
  return {
    database,
    accountId: databaseAccountId,
    attachmentRecordCount,
  };
}

function validateEmployeeAssignmentManifest(manifest, {
  expectedSourceAccountId,
  expectedTargetAccountId,
  expectedKeyId,
  transferKey,
  packageRoot,
}) {
  if (!decodePackageTransferKey(transferKey) || !PACKAGE_TRANSFER_KEY_ID_PATTERN.test(String(expectedKeyId || ""))) {
    throwPackageValidationError("PACKAGE_PAIRING_REQUIRED", { statusCode: 403 });
  }
  assertExactObjectKeys(manifest, [
    "format",
    "version",
    "kind",
    "sourceAccountId",
    "targetAccountId",
    "createdAt",
    "database",
    "attachments",
    "authentication",
  ], "PACKAGE_MANIFEST_INVALID");
  const sourceAccountId = normalizePackageAccountId(manifest.sourceAccountId);
  const targetAccountId = normalizePackageAccountId(manifest.targetAccountId);
  const normalizedExpectedSource = normalizePackageAccountId(expectedSourceAccountId);
  const normalizedExpectedTarget = normalizePackageAccountId(expectedTargetAccountId);
  if (
    manifest.format !== AGENCY_PACKAGE_MANIFEST_FORMAT
    || manifest.version !== EMPLOYEE_ASSIGNMENT_PACKAGE_MANIFEST_VERSION
    || manifest.kind !== EMPLOYEE_ASSIGNMENT_PACKAGE_KIND
    || !sourceAccountId
    || !targetAccountId
    || manifest.sourceAccountId !== sourceAccountId
    || manifest.targetAccountId !== targetAccountId
  ) {
    throwPackageValidationError("PACKAGE_MANIFEST_INVALID");
  }
  if (
    sourceAccountId !== normalizedExpectedSource
    || targetAccountId !== normalizedExpectedTarget
  ) {
    throwPackageValidationError("PACKAGE_ACCOUNT_MISMATCH", { statusCode: 403 });
  }
  const createdAt = String(manifest.createdAt || "");
  const createdDate = new Date(createdAt);
  if (!createdAt || Number.isNaN(createdDate.getTime()) || createdDate.toISOString() !== createdAt) {
    throwPackageValidationError("PACKAGE_MANIFEST_INVALID");
  }

  assertExactObjectKeys(manifest.database, [
    "path",
    "size",
    "sha256",
    "dataVersion",
    "policyCount",
  ], "PACKAGE_MANIFEST_INVALID");
  if (
    manifest.database.path !== CENTRAL_PACKAGE_DATABASE_PATH
    || !Number.isSafeInteger(manifest.database.size)
    || manifest.database.size < 0
    || manifest.database.size > PACKAGE_MAX_DATABASE_BYTES
    || !PACKAGE_SHA256_PATTERN.test(String(manifest.database.sha256 || ""))
    || manifest.database.dataVersion !== 6
    || !Number.isInteger(manifest.database.policyCount)
    || manifest.database.policyCount < 1
    || manifest.database.policyCount > 10_000
  ) {
    throwPackageValidationError("PACKAGE_MANIFEST_INVALID");
  }

  assertExactObjectKeys(
    manifest.authentication,
    ["algorithm", "keyId", "sha256"],
    "PACKAGE_MANIFEST_INVALID",
  );
  if (
    manifest.authentication.algorithm !== "hmac-sha256"
    || !PACKAGE_TRANSFER_KEY_ID_PATTERN.test(String(manifest.authentication.keyId || ""))
    || !PACKAGE_SHA256_PATTERN.test(String(manifest.authentication.sha256 || ""))
  ) {
    throwPackageValidationError("PACKAGE_MANIFEST_INVALID");
  }
  if (
    manifest.authentication.keyId !== expectedKeyId
    || manifest.authentication.keyId !== packageTransferKeyId(transferKey)
  ) {
    throwPackageValidationError("PACKAGE_PAIRING_REQUIRED", { statusCode: 403 });
  }

  if (!Array.isArray(manifest.attachments) || manifest.attachments.length > MAX_EMPLOYEE_SEED_ATTACHMENTS) {
    throwPackageValidationError("PACKAGE_MANIFEST_INVALID");
  }
  const attachments = [];
  const pathsByKey = new Map();
  let totalAttachmentBytes = 0;
  let previousPath = "";
  for (const attachment of manifest.attachments) {
    assertExactObjectKeys(attachment, ["path", "size", "sha256"], "PACKAGE_MANIFEST_INVALID");
    const path = normalizeManifestAttachmentPath(attachment.path, packageRoot);
    if (
      attachment.path !== path
      || !Number.isSafeInteger(attachment.size)
      || attachment.size < 0
      || attachment.size > PACKAGE_MAX_ATTACHMENT_BYTES
      || !PACKAGE_SHA256_PATTERN.test(String(attachment.sha256 || ""))
      || (previousPath && path <= previousPath)
    ) {
      throwPackageValidationError("PACKAGE_MANIFEST_INVALID");
    }
    const pathKey = packagePathKey(path);
    if (pathsByKey.has(pathKey)) throwPackageValidationError("PACKAGE_PATH_COLLISION");
    pathsByKey.set(pathKey, attachment);
    totalAttachmentBytes += attachment.size;
    if (totalAttachmentBytes > PACKAGE_MAX_EXTERNAL_BYTES) {
      throwPackageValidationError("PACKAGE_SIZE_LIMIT");
    }
    previousPath = path;
    attachments.push({ ...attachment, path });
  }

  const expectedHmac = calculateEmployeeAssignmentHmac(manifest, transferKey);
  const expectedBytes = Buffer.from(expectedHmac, "hex");
  const actualBytes = Buffer.from(manifest.authentication.sha256, "hex");
  if (
    expectedBytes.length !== actualBytes.length
    || !timingSafeEqual(expectedBytes, actualBytes)
  ) {
    throwPackageValidationError("PACKAGE_AUTHENTICATION", { statusCode: 403 });
  }
  return {
    sourceAccountId,
    targetAccountId,
    database: { ...manifest.database },
    attachments,
  };
}

function validateEmployeeAssignmentDatabase(database, manifestContract) {
  assertExactObjectKeys(database, [
    "version",
    "accountId",
    "centralAccountId",
    "policies",
    "deletedPolicies",
    "calendarTasks",
  ], "PACKAGE_DATABASE_INVALID");
  const databaseAccountId = normalizePackageAccountId(database.accountId);
  const centralAccountId = normalizePackageAccountId(database.centralAccountId);
  if (
    database.version !== 6
    || databaseAccountId !== manifestContract.targetAccountId
    || centralAccountId !== manifestContract.sourceAccountId
    || database.accountId !== databaseAccountId
    || database.centralAccountId !== centralAccountId
    || !Array.isArray(database.deletedPolicies)
    || database.deletedPolicies.length !== 0
    || !Array.isArray(database.calendarTasks)
    || database.calendarTasks.length !== 0
  ) {
    if (
      (databaseAccountId && databaseAccountId !== manifestContract.targetAccountId)
      || (centralAccountId && centralAccountId !== manifestContract.sourceAccountId)
    ) {
      throwPackageValidationError("PACKAGE_ACCOUNT_MISMATCH", { statusCode: 403 });
    }
    throwPackageValidationError("PACKAGE_DATABASE_INVALID");
  }
  const validated = validateCentralPackageDatabase(database, {
    sourceAccountId: manifestContract.targetAccountId,
    database: manifestContract.database,
    attachments: manifestContract.attachments,
  });
  return {
    database: validated.database,
    sourceAccountId: centralAccountId,
    targetAccountId: databaseAccountId,
    attachmentRecordCount: validated.attachmentRecordCount,
  };
}

function calculateFileSha256(filePath) {
  return new Promise((resolveHash, rejectHash) => {
    const hash = createHash("sha256");
    const input = createReadStream(filePath);
    input.on("data", (chunk) => hash.update(chunk));
    input.on("error", () => rejectHash(new PackageValidationError("PACKAGE_HASH_MISMATCH")));
    input.on("end", () => resolveHash(hash.digest("hex")));
  });
}

function assertExactFileSet(actualFiles, expectedPaths) {
  const actualPaths = actualFiles.map((entry) => entry.logicalPath).sort();
  const sortedExpectedPaths = [...expectedPaths].sort();
  if (
    actualPaths.length !== sortedExpectedPaths.length
    || actualPaths.some((path, index) => path !== sortedExpectedPaths[index])
  ) {
    throwPackageValidationError("PACKAGE_LAYOUT_INVALID");
  }
}

export async function validateExtractedCentralPackage({
  extractionDirectory,
  preflight,
  expectedAccountId,
}) {
  if (!preflight || typeof preflight !== "object") {
    throwPackageValidationError("PACKAGE_ARCHIVE_INVALID");
  }
  await assertExtractedPackageHasNoReparsePoints(extractionDirectory);
  const actualRawEntries = await collectExtractedPackageEntries(extractionDirectory);
  const actualLayout = validateCentralPackageArchiveEntries(actualRawEntries, {
    extractionDirectory,
  });
  if (actualLayout.wrapperName !== preflight.wrapperName) {
    throwPackageValidationError("PACKAGE_LAYOUT_INVALID");
  }
  const preflightFilesByPath = new Map(preflight.regularFiles.map((entry) => (
    [entry.archivePath, entry]
  )));
  const actualArchiveFiles = actualLayout.regularFiles.map((entry) => ({
    ...entry,
    archivePath: preflight.wrapperName
      ? `${preflight.wrapperName}/${entry.logicalPath}`
      : entry.logicalPath,
  }));
  if (
    actualArchiveFiles.length !== preflightFilesByPath.size
    || actualArchiveFiles.some((entry) => {
      const expectedEntry = preflightFilesByPath.get(entry.archivePath);
      return !expectedEntry || expectedEntry.size !== entry.size;
    })
  ) {
    throwPackageValidationError("PACKAGE_ARCHIVE_INVALID");
  }

  const packageRoot = preflight.wrapperName
    ? join(extractionDirectory, preflight.wrapperName)
    : extractionDirectory;
  const manifestPath = join(packageRoot, CENTRAL_PACKAGE_MANIFEST_PATH);
  let manifest;
  try {
    const manifestStats = await stat(manifestPath);
    if (!manifestStats.isFile() || manifestStats.size > PACKAGE_MAX_MANIFEST_BYTES) {
      throw new Error("manifest-size");
    }
    manifest = JSON.parse(await readFile(manifestPath, "utf8"));
  } catch {
    throwPackageValidationError("PACKAGE_MANIFEST_INVALID");
  }
  const manifestContract = validateCentralPackageManifest(manifest, {
    expectedAccountId,
    packageRoot,
  });
  const expectedFilePaths = [
    CENTRAL_PACKAGE_MANIFEST_PATH,
    CENTRAL_PACKAGE_DATABASE_PATH,
    ...manifestContract.attachments.map((attachment) => attachment.path),
  ];
  assertExactFileSet(actualLayout.regularFiles, expectedFilePaths);

  const databasePath = join(packageRoot, CENTRAL_PACKAGE_DATABASE_PATH);
  let databaseContents;
  try {
    const databaseStats = await stat(databasePath);
    if (
      !databaseStats.isFile()
      || databaseStats.size !== manifestContract.database.size
      || databaseStats.size > PACKAGE_MAX_DATABASE_BYTES
    ) {
      throw new Error("database-size");
    }
    databaseContents = await readFile(databasePath);
  } catch {
    throwPackageValidationError("PACKAGE_DATABASE_INVALID");
  }
  if (calculateSha256(databaseContents) !== manifestContract.database.sha256) {
    throwPackageValidationError("PACKAGE_HASH_MISMATCH");
  }
  let database;
  try {
    database = JSON.parse(databaseContents.toString("utf8").replace(/^\uFEFF/, ""));
  } catch {
    throwPackageValidationError("PACKAGE_DATABASE_INVALID");
  }
  const databaseContract = validateCentralPackageDatabase(database, manifestContract);

  for (const attachment of manifestContract.attachments) {
    const attachmentPath = join(packageRoot, ...attachment.path.split("/"));
    let attachmentStats;
    try {
      attachmentStats = await stat(attachmentPath);
    } catch {
      throwPackageValidationError("PACKAGE_ATTACHMENT_MISMATCH");
    }
    if (!attachmentStats.isFile() || attachmentStats.size !== attachment.size) {
      throwPackageValidationError("PACKAGE_ATTACHMENT_MISMATCH");
    }
    if (await calculateFileSha256(attachmentPath) !== attachment.sha256) {
      throwPackageValidationError("PACKAGE_HASH_MISMATCH");
    }
  }
  return {
    packageRoot,
    databasePath,
    attachmentsDirectory: join(packageRoot, "Zalaczniki"),
    manifest,
    database: databaseContract.database,
    databaseContents,
    accountId: databaseContract.accountId,
  };
}

export async function validateExtractedEmployeeAssignmentPackage({
  extractionDirectory,
  preflight,
  expectedSourceAccountId,
  expectedTargetAccountId,
  expectedKeyId,
  transferKey,
}) {
  if (!preflight || typeof preflight !== "object") {
    throwPackageValidationError("PACKAGE_ARCHIVE_INVALID");
  }
  await assertExtractedPackageHasNoReparsePoints(extractionDirectory);
  const actualRawEntries = await collectExtractedPackageEntries(extractionDirectory);
  const actualLayout = validateCentralPackageArchiveEntries(actualRawEntries, {
    extractionDirectory,
  });
  if (actualLayout.wrapperName !== preflight.wrapperName) {
    throwPackageValidationError("PACKAGE_LAYOUT_INVALID");
  }
  const preflightFilesByPath = new Map(preflight.regularFiles.map((entry) => (
    [entry.archivePath, entry]
  )));
  const actualArchiveFiles = actualLayout.regularFiles.map((entry) => ({
    ...entry,
    archivePath: preflight.wrapperName
      ? `${preflight.wrapperName}/${entry.logicalPath}`
      : entry.logicalPath,
  }));
  if (
    actualArchiveFiles.length !== preflightFilesByPath.size
    || actualArchiveFiles.some((entry) => {
      const expectedEntry = preflightFilesByPath.get(entry.archivePath);
      return !expectedEntry || expectedEntry.size !== entry.size;
    })
  ) {
    throwPackageValidationError("PACKAGE_ARCHIVE_INVALID");
  }

  const packageRoot = preflight.wrapperName
    ? join(extractionDirectory, preflight.wrapperName)
    : extractionDirectory;
  const manifestPath = join(packageRoot, CENTRAL_PACKAGE_MANIFEST_PATH);
  let manifest;
  try {
    const manifestStats = await stat(manifestPath);
    if (!manifestStats.isFile() || manifestStats.size > PACKAGE_MAX_MANIFEST_BYTES) {
      throw new Error("manifest-size");
    }
    manifest = JSON.parse(await readFile(manifestPath, "utf8"));
  } catch {
    throwPackageValidationError("PACKAGE_MANIFEST_INVALID");
  }
  const manifestContract = validateEmployeeAssignmentManifest(manifest, {
    expectedSourceAccountId,
    expectedTargetAccountId,
    expectedKeyId,
    transferKey,
    packageRoot,
  });
  const expectedFilePaths = [
    CENTRAL_PACKAGE_MANIFEST_PATH,
    CENTRAL_PACKAGE_DATABASE_PATH,
    ...manifestContract.attachments.map((attachment) => attachment.path),
  ];
  assertExactFileSet(actualLayout.regularFiles, expectedFilePaths);

  const databasePath = join(packageRoot, CENTRAL_PACKAGE_DATABASE_PATH);
  let databaseContents;
  try {
    const databaseStats = await stat(databasePath);
    if (
      !databaseStats.isFile()
      || databaseStats.size !== manifestContract.database.size
      || databaseStats.size > PACKAGE_MAX_DATABASE_BYTES
    ) {
      throw new Error("database-size");
    }
    databaseContents = await readFile(databasePath);
  } catch {
    throwPackageValidationError("PACKAGE_DATABASE_INVALID");
  }
  if (calculateSha256(databaseContents) !== manifestContract.database.sha256) {
    throwPackageValidationError("PACKAGE_HASH_MISMATCH");
  }
  let database;
  try {
    database = JSON.parse(databaseContents.toString("utf8").replace(/^\uFEFF/, ""));
  } catch {
    throwPackageValidationError("PACKAGE_DATABASE_INVALID");
  }
  const databaseContract = validateEmployeeAssignmentDatabase(database, manifestContract);

  for (const attachment of manifestContract.attachments) {
    const attachmentPath = join(packageRoot, ...attachment.path.split("/"));
    let attachmentStats;
    try {
      attachmentStats = await stat(attachmentPath);
    } catch {
      throwPackageValidationError("PACKAGE_ATTACHMENT_MISMATCH");
    }
    if (!attachmentStats.isFile() || attachmentStats.size !== attachment.size) {
      throwPackageValidationError("PACKAGE_ATTACHMENT_MISMATCH");
    }
    if (await calculateFileSha256(attachmentPath) !== attachment.sha256) {
      throwPackageValidationError("PACKAGE_HASH_MISMATCH");
    }
  }
  return {
    packageRoot,
    databasePath,
    attachmentsDirectory: join(packageRoot, "Zalaczniki"),
    manifest,
    database: databaseContract.database,
    databaseContents,
    sourceAccountId: databaseContract.sourceAccountId,
    targetAccountId: databaseContract.targetAccountId,
  };
}
