#!/usr/bin/env -S npx tsx

import { createHash, randomUUID } from "node:crypto";
import {
  chmod,
  lstat,
  mkdir,
  open,
  readFile,
  readdir,
  rename,
  stat,
  unlink,
  writeFile,
} from "node:fs/promises";
import { createReadStream } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";

type JsonRecord = Record<string, unknown>;

interface CliOptions {
  exportDirectory?: string;
  outputRoot: string;
  concurrency: number;
  help: boolean;
}

interface SourceManifestFile {
  path: string;
  dataset: string;
  page: number;
  rowCount: number;
  bytes: number;
  sha256: string;
}

interface SourceDataset {
  rowCount: number;
  pages: number;
  sourceCountBefore: number;
  sourceCountAfter: number;
}

interface SourceManifest {
  format: string;
  formatVersion: number;
  createdAt: string;
  source: { projectRef: string };
  datasets: Record<string, SourceDataset>;
  files: SourceManifestFile[];
}

interface SourceBucket {
  id: string;
  name: string;
  public: boolean;
}

interface SourceObject {
  id: string;
  bucketId: string;
  name: string;
  version: string | null;
  sourceBytes: number | null;
  mimeType: string | null;
  etag: string | null;
  relativePath: string;
}

interface MirroredObject {
  id: string;
  bucketId: string;
  name: string;
  path: string;
  version: string | null;
  sourceBytes: number | null;
  mimeType: string | null;
  etag: string | null;
  bytes: number;
  sha256: string;
  downloadedAt: string;
}

interface MirrorManifest {
  format: "tonline-supabase-storage-mirror";
  formatVersion: 1;
  status: "in_progress" | "complete";
  createdAt: string;
  updatedAt: string;
  completedAt?: string;
  source: {
    provider: "supabase";
    projectRef: string;
    accessMode: "read-only";
    exportCreatedAt: string;
    exportManifestSha256: string;
    bucketCount: number;
    objectCount: number;
  };
  destination: { layout: "<bucket-id>/<object-name>" };
  summary: {
    bucketCount: number;
    objectCount: number;
    completedObjectCount: number;
    totalBytes: number;
  };
  buckets: SourceBucket[];
  objects: MirroredObject[];
}

const scriptDirectory = path.dirname(fileURLToPath(import.meta.url));
const backendDirectory = path.resolve(scriptDirectory, "..");
const migrationRoot = path.join(backendDirectory, "data", "migration");
const defaultOutputRoot = path.join(backendDirectory, "data", "storage");
const envFile = path.join(backendDirectory, ".env");
const managementApiBaseUrl = "https://api.supabase.com/v1";
const manifestFileName = "manifest.json";
const checksumFileName = "manifest.sha256";

function printHelp(): void {
  console.log(`Usage: npx tsx backend/scripts/mirror-storage.ts [options]

Downloads every object recorded by the newest source export. The Supabase source
is accessed only with GET requests.

Options:
  --export-directory <path>  Source export (default: newest complete export)
  --output-root <path>       Mirror root (default: backend/data/storage)
  --concurrency <number>     Parallel downloads, 1-8 (default: 4)
  --help                     Show this help

Required environment variables (backend/.env is loaded automatically):
  SUPABASE_ACCESS_TOKEN
  SUPABASE_SOURCE_PROJECT_REF (or SUPABASE_PROJECT_REF)`);
}

function parseCli(arguments_: string[]): CliOptions {
  const options: CliOptions = {
    outputRoot: defaultOutputRoot,
    concurrency: 4,
    help: false,
  };

  for (let index = 0; index < arguments_.length; index += 1) {
    const argument = arguments_[index];
    if (argument === "--help" || argument === "-h") {
      options.help = true;
      continue;
    }
    if (argument === "--export-directory" || argument === "--output-root") {
      const value = arguments_[index + 1];
      if (!value) throw new Error(`${argument} requires a path`);
      if (argument === "--export-directory") options.exportDirectory = path.resolve(value);
      else options.outputRoot = path.resolve(value);
      index += 1;
      continue;
    }
    if (argument === "--concurrency") {
      const value = Number(arguments_[index + 1]);
      if (!Number.isInteger(value) || value < 1 || value > 8) {
        throw new Error("--concurrency must be an integer between 1 and 8");
      }
      options.concurrency = value;
      index += 1;
      continue;
    }
    throw new Error(`Unknown argument: ${argument}`);
  }
  return options;
}

