import { copyFile, mkdir, readFile, rename, unlink } from "node:fs/promises";
import path from "node:path";
import dotenv from "dotenv";
import { createClient } from "@supabase/supabase-js";
import {
  orphanUploadFolderName,
  type OrphanUploadIdentity
} from "../src/lib/orphan-upload-path";

dotenv.config({ path: ".env" });

const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
const extensions = new Set(["pdf", "jpg", "jpeg", "png", "webp"]);

type UploadKind = "documents" | "receipts";
type FileReference = OrphanUploadIdentity & {
  storedName: string;
  extension: string;
  kind: UploadKind;
};

function one<T>(value: T | T[] | null): T | null {
  return Array.isArray(value) ? value[0] ?? null : value;
}

function isWithin(root: string, candidate: string): boolean {
  const relative = path.relative(root, candidate);
  return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
}

function safePath(root: string, ...segments: string[]): string {
  const candidate = path.resolve(root, ...segments);
  if (!isWithin(root, candidate)) throw new Error("Unsafe upload path");
  return candidate;
}

function serverKeyIsValid(key: string): boolean {
  if (key.startsWith("sb_secret_")) return true;
  try {
    const payload = key.split(".")[1];
    if (!payload) return false;
    const claims = JSON.parse(Buffer.from(payload, "base64url").toString("utf8")) as { role?: string };
    return claims.role === "service_role";
  } catch {
    return false;
  }
}

async function destinationMatches(source: string, destination: string): Promise<boolean> {
  try {
    const [sourceContent, destinationContent] = await Promise.all([
      readFile(source),
      readFile(destination)
    ]);
    return sourceContent.equals(destinationContent);
  } catch {
    return false;
  }
}

async function moveReference(sourceRoot: string, destinationRoot: string, reference: FileReference) {
  if (
    !uuidPattern.test(reference.orphanId) ||
    !uuidPattern.test(reference.storedName) ||
    !extensions.has(reference.extension)
  ) {
    throw new Error(`Invalid database file reference ${reference.storedName}.${reference.extension}`);
  }

  const source = safePath(sourceRoot, `${reference.storedName}.${reference.extension}`);
  const destinationDirectory = safePath(
    destinationRoot,
    "orphans",
    orphanUploadFolderName(reference),
    reference.kind
  );
  const destination = safePath(
    destinationDirectory,
    `${reference.storedName}.${reference.extension}`
  );
  await mkdir(destinationDirectory, { recursive: true });

  if (await destinationMatches(source, destination)) {
    await unlink(source);
    return "already-moved" as const;
  }

  try {
    await rename(source, destination);
    return "moved" as const;
  } catch (error) {
    const code = (error as NodeJS.ErrnoException).code;
    if (code === "ENOENT") {
      try {
        await readFile(destination);
        return "already-moved" as const;
      } catch {
        return "missing" as const;
      }
    }
    if (code !== "EXDEV") throw error;
    await copyFile(source, destination);
    await unlink(source);
    return "moved" as const;
  }
}

async function main() {
  const projectId = process.env.SUPABASE_PROJECT_ID?.trim();
  const url =
    process.env.NEXT_PUBLIC_SUPABASE_URL?.trim() ||
    (projectId ? `https://${projectId}.supabase.co` : null);
  const key = process.env.SUPABASE_SECRET_KEY?.trim();
  const configuredSource = process.env.UPLOAD_ROOT?.trim();
  if (!url || !key || !serverKeyIsValid(key)) {
    throw new Error("Set a valid Supabase URL and SUPABASE_SECRET_KEY before migrating uploads.");
  }
  if (!configuredSource || !path.isAbsolute(configuredSource)) {
    throw new Error("UPLOAD_ROOT must point to the existing flat upload directory.");
  }

  const sourceRoot = path.resolve(configuredSource);
  const destinationRoot = path.resolve(process.cwd(), "public", "uploads");
  const supabase = createClient(url, key, {
    auth: { autoRefreshToken: false, persistSession: false }
  });

  const [{ data: orphans, error: orphanError }, { data: documents, error: documentError }, { data: receipts, error: receiptError }] =
    await Promise.all([
      supabase.from("orphans").select("id,full_name,case_number").limit(10_000),
      supabase
        .from("orphan_documents")
        .select("orphan_id,file_assets(stored_name,extension)")
        .limit(10_000),
      supabase
        .from("transfer_receipts")
        .select("file_assets(stored_name,extension),orphan_transfers(orphan_id)")
        .limit(10_000)
    ]);
  if (orphanError || documentError || receiptError) {
    throw new Error(
      orphanError?.message ||
        documentError?.message ||
        receiptError?.message ||
        "Unable to inspect upload references."
    );
  }

  const orphanIdentities = new Map<string, OrphanUploadIdentity>();
  for (const orphan of orphans ?? []) {
    if (!uuidPattern.test(orphan.id)) throw new Error("Invalid orphan id in database.");
    const identity: OrphanUploadIdentity = {
      orphanId: orphan.id,
      orphanName: orphan.full_name,
      caseNumber: orphan.case_number
    };
    orphanIdentities.set(orphan.id, identity);
    const folderName = orphanUploadFolderName(identity);
    await Promise.all([
      mkdir(safePath(destinationRoot, "orphans", folderName, "documents"), { recursive: true }),
      mkdir(safePath(destinationRoot, "orphans", folderName, "receipts"), { recursive: true })
    ]);
  }

  const references = new Map<string, FileReference>();
  for (const document of documents ?? []) {
    const asset = one(
      document.file_assets as
        | { stored_name: string; extension: string }
        | { stored_name: string; extension: string }[]
        | null
    );
    if (!asset) continue;
    const orphan = orphanIdentities.get(document.orphan_id);
    if (!orphan) throw new Error(`Orphan ${document.orphan_id} is missing.`);
    const reference: FileReference = {
      ...orphan,
      storedName: asset.stored_name,
      extension: asset.extension,
      kind: "documents"
    };
    references.set(`${reference.storedName}.${reference.extension}`, reference);
  }
  for (const receipt of receipts ?? []) {
    const asset = one(
      receipt.file_assets as
        | { stored_name: string; extension: string }
        | { stored_name: string; extension: string }[]
        | null
    );
    const transfer = one(
      receipt.orphan_transfers as
        | { orphan_id: string }
        | { orphan_id: string }[]
        | null
    );
    if (!asset || !transfer) continue;
    const orphan = orphanIdentities.get(transfer.orphan_id);
    if (!orphan) throw new Error(`Orphan ${transfer.orphan_id} is missing.`);
    const reference: FileReference = {
      ...orphan,
      storedName: asset.stored_name,
      extension: asset.extension,
      kind: "receipts"
    };
    references.set(`${reference.storedName}.${reference.extension}`, reference);
  }

  const totals = { moved: 0, "already-moved": 0, missing: 0 };
  for (const reference of references.values()) {
    const result = await moveReference(sourceRoot, destinationRoot, reference);
    totals[result] += 1;
  }

  console.log(
    `Upload layout ready: ${orphans?.length ?? 0} orphan folders, ${totals.moved} moved, ${totals["already-moved"]} already moved, ${totals.missing} missing.`
  );
  if (totals.missing > 0) {
    throw new Error("Some database file references had no source or destination file.");
  }
}

main().catch((error: unknown) => {
  console.error(error instanceof Error ? error.message : "Upload migration failed.");
  process.exitCode = 1;
});
