// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
import { assert, assertEquals } from "./test_util.ts";

// just a hack to get a body object
// deno-lint-ignore no-explicit-any
function buildBody(body: any, headers?: Headers): Body {
  const stub = new Request("http://foo/", {
    body: body,
    headers,
    method: "POST",
  });
  return stub as Body;
}

const intArrays = [
  Int8Array,
  Int16Array,
  Int32Array,
  Uint8Array,
  Uint16Array,
  Uint32Array,
  Uint8ClampedArray,
  Float32Array,
  Float64Array,
];
Deno.test(async function arrayBufferFromByteArrays() {
  const buffer = new TextEncoder().encode("ahoyhoy8").buffer;

  for (const type of intArrays) {
    const body = buildBody(new type(buffer));
    const text = new TextDecoder("utf-8").decode(await body.arrayBuffer());
    assertEquals(text, "ahoyhoy8");
  }
});

//FormData
Deno.test(
  { permissions: { net: true } },
  async function bodyMultipartFormData() {
    const response = await fetch(
      "http://localhost:4545/multipart_form_data.txt",
    );
    assert(response.body instanceof ReadableStream);

    const text = await response.text();

    const body = buildBody(text, response.headers);

    const formData = await body.formData();
    assert(formData.has("field_1"));
    assertEquals(formData.get("field_1")!.toString(), "value_1 \r\n");
    assert(formData.has("field_2"));
  },
);

// FormData: non-ASCII names and filenames
Deno.test(
  { permissions: { net: true } },
  async function bodyMultipartFormDataNonAsciiNames() {
    const boundary = "----01230123";
    const payload = [
      `--${boundary}`,
      `Content-Disposition: form-data; name="文字"`,
      "",
      "文字",
      `--${boundary}`,
      `Content-Disposition: form-data; name="file"; filename="文字"`,
      "Content-Type: application/octet-stream",
      "",
      "",
      `--${boundary}--`,
    ].join("\r\n");

    const body = buildBody(
      new TextEncoder().encode(payload),
      new Headers({
        "Content-Type": `multipart/form-data; boundary=${boundary}`,
      }),
    );

    const formData = await body.formData();
    assert(formData.has("文字"));
    assertEquals(formData.get("文字"), "文字");
    assert(formData.has("file"));
    assert(formData.get("file") instanceof File);
    assertEquals((formData.get("file") as File).name, "文字");
  },
);

// FormData: non-ASCII names and filenames roundtrip
Deno.test(
  { permissions: { net: true } },
  async function bodyMultipartFormDataNonAsciiRoundtrip() {
    const inFormData = new FormData();
    inFormData.append("文字", "文字");
    inFormData.append("file", new File([], "文字"));

    const body = buildBody(inFormData);

    const formData = await body.formData();
    assert(formData.has("文字"));
    assertEquals(formData.get("文字"), "文字");
    assert(formData.has("file"));
    assert(formData.get("file") instanceof File);
    assertEquals((formData.get("file") as File).name, "文字");
  },
);

Deno.test(
  { permissions: { net: true } },
  async function bodyURLEncodedFormData() {
    const response = await fetch(
      "http://localhost:4545/subdir/form_urlencoded.txt",
    );
    assert(response.body instanceof ReadableStream);

    const text = await response.text();

    const body = buildBody(text, response.headers);

    const formData = await body.formData();
    assert(formData.has("field_1"));
    assertEquals(formData.get("field_1")!.toString(), "Hi");
    assert(formData.has("field_2"));
    assertEquals(formData.get("field_2")!.toString(), "<Deno>");
  },
);

Deno.test({ permissions: {} }, async function bodyURLSearchParams() {
  const body = buildBody(new URLSearchParams({ hello: "world" }));

  const text = await body.text();
  assertEquals(text, "hello=world");
});

Deno.test(async function bodyArrayBufferMultipleParts() {
  const parts: Uint8Array[] = [];
  let size = 0;
  for (let i = 0; i <= 150000; i++) {
    const part = new Uint8Array([1]);
    parts.push(part);
    size += part.length;
  }

  let offset = 0;
  const stream = new ReadableStream({
    pull(controller) {
      // parts.shift() takes forever: https://github.com/denoland/deno/issues/5259
      const chunk = parts[offset++];
      if (!chunk) return controller.close();
      controller.enqueue(chunk);
    },
  });

  const body = buildBody(stream);
  assertEquals((await body.arrayBuffer()).byteLength, size);
});

// https://github.com/denoland/deno/issues/20793
Deno.test(
  { permissions: { net: true } },
  async function bodyMultipartFormDataMultipleHeaders() {
    const boundary = "----formdata-polyfill-0.970665446687947";
    const payload = [
      "------formdata-polyfill-0.970665446687947",
      'Content-Disposition: form-data; name="x"; filename="blob"',
      "Content-Length: 1",
      "Content-Type: application/octet-stream",
      "last-modified: Wed, 04 Oct 2023 20:28:45 GMT",
      "",
      "y",
      "------formdata-polyfill-0.970665446687947--",
    ].join("\r\n");

    const body = buildBody(
      new TextEncoder().encode(payload),
      new Headers({
        "Content-Type": `multipart/form-data; boundary=${boundary}`,
      }),
    );

    const formData = await body.formData();
    const file = formData.get("x");
    assert(file instanceof File);
    const text = await file.text();
    assertEquals(text, "y");
    assertEquals(file.size, 1);
  },
);