function parseDotEnv(contents: string): Record<string, string> {
  const parsed: Record<string, string> = {};
  for (const rawLine of contents.replace(/^\uFEFF/, "").split(/\r?\n/)) {
    const line = rawLine.trim();
    if (!line || line.startsWith("#")) continue;
    const match = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/.exec(line);
    if (!match?.[1] || match[2] === undefined) continue;
    let value = match[2].trim();
    if (value.length >= 2 && ((value.startsWith('"') && value.endsWith('"')) ||
      (value.startsWith("'") && value.endsWith("'")))) {
      value = value.slice(1, -1);
    }
    parsed[match[1]] = value;
  }
  return parsed;
}

async function loadLocalEnvironment(): Promise<void> {
  try {
    const parsed = parseDotEnv(await readFile(envFile, "utf8"));
    for (const [key, value] of Object.entries(parsed)) {
      if (process.env[key] === undefined) process.env[key] = value;
    }
  } catch (error) {
    if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
  }
}

function requireEnvironment(name: string, aliases: string[] = []): string {
  for (const candidate of [name, ...aliases]) {
    const value = process.env[candidate]?.trim();
    if (value) return value;
  }
  throw new Error(`Missing required environment variable: ${name}`);
}

function sha256(contents: Uint8Array): string {
  return createHash("sha256").update(contents).digest("hex");
}

function asRecord(value: unknown, label: string): JsonRecord {
  if (!value || typeof value !== "object" || Array.isArray(value)) {
    throw new Error(`${label} must be an object`);
  }
  return value as JsonRecord;
}

function asString(value: unknown, label: string): string {
  if (typeof value !== "string" || value.length === 0) {
    throw new Error(`${label} must be a non-empty string`);
  }
  return value;
}

function asInteger(value: unknown, label: string): number {
  if (!Number.isSafeInteger(value) || (value as number) < 0) {
    throw new Error(`${label} must be a non-negative integer`);
  }
  return value as number;
}

function parseSourceManifest(value: unknown): SourceManifest {
  const manifest = asRecord(value, "source manifest");
  if (manifest.format !== "tonline-supabase-source-export" || manifest.formatVersion !== 1) {
    throw new Error("Unsupported source export manifest format");
  }
  const source = asRecord(manifest.source, "source manifest source");
  const projectRef = asString(source.projectRef, "source.projectRef");
  const datasetsValue = asRecord(manifest.datasets, "source manifest datasets");
  if (!Array.isArray(manifest.files)) throw new Error("Source manifest has no file list");

  const datasets: Record<string, SourceDataset> = {};
  for (const [name, rawDataset] of Object.entries(datasetsValue)) {
    const dataset = asRecord(rawDataset, `datasets.${name}`);
    datasets[name] = {
      rowCount: asInteger(dataset.rowCount, `${name}.rowCount`),
      pages: asInteger(dataset.pages, `${name}.pages`),
      sourceCountBefore: asInteger(dataset.sourceCountBefore, `${name}.sourceCountBefore`),
      sourceCountAfter: asInteger(dataset.sourceCountAfter, `${name}.sourceCountAfter`),
    };
  }

  const files = manifest.files.map((rawFile, index): SourceManifestFile => {
    const file = asRecord(rawFile, `files[${index}]`);
    const digest = asString(file.sha256, `files[${index}].sha256`);
    if (!/^[a-f0-9]{64}$/.test(digest)) throw new Error(`Invalid source checksum at files[${index}]`);
    return {
      path: asString(file.path, `files[${index}].path`),
      dataset: asString(file.dataset, `files[${index}].dataset`),
      page: asInteger(file.page, `files[${index}].page`),
      rowCount: asInteger(file.rowCount, `files[${index}].rowCount`),
      bytes: asInteger(file.bytes, `files[${index}].bytes`),
      sha256: digest,
    };
  });

  return {
    format: manifest.format,
    formatVersion: manifest.formatVersion,
    createdAt: asString(manifest.createdAt, "source.createdAt"),
    source: { projectRef },
    datasets,
    files,
  };
}

