import "server-only";
import type { Readable } from "node:stream";
import { z } from "zod";
import { MAX_DOCUMENT_BYTES } from "./application-schema";
import { UploadRejected, type UploadStorage } from "./application-upload";
type SignedOptions = {
  version: "v4";
  action: "write";
  expires: number;
  contentType: string;
  extensionHeaders: Record<string, string>;
};
type FilePort = {
  getSignedUrl(options: SignedOptions): Promise<[string]>;
  getMetadata(): Promise<readonly [unknown, ...unknown[]]>;
  createReadStream(options: { decompress: false }): Readable;
};
export interface UploadBucket {
  file(path: string, options?: { generation: string }): FilePort;
}
const metadataSchema = z.object({
  size: z
    .string()
    .regex(/^\d+$/)
    .transform(Number)
    .pipe(z.number().int().min(1).max(MAX_DOCUMENT_BYTES)),
  contentType: z.enum(["application/pdf", "image/jpeg", "image/png"]),
  generation: z
    .string()
    .regex(/^[1-9][0-9]{0,19}$/)
    .refine((value) => Number.isSafeInteger(Number(value))),
  contentEncoding: z.undefined().optional(),
});
function validatePath(path: string) {
  const prefix = "applications/uploads/";
  if (
    !path.startsWith(prefix) ||
    !z.uuid().safeParse(path.slice(prefix.length)).success
  )
    throw new UploadRejected();
}

export function firebaseUploadStorage(bucket: UploadBucket): UploadStorage {
  return {
    signUpload: async (path, expiresAt, headers) => {
      validatePath(path);
      const { "Content-Type": contentType, ...extensionHeaders } = headers;
      try {
        const [url] = await bucket
          .file(path)
          .getSignedUrl({
            version: "v4",
            action: "write",
            expires: expiresAt,
            contentType,
            extensionHeaders,
          });
        return url;
      } catch {
        throw new UploadRejected();
      }
    },
    read: async (path, signal) => {
      validatePath(path);
      if (signal.aborted) throw new UploadRejected();
      try {
        const [raw] = await bucket.file(path).getMetadata();
        const metadata = metadataSchema.safeParse(raw);
        if (!metadata.success || signal.aborted) throw new UploadRejected();
        const stream = bucket
          .file(path, { generation: metadata.data.generation })
          .createReadStream({ decompress: false });
        const abort = () => {
          stream.destroy(new UploadRejected());
        };
        signal.addEventListener("abort", abort, { once: true });
        stream.once("close", () => signal.removeEventListener("abort", abort));
        stream.on("error", () => {});
        const body = (async function* () {
          try {
            for await (const rawChunk of stream) {
              const chunk: unknown = rawChunk;
              if (!(chunk instanceof Uint8Array) || signal.aborted)
                throw new UploadRejected();
              yield chunk;
            }
          } finally {
            signal.removeEventListener("abort", abort);
            stream.destroy();
          }
        })();
        return { ...metadata.data, body };
      } catch {
        throw new UploadRejected();
      }
    },
  };
}
