import { BASE } from './env';
/**
 * Node-side multipart upload helper. Lets adversarial specs post arbitrary
 * bytes with arbitrary `Content-Type` claims to /api/v1/upload without
 * driving the UI. Crucial for MIME-spoofing, oversize, polyglot, and
 * filename-injection tests.
 *
 * Field shape matches [backend/src/handlers/upload.rs]:
 *   - file       (binary; carries filename + content_type in the part headers)
 *   - caption    (text, optional)
 *   - hashtags   (CSV text, optional)
 */
// Node 22+ ships FormData and Blob as globals — no import needed.

export type UploadOptions = {
  filename?: string;
  contentType?: string;
  caption?: string;
  hashtags?: string;
};

export async function uploadRaw(
  token: string,
  body: Uint8Array | Buffer,
  opts: UploadOptions = {}
) {
  const form = new FormData();
  const blob = new Blob([body as any], { type: opts.contentType ?? 'application/octet-stream' });
  form.append('file', blob as any, opts.filename ?? 'upload.bin');
  if (opts.caption !== undefined) form.append('caption', opts.caption);
  if (opts.hashtags !== undefined) form.append('hashtags', opts.hashtags);
  return fetch(`${BASE}/api/v1/upload`, {
    method: 'POST',
    headers: { Authorization: `Bearer ${token}` },
    body: form as any,
  });
}

/** Convenience: read a fixture from disk and upload it. */
export async function uploadFile(token: string, path: string, opts: UploadOptions = {}) {
  const { readFile } = await import('node:fs/promises');
  const body = await readFile(path);
  return uploadRaw(token, body, opts);
}

/** Tiny valid JPEG header — magic bytes only, useful for "claim image but is N MB of zeros" tests. */
export const JPEG_MAGIC = new Uint8Array([
  0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46,
]);
/** Tiny valid PNG magic bytes. */
export const PNG_MAGIC = new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
/** ELF header — the magic bytes of a Linux executable. `infer` reports `application/x-executable`. */
export const ELF_MAGIC = new Uint8Array([0x7f, 0x45, 0x4c, 0x46]);

/**
 * Upload whose request body PAUSES mid-stream until you release it.
 *
 * This exists to make the release-vs-upload race deterministic instead of hoped-for. The bug it
 * guards is a TOCTOU: `upload` checks `uploads_locked_at` BEFORE reading the body (minutes, for a
 * big video) and commits the row afterwards — so a release landing in between used to let the photo
 * commit AFTER the export snapshot, leaving it in the live feed but permanently absent from the
 * keepsake.
 *
 * Racing N normal uploads against a release can't prove anything: if they all happen to commit
 * first (or all get rejected), the assertion passes on broken code too. Here the server is *stuck*
 * mid-body with its pre-flight check already passed, so the release provably lands inside the
 * window. Await `checkPassed`, fire the release, then `finish()`.
 */
export function uploadPausedMidStream(
  token: string,
  head: Buffer,
  tail: Buffer,
  opts: UploadOptions = {}
): { checkPassed: Promise<void>; finish: () => void; response: Promise<Response> } {
  const boundary = '----eventsnapPausedBoundary' + Math.random().toString(16).slice(2);
  const filename = opts.filename ?? 'paused.jpg';
  const contentType = opts.contentType ?? 'image/jpeg';

  const preamble = Buffer.from(
    `--${boundary}\r\n` +
      `Content-Disposition: form-data; name="file"; filename="${filename}"\r\n` +
      `Content-Type: ${contentType}\r\n\r\n`
  );
  const epilogue = Buffer.from(`\r\n--${boundary}--\r\n`);

  let releaseBody: () => void;
  const gate = new Promise<void>((r) => (releaseBody = r));
  let markCheckPassed: () => void;
  const checkPassed = new Promise<void>((r) => (markCheckPassed = r));

  const body = new ReadableStream<Uint8Array>({
    async pull(controller) {
      controller.enqueue(new Uint8Array(preamble));
      controller.enqueue(new Uint8Array(head));
      // The server has now read the part headers and the first bytes — which means it is PAST its
      // pre-flight lock check and is sitting in the body loop. This is the window.
      markCheckPassed();
      await gate;
      controller.enqueue(new Uint8Array(tail));
      controller.enqueue(new Uint8Array(epilogue));
      controller.close();
    },
  });

  const response = fetch(`${BASE}/api/v1/upload`, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${token}`,
      'Content-Type': `multipart/form-data; boundary=${boundary}`,
    },
    body,
    // Node's fetch requires this for a streaming request body.
    duplex: 'half',
  } as RequestInit & { duplex: 'half' });

  return { checkPassed, finish: () => releaseBody(), response };
}