function resolveSafe(root: string, relativePath: string): string {
  if (path.isAbsolute(relativePath) || path.win32.isAbsolute(relativePath) || relativePath.includes("\0")) {
    throw new Error(`Unsafe path: ${relativePath}`);
  }
  const segments = relativePath.split("/");
  if (segments.some((segment) => !segment || segment === "." || segment === ".." || segment.includes("\\"))) {
    throw new Error(`Unsafe path segment: ${relativePath}`);
  }
  if (process.platform === "win32") {
    for (const segment of segments) {
      if (/[<>:"|?*\u0000-\u001f]/.test(segment) || /[. ]$/.test(segment) ||
        /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\..*)?$/i.test(segment)) {
        throw new Error(`Path cannot be represented safely on Windows: ${relativePath}`);
      }
    }
  }
  const resolvedRoot = path.resolve(root);
  const resolved = path.resolve(resolvedRoot, ...segments);
  if (!resolved.startsWith(`${resolvedRoot}${path.sep}`)) {
    throw new Error(`Path escapes destination root: ${relativePath}`);
  }
  return resolved;
}

async function assertNoSymlinks(root: string, filePath: string): Promise<void> {
  const rootPath = path.resolve(root);
  const relative = path.relative(rootPath, filePath);
  if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) {
    throw new Error(`Destination escapes storage root: ${filePath}`);
  }
  const rootStats = await lstat(rootPath);
  if (!rootStats.isDirectory() || rootStats.isSymbolicLink()) {
    throw new Error("Storage root must be a real directory, not a symbolic link");
  }
  let current = rootPath;
  for (const segment of relative.split(path.sep).slice(0, -1)) {
    current = path.join(current, segment);
    const currentStats = await lstat(current);
    if (!currentStats.isDirectory() || currentStats.isSymbolicLink()) {
      throw new Error(`Unsafe destination directory: ${current}`);
    }
  }
  try {
    const fileStats = await lstat(filePath);
    if (!fileStats.isFile() || fileStats.isSymbolicLink()) {
      throw new Error(`Destination is not a regular file: ${filePath}`);
    }
  } catch (error) {
    if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
  }
}

async function atomicReplace(filePath: string, contents: Uint8Array): Promise<void> {
  const temporaryPath = `${filePath}.tmp-${process.pid}-${randomUUID()}`;
  await writeFile(temporaryPath, contents, { flag: "wx", mode: 0o600 });
  try {
    await rename(temporaryPath, filePath);
  } catch (error) {
    const code = (error as NodeJS.ErrnoException).code;
    if (code !== "EEXIST" && code !== "EPERM") throw error;
    await unlink(filePath);
    await rename(temporaryPath, filePath);
  } finally {
    await unlink(temporaryPath).catch(() => undefined);
  }
  await chmod(filePath, 0o600).catch(() => undefined);
}

async function newestExportDirectory(): Promise<string> {
  let entries;
  try {
    entries = await readdir(migrationRoot, { withFileTypes: true });
  } catch (error) {
    if ((error as NodeJS.ErrnoException).code === "ENOENT") {
      throw new Error(`No migration exports found in ${migrationRoot}`);
    }
    throw error;
  }
  const candidates: Array<{ directory: string; modified: number }> = [];
  for (const entry of entries) {
    if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
    const directory = path.join(migrationRoot, entry.name);
    try {
      const details = await stat(path.join(directory, manifestFileName));
      candidates.push({ directory, modified: details.mtimeMs });
    } catch {
      // Incomplete exports are skipped.
    }
  }
  candidates.sort((left, right) => right.modified - left.modified);
  if (!candidates[0]) throw new Error(`No complete migration exports found in ${migrationRoot}`);
  return candidates[0].directory;
}

async function readSourceManifest(exportDirectory: string): Promise<{
  manifest: SourceManifest;
  hash: string;
}> {
  const [contents, checksum] = await Promise.all([
    readFile(path.join(exportDirectory, manifestFileName)),
    readFile(path.join(exportDirectory, checksumFileName), "ascii"),
  ]);
  const match = /^([a-f0-9]{64})\s+manifest\.json\s*$/i.exec(checksum);
  if (!match?.[1]) throw new Error("Source manifest.sha256 has an invalid format");
  const hash = sha256(contents);
  if (hash !== match[1].toLowerCase()) throw new Error("Source manifest checksum mismatch");
  let parsed: unknown;
  try {
    parsed = JSON.parse(contents.toString("utf8"));
  } catch {
    throw new Error("Source manifest is not valid JSON");
  }
  return { manifest: parseSourceManifest(parsed), hash };
}

async function readDatasetRows(
  exportDirectory: string,
  manifest: SourceManifest,
  datasetName: "storageBuckets" | "storageObjects",
): Promise<JsonRecord[]> {
  const dataset = manifest.datasets[datasetName];
  if (!dataset) throw new Error(`Source manifest has no ${datasetName} dataset`);
  if (dataset.sourceCountBefore !== dataset.sourceCountAfter || dataset.rowCount !== dataset.sourceCountAfter) {
    throw new Error(`Source export recorded unstable counts for ${datasetName}`);
  }
  const files = manifest.files.filter((file) => file.dataset === datasetName)
    .sort((left, right) => left.page - right.page);
  if (files.length !== dataset.pages) throw new Error(`Source page count mismatch for ${datasetName}`);
  const rows: JsonRecord[] = [];
  for (const [index, file] of files.entries()) {
    if (file.page !== index + 1) throw new Error(`Non-contiguous source pages for ${datasetName}`);
    const diskPath = resolveSafe(exportDirectory, file.path);
    const contents = await readFile(diskPath);
    if (contents.byteLength !== file.bytes || sha256(contents) !== file.sha256) {
      throw new Error(`Source export file failed integrity check: ${file.path}`);
    }
    let rawPage: unknown;
    try {
      rawPage = JSON.parse(contents.toString("utf8"));
    } catch {
      throw new Error(`Invalid source JSON: ${file.path}`);
    }
    const page = asRecord(rawPage, file.path);
    if (page.dataset !== datasetName || page.page !== file.page || page.rowCount !== file.rowCount ||
      !Array.isArray(page.rows) || page.rows.length !== file.rowCount) {
      throw new Error(`Source page metadata mismatch: ${file.path}`);
    }
    rows.push(...page.rows.map((row, rowIndex) => asRecord(row, `${file.path}.rows[${rowIndex}]`)));
  }
  if (rows.length !== dataset.rowCount) throw new Error(`Source row count mismatch for ${datasetName}`);
  return rows;
}

function optionalString(value: unknown): string | null {
  return typeof value === "string" && value.length > 0 ? value : null;
}

function sourceSize(metadataValue: unknown, label: string): number | null {
  if (!metadataValue || typeof metadataValue !== "object" || Array.isArray(metadataValue)) return null;
  const metadata = metadataValue as JsonRecord;
  const values = [metadata.size, metadata.contentLength]
    .filter((value) => value !== undefined && value !== null)
    .map((value) => typeof value === "number" ? value : Number(value));
  for (const value of values) {
    if (!Number.isSafeInteger(value) || value < 0) throw new Error(`Invalid source size for ${label}`);
  }
  if (values.length > 1 && values.some((value) => value !== values[0])) {
    throw new Error(`Conflicting source sizes for ${label}`);
  }
  return values[0] ?? null;
}

function parseInventory(bucketRows: JsonRecord[], objectRows: JsonRecord[]): {
  buckets: SourceBucket[];
  objects: SourceObject[];
} {
  const buckets = bucketRows.map((row, index): SourceBucket => ({
    id: asString(row.id, `storageBuckets[${index}].id`),
    name: asString(row.name, `storageBuckets[${index}].name`),
    public: typeof row.public === "boolean" ? row.public : false,
  }));
  const bucketIds = new Set<string>();
  const bucketPaths = new Set<string>();
  for (const bucket of buckets) {
    if (bucket.id.includes("/") || bucket.id.includes("\\")) {
      throw new Error(`Bucket id is not a single safe path segment: ${bucket.id}`);
    }
    resolveSafe(defaultOutputRoot, bucket.id);
    if (bucketIds.has(bucket.id)) throw new Error(`Duplicate source bucket: ${bucket.id}`);
    const collisionKey = process.platform === "win32" ? bucket.id.toLowerCase() : bucket.id;
    if (bucketPaths.has(collisionKey)) throw new Error(`Colliding source bucket path: ${bucket.id}`);
    bucketIds.add(bucket.id);
    bucketPaths.add(collisionKey);
  }
  const seenIds = new Set<string>();
  const seenPaths = new Set<string>();
  const objects = objectRows.map((row, index): SourceObject => {
    const id = asString(row.id, `storageObjects[${index}].id`);
    const bucketId = asString(row.bucket_id, `storageObjects[${index}].bucket_id`);
    const name = asString(row.name, `storageObjects[${index}].name`);
    if (!bucketIds.has(bucketId)) throw new Error(`Object references unknown bucket: ${bucketId}`);
    const relativePath = `${bucketId}/${name}`;
    resolveSafe(defaultOutputRoot, relativePath);
    if (seenIds.has(id)) throw new Error(`Duplicate source object id: ${id}`);
    const collisionKey = process.platform === "win32" ? relativePath.toLowerCase() : relativePath;
    if (seenPaths.has(collisionKey)) throw new Error(`Duplicate destination path: ${relativePath}`);
    seenIds.add(id);
    seenPaths.add(collisionKey);
    const metadata = row.metadata && typeof row.metadata === "object" && !Array.isArray(row.metadata)
      ? row.metadata as JsonRecord
      : {};
    return {
      id,
      bucketId,
      name,
      version: optionalString(row.version),
      sourceBytes: sourceSize(row.metadata, relativePath),
      mimeType: optionalString(metadata.mimetype),
      etag: optionalString(metadata.eTag ?? metadata.etag),
      relativePath,
    };
  });
  return { buckets, objects };
}

function delay(milliseconds: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, milliseconds));
}

function retryableStatus(status: number): boolean {
  return status === 408 || status === 425 || status === 429 || status >= 500;
}

async function fetchWithRetries(url: string, accessToken: string, label: string): Promise<Response> {
  for (let attempt = 1; attempt <= 5; attempt += 1) {
    try {
      const response = await fetch(url, {
        method: "GET",
        headers: { Authorization: `Bearer ${accessToken}` },
        redirect: "manual",
        signal: AbortSignal.timeout(120_000),
      });
      if (response.ok) return response;
      await response.body?.cancel().catch(() => undefined);
      if (!retryableStatus(response.status) || attempt === 5) {
        const requestId = response.headers.get("x-request-id");
        throw new Error(`${label} returned HTTP ${response.status}${requestId ? ` (request ${requestId})` : ""}`);
      }
    } catch (error) {
      if (attempt === 5 || (error instanceof Error && /returned HTTP 4\d\d/.test(error.message))) throw error;
    }
    await delay(500 * (2 ** (attempt - 1)) + Math.floor(Math.random() * 250));
  }
  throw new Error(`${label} retry loop ended unexpectedly`);
}

async function getStorageApiKey(managementToken: string, projectRef: string): Promise<string> {
  const endpoint = `${managementApiBaseUrl}/projects/${encodeURIComponent(projectRef)}/api-keys?reveal=true`;
  const response = await fetchWithRetries(endpoint, managementToken, "Supabase Management API");
  let payload: unknown;
  try {
    payload = await response.json();
  } catch {
    throw new Error("Supabase Management API returned invalid JSON");
  }
  if (!Array.isArray(payload)) throw new Error("Supabase Management API returned an invalid API key list");
  const keys = payload.map((value) => asRecord(value, "API key entry"));
  const selected = keys.find((key) => key.type === "secret" && typeof key.api_key === "string" && key.api_key) ??
    keys.find((key) => key.type === "legacy" && key.name === "service_role" &&
      typeof key.api_key === "string" && key.api_key);
  if (!selected || typeof selected.api_key !== "string") {
    throw new Error("No revealed secret or service_role API key is available for the source project");
  }
  return selected.api_key;
}

async function hashLocalFile(filePath: string): Promise<{ bytes: number; sha256: string }> {
  const details = await stat(filePath);
  if (!details.isFile()) throw new Error(`Not a regular file: ${filePath}`);
  const hash = createHash("sha256");
  let bytes = 0;
  for await (const chunk of createReadStream(filePath)) {
    const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
    hash.update(buffer);
    bytes += buffer.byteLength;
  }
  return { bytes, sha256: hash.digest("hex") };
}

async function readResumeManifest(outputRoot: string, projectRef: string): Promise<MirrorManifest | null> {
  try {
    const [contents, checksum] = await Promise.all([
      readFile(path.join(outputRoot, manifestFileName)),
      readFile(path.join(outputRoot, checksumFileName), "ascii"),
    ]);
    const match = /^([a-f0-9]{64})\s+manifest\.json\s*$/i.exec(checksum);
    if (!match?.[1] || sha256(contents) !== match[1].toLowerCase()) return null;
    const value = JSON.parse(contents.toString("utf8")) as unknown;
    const record = asRecord(value, "existing mirror manifest");
    if (record.format !== "tonline-supabase-storage-mirror" || record.formatVersion !== 1 ||
      !Array.isArray(record.objects)) return null;
    const source = asRecord(record.source, "existing mirror source");
    if (source.projectRef !== projectRef) return null;
    return value as MirrorManifest;
  } catch (error) {
    if ((error as NodeJS.ErrnoException).code === "ENOENT" || error instanceof SyntaxError) return null;
    throw error;
  }
}

function sameSourceObject(source: SourceObject, mirrored: MirroredObject): boolean {
  return mirrored.id === source.id && mirrored.bucketId === source.bucketId && mirrored.name === source.name &&
    mirrored.path === source.relativePath && mirrored.version === source.version &&
    mirrored.sourceBytes === source.sourceBytes && mirrored.etag === source.etag &&
    typeof mirrored.bytes === "number" && /^[a-f0-9]{64}$/.test(mirrored.sha256);
}

async function downloadObject(
  sourceUrl: string,
  apiKey: string,
  outputRoot: string,
  object: SourceObject,
): Promise<MirroredObject> {
  const diskPath = resolveSafe(outputRoot, object.relativePath);
  await mkdir(path.dirname(diskPath), { recursive: true, mode: 0o700 });
  await assertNoSymlinks(outputRoot, diskPath);
  const encodedPath = [object.bucketId, ...object.name.split("/")].map(encodeURIComponent).join("/");
  const endpoint = `${sourceUrl}/storage/v1/object/authenticated/${encodedPath}`;

  for (let attempt = 1; attempt <= 5; attempt += 1) {
    const temporaryPath = `${diskPath}.mirror-part-${process.pid}-${randomUUID()}`;
    try {
      const response = await fetch(endpoint, {
        method: "GET",
        headers: { apikey: apiKey, Authorization: `Bearer ${apiKey}` },
        redirect: "manual",
        signal: AbortSignal.timeout(300_000),
      });
      if (!response.ok || !response.body) {
        await response.body?.cancel().catch(() => undefined);
        if (!retryableStatus(response.status) || attempt === 5) {
          const requestId = response.headers.get("x-request-id");
          throw new Error(`Storage GET returned HTTP ${response.status} for ${object.relativePath}${requestId ? ` (request ${requestId})` : ""}`);
        }
        throw new Error(`Retryable Storage HTTP ${response.status}`);
      }

      const handle = await open(temporaryPath, "wx", 0o600);
      const hash = createHash("sha256");
      let bytes = 0;
      try {
        const reader = response.body.getReader();
        while (true) {
          const result = await reader.read();
          if (result.done) break;
          hash.update(result.value);
          bytes += result.value.byteLength;
          let offset = 0;
          while (offset < result.value.byteLength) {
            const written = await handle.write(
              result.value,
              offset,
              result.value.byteLength - offset,
            );
            if (written.bytesWritten <= 0) throw new Error(`Unable to write ${object.relativePath}`);
            offset += written.bytesWritten;
          }
        }
      } finally {
        await handle.close();
      }
      if (object.sourceBytes !== null && bytes !== object.sourceBytes) {
        throw new Error(`Downloaded size mismatch for ${object.relativePath}: expected ${object.sourceBytes}, received ${bytes}`);
      }
      await assertNoSymlinks(outputRoot, diskPath);
      try {
        await rename(temporaryPath, diskPath);
      } catch (error) {
        const code = (error as NodeJS.ErrnoException).code;
        if (code !== "EEXIST" && code !== "EPERM") throw error;
        await unlink(diskPath);
        await rename(temporaryPath, diskPath);
      }
      await chmod(diskPath, 0o600).catch(() => undefined);
      return {
        id: object.id,
        bucketId: object.bucketId,
        name: object.name,
        path: object.relativePath,
        version: object.version,
        sourceBytes: object.sourceBytes,
        mimeType: object.mimeType,
        etag: object.etag,
        bytes,
        sha256: hash.digest("hex"),
        downloadedAt: new Date().toISOString(),
      };
    } catch (error) {
      await unlink(temporaryPath).catch(() => undefined);
      if (attempt === 5 || (error instanceof Error && /HTTP 4\d\d/.test(error.message))) throw error;
      await delay(750 * (2 ** (attempt - 1)) + Math.floor(Math.random() * 300));
    }
  }
  throw new Error(`Download retry loop ended unexpectedly for ${object.relativePath}`);
}

async function main(): Promise<void> {
  const options = parseCli(process.argv.slice(2));
  if (options.help) {
    printHelp();
    return;
  }
  await loadLocalEnvironment();
  const managementToken = requireEnvironment("SUPABASE_ACCESS_TOKEN");
  const configuredProjectRef = requireEnvironment("SUPABASE_SOURCE_PROJECT_REF", ["SUPABASE_PROJECT_REF"]);
  if (!/^[a-z0-9]+$/i.test(configuredProjectRef)) throw new Error("Invalid Supabase project reference");

  const exportDirectory = options.exportDirectory ?? await newestExportDirectory();
  const { manifest: sourceManifest, hash: sourceManifestHash } = await readSourceManifest(exportDirectory);
  if (sourceManifest.source.projectRef !== configuredProjectRef) {
    throw new Error("Source export project does not match SUPABASE_SOURCE_PROJECT_REF");
  }
  const [bucketRows, objectRows] = await Promise.all([
    readDatasetRows(exportDirectory, sourceManifest, "storageBuckets"),
    readDatasetRows(exportDirectory, sourceManifest, "storageObjects"),
  ]);
  const inventory = parseInventory(bucketRows, objectRows);
  const outputRoot = path.resolve(options.outputRoot);
  await mkdir(outputRoot, { recursive: true, mode: 0o700 });
  const outputStats = await lstat(outputRoot);
  if (!outputStats.isDirectory() || outputStats.isSymbolicLink()) {
    throw new Error("Storage output root must be a real directory");
  }
  for (const bucket of inventory.buckets) {
    const bucketPath = resolveSafe(outputRoot, bucket.id);
    await mkdir(bucketPath, { recursive: true, mode: 0o700 });
    const details = await lstat(bucketPath);
    if (!details.isDirectory() || details.isSymbolicLink()) throw new Error(`Unsafe bucket directory: ${bucket.id}`);
  }

  console.log(`Storage inventory: ${inventory.buckets.length} buckets, ${inventory.objects.length} objects.`);
  console.log("Source mode: read-only Management API and Storage GET requests only.");
  const apiKey = await getStorageApiKey(managementToken, configuredProjectRef);
  const sourceUrl = `https://${configuredProjectRef}.supabase.co`;
  const previous = await readResumeManifest(outputRoot, configuredProjectRef);
  const previousById = new Map((previous?.objects ?? []).map((object) => [object.id, object]));
  const completed = new Map<string, MirroredObject>();

  for (const object of inventory.objects) {
    const candidate = previousById.get(object.id);
    if (!candidate || !sameSourceObject(object, candidate)) continue;
    const diskPath = resolveSafe(outputRoot, object.relativePath);
    try {
      await assertNoSymlinks(outputRoot, diskPath);
      const local = await hashLocalFile(diskPath);
      if (local.bytes === candidate.bytes && local.sha256 === candidate.sha256) completed.set(object.id, candidate);
    } catch (error) {
      if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
    }
  }

  const createdAt = previous?.createdAt ?? new Date().toISOString();
  let checkpointChain = Promise.resolve();
  const writeCheckpoint = (complete: boolean): Promise<void> => {
    checkpointChain = checkpointChain.then(async () => {
      const objects = inventory.objects.map((object) => completed.get(object.id))
        .filter((object): object is MirroredObject => object !== undefined);
      const now = new Date().toISOString();
      const manifest: MirrorManifest = {
        format: "tonline-supabase-storage-mirror",
        formatVersion: 1,
        status: complete ? "complete" : "in_progress",
        createdAt,
        updatedAt: now,
        ...(complete ? { completedAt: now } : {}),
        source: {
          provider: "supabase",
          projectRef: configuredProjectRef,
          accessMode: "read-only",
          exportCreatedAt: sourceManifest.createdAt,
          exportManifestSha256: sourceManifestHash,
          bucketCount: inventory.buckets.length,
          objectCount: inventory.objects.length,
        },
        destination: { layout: "<bucket-id>/<object-name>" },
        summary: {
          bucketCount: inventory.buckets.length,
          objectCount: inventory.objects.length,
          completedObjectCount: objects.length,
          totalBytes: objects.reduce((sum, object) => sum + object.bytes, 0),
        },
        buckets: inventory.buckets,
        objects,
      };
      const contents = Buffer.from(`${JSON.stringify(manifest, null, 2)}\n`, "utf8");
      await atomicReplace(path.join(outputRoot, manifestFileName), contents);
      await atomicReplace(
        path.join(outputRoot, checksumFileName),
        Buffer.from(`${sha256(contents)}  manifest.json\n`, "ascii"),
      );
    });
    return checkpointChain;
  };

  await writeCheckpoint(false);
  const pending = inventory.objects.filter((object) => !completed.has(object.id));
  console.log(`Resume check: ${completed.size} verified locally; ${pending.length} to download.`);
  let nextIndex = 0;
  let failure: unknown;
  let completedThisRun = 0;
  const worker = async (): Promise<void> => {
    while (failure === undefined) {
      const index = nextIndex;
      nextIndex += 1;
      const object = pending[index];
      if (!object) return;
      try {
        const mirrored = await downloadObject(sourceUrl, apiKey, outputRoot, object);
        completed.set(object.id, mirrored);
        completedThisRun += 1;
        await writeCheckpoint(false);
        console.log(`Objects: ${completed.size}/${inventory.objects.length} (${object.relativePath})`);
      } catch (error) {
        failure = error;
      }
    }
  };
  await Promise.all(Array.from({ length: Math.min(options.concurrency, pending.length || 1) }, () => worker()));
  await checkpointChain;
  if (failure !== undefined) throw failure;
  if (completed.size !== inventory.objects.length) throw new Error("Mirror ended with incomplete object count");
  await writeCheckpoint(true);
  console.log(`Storage mirror complete: ${outputRoot}`);
  console.log(`Objects: ${completed.size}; downloaded this run: ${completedThisRun}.`);
  console.log("All mirrored objects have recorded byte counts and SHA-256 checksums.");
}

main().catch((error: unknown) => {
  const message = error instanceof Error ? error.message : "Unknown storage mirror error";
  console.error(`Storage mirror failed: ${message}`);
  process.exitCode = 1;
});
