// Copyright 2018-2026 the Deno authors. MIT license.
import {
  O_APPEND,
  O_CREAT,
  O_DIRECTORY,
  O_EXCL,
  O_RDONLY,
  O_RDWR,
  O_SYNC,
  O_TRUNC,
  O_WRONLY,
} from "node:constants";
import { assertEquals, assertRejects, assertThrows, fail } from "@std/assert";
import { assertCallbackErrorUncaught } from "../_test_utils.ts";
import {
  closeSync,
  existsSync,
  open,
  openSync,
  readSync,
  writeFileSync,
  writeSync,
} from "node:fs";
import { open as openPromise } from "node:fs/promises";
import { join, parse } from "node:path";

const tempDir = parse(Deno.makeTempFileSync()).dir;

Deno.test({
  name: "ASYNC: open file",
  async fn() {
    const file = Deno.makeTempFileSync();
    let fd1: number;
    await new Promise<number>((resolve, reject) => {
      open(file, (err, fd) => {
        if (err) reject(err);
        resolve(fd);
      });
    })
      .then((fd) => {
        fd1 = fd;
      }, () => fail())
      .finally(() => closeSync(fd1));
  },
});

Deno.test({
  name: "SYNC: open file",
  fn() {
    const file = Deno.makeTempFileSync();
    const fd = openSync(file, "r");
    closeSync(fd);
  },
});

Deno.test({
  name: "open with string flag 'a'",
  fn() {
    const file = join(tempDir, "some_random_file");
    const fd = openSync(file, "a");
    assertEquals(typeof fd, "number");
    assertEquals(existsSync(file), true);
    closeSync(fd);
  },
});

Deno.test({
  name: "open with string flag 'ax'",
  fn() {
    const file = Deno.makeTempFileSync();
    assertThrows(
      () => {
        openSync(file, "ax");
      },
      Error,
      `EEXIST: file already exists, open '${file}'`,
    );
    Deno.removeSync(file);
  },
});

Deno.test({
  name: "open with string flag 'a+'",
  fn() {
    const file = join(tempDir, "some_random_file2");
    const fd = openSync(file, "a+");
    assertEquals(typeof fd, "number");
    assertEquals(existsSync(file), true);
    closeSync(fd);
  },
});

Deno.test({
  name: "open with string flag 'ax+'",
  fn() {
    const file = Deno.makeTempFileSync();
    assertThrows(
      () => {
        openSync(file, "ax+");
      },
      Error,
      `EEXIST: file already exists, open '${file}'`,
    );
    Deno.removeSync(file);
  },
});

Deno.test({
  name: "open with string flag 'as'",
  fn() {
    const file = join(tempDir, "some_random_file10");
    const fd = openSync(file, "as");
    assertEquals(existsSync(file), true);
    assertEquals(typeof fd, "number");
    closeSync(fd);
  },
});

Deno.test({
  name: "open with string flag 'as+'",
  fn() {
    const file = join(tempDir, "some_random_file10");
    const fd = openSync(file, "as+");
    assertEquals(existsSync(file), true);
    assertEquals(typeof fd, "number");
    closeSync(fd);
  },
});

Deno.test({
  name: "open with string flag 'r'",
  fn() {
    const file = join(tempDir, "some_random_file3");
    assertThrows(() => {
      openSync(file, "r");
    }, Error);
  },
});

Deno.test({
  name: "open with string flag 'r+'",
  fn() {
    const file = join(tempDir, "some_random_file4");
    assertThrows(() => {
      openSync(file, "r+");
    }, Error);
  },
});

Deno.test({
  name: "open with string flag 'w'",
  fn() {
    const file = Deno.makeTempFileSync();
    Deno.writeTextFileSync(file, "hi there");
    const fd = openSync(file, "w");
    assertEquals(typeof fd, "number");
    assertEquals(Deno.readTextFileSync(file), "");
    closeSync(fd);

    const file2 = join(tempDir, "some_random_file5");
    const fd2 = openSync(file2, "w");
    assertEquals(typeof fd2, "number");
    assertEquals(existsSync(file2), true);
    closeSync(fd2);
  },
});

Deno.test({
  name: "open with string flag 'wx'",
  fn() {
    const file = Deno.makeTempFileSync();
    Deno.writeTextFileSync(file, "hi there");
    const fd = openSync(file, "w");
    assertEquals(typeof fd, "number");
    assertEquals(Deno.readTextFileSync(file), "");
    closeSync(fd);

    const file2 = Deno.makeTempFileSync();
    assertThrows(
      () => {
        openSync(file2, "wx");
      },
      Error,
      `EEXIST: file already exists, open '${file2}'`,
    );
  },
});

Deno.test({
  name: "open with string flag 'w+'",
  fn() {
    const file = Deno.makeTempFileSync();
    Deno.writeTextFileSync(file, "hi there");
    const fd = openSync(file, "w+");
    assertEquals(typeof fd, "number");
    assertEquals(Deno.readTextFileSync(file), "");
    closeSync(fd);

    const file2 = join(tempDir, "some_random_file6");
    const fd2 = openSync(file2, "w+");
    assertEquals(typeof fd2, "number");
    assertEquals(existsSync(file2), true);
    closeSync(fd2);
  },
});

Deno.test({
  name: "open with string flag 'wx+'",
  fn() {
    const file = Deno.makeTempFileSync();
    assertThrows(
      () => {
        openSync(file, "wx+");
      },
      Error,
      `EEXIST: file already exists, open '${file}'`,
    );
    Deno.removeSync(file);
  },
});

Deno.test({
  name: "open with numeric flag `O_APPEND | O_CREAT | O_WRONLY` ('a')",
  fn() {
    const file = join(tempDir, "some_random_file");
    const fd = openSync(file, O_APPEND | O_CREAT | O_WRONLY);
    assertEquals(typeof fd, "number");
    assertEquals(existsSync(file), true);
    closeSync(fd);
  },
});

Deno.test({
  name:
    "open with numeric flag `O_APPEND | O_CREAT | O_WRONLY | O_EXCL` ('ax')",
  fn() {
    const file = Deno.makeTempFileSync();
    assertThrows(
      () => {
        openSync(file, O_APPEND | O_CREAT | O_WRONLY | O_EXCL);
      },
      Error,
      `EEXIST: file already exists, open '${file}'`,
    );
    Deno.removeSync(file);
  },
});

Deno.test({
  name: "open with numeric flag `O_APPEND | O_CREAT | O_RDWR` ('a+')",
  fn() {
    const file = join(tempDir, "some_random_file2");
    const fd = openSync(file, O_APPEND | O_CREAT | O_RDWR);
    assertEquals(typeof fd, "number");
    assertEquals(existsSync(file), true);
    closeSync(fd);
  },
});

Deno.test({
  name: "open with numeric flag `O_APPEND | O_CREAT | O_RDWR | O_EXCL` ('ax+')",
  fn() {
    const file = Deno.makeTempFileSync();
    assertThrows(
      () => {
        openSync(file, O_APPEND | O_CREAT | O_RDWR | O_EXCL);
      },
      Error,
      `EEXIST: file already exists, open '${file}'`,
    );
    Deno.removeSync(file);
  },
});

Deno.test({
  name:
    "open with numeric flag `O_APPEND | O_CREAT | O_WRONLY | O_SYNC` ('as')",
  fn() {
    const file = join(tempDir, "some_random_file10");
    const fd = openSync(file, O_APPEND | O_CREAT | O_WRONLY | O_SYNC);
    assertEquals(existsSync(file), true);
    assertEquals(typeof fd, "number");
    closeSync(fd);
  },
});

Deno.test({
  name: "open with numeric flag `O_APPEND | O_CREAT | O_RDWR | O_SYNC` ('as+')",
  fn() {
    const file = join(tempDir, "some_random_file10");
    const fd = openSync(file, O_APPEND | O_CREAT | O_RDWR | O_SYNC);
    assertEquals(existsSync(file), true);
    assertEquals(typeof fd, "number");
    closeSync(fd);
  },
});

Deno.test({
  name: "open with numeric flag `O_RDONLY` ('r')",
  fn() {
    const file = join(tempDir, "some_random_file3");
    assertThrows(() => {
      openSync(file, O_RDONLY);
    }, Error);
  },
});

Deno.test({
  name: "open with numeric flag `O_RDWR` ('r+')",
  fn() {
    const file = join(tempDir, "some_random_file4");
    assertThrows(() => {
      openSync(file, O_RDWR);
    }, Error);
  },
});

Deno.test({
  name: "open with numeric flag `O_TRUNC | O_CREAT | O_WRONLY` ('w')",
  fn() {
    const file = Deno.makeTempFileSync();
    Deno.writeTextFileSync(file, "hi there");
    const fd = openSync(file, O_TRUNC | O_CREAT | O_WRONLY);
    assertEquals(typeof fd, "number");
    assertEquals(Deno.readTextFileSync(file), "");
    closeSync(fd);

    const file2 = join(tempDir, "some_random_file5");
    const fd2 = openSync(file2, O_TRUNC | O_CREAT | O_WRONLY);
    assertEquals(typeof fd2, "number");
    assertEquals(existsSync(file2), true);
    closeSync(fd2);
  },
});

Deno.test({
  name: "open with numeric flag `O_TRUNC | O_CREAT | O_WRONLY | O_EXCL` ('wx')",
  fn() {
    const file = Deno.makeTempFileSync();
    Deno.writeTextFileSync(file, "hi there");
    const fd = openSync(file, "w");
    assertEquals(typeof fd, "number");
    assertEquals(Deno.readTextFileSync(file), "");
    closeSync(fd);

    const file2 = Deno.makeTempFileSync();
    assertThrows(
      () => {
        openSync(file2, O_TRUNC | O_CREAT | O_WRONLY | O_EXCL);
      },
      Error,
      `EEXIST: file already exists, open '${file2}'`,
    );
  },
});

Deno.test({
  name: "open with numeric flag `O_TRUNC | O_CREAT | O_RDWR` ('w+')",
  fn() {
    const file = Deno.makeTempFileSync();
    Deno.writeTextFileSync(file, "hi there");
    const fd = openSync(file, O_TRUNC | O_CREAT | O_RDWR);
    assertEquals(typeof fd, "number");
    assertEquals(Deno.readTextFileSync(file), "");
    closeSync(fd);

    const file2 = join(tempDir, "some_random_file6");
    const fd2 = openSync(file2, O_TRUNC | O_CREAT | O_RDWR);
    assertEquals(typeof fd2, "number");
    assertEquals(existsSync(file2), true);
    closeSync(fd2);
  },
});

Deno.test({
  name: "open with numeric flag `O_TRUNC | O_CREAT | O_RDWR | O_EXCL` ('wx+')",
  fn() {
    const file = Deno.makeTempFileSync();
    assertThrows(
      () => {
        openSync(file, O_TRUNC | O_CREAT | O_RDWR | O_EXCL);
      },
      Error,
      `EEXIST: file already exists, open '${file}'`,
    );
    Deno.removeSync(file);
  },
});

Deno.test("[std/node/fs] open callback isn't called twice if error is thrown", async () => {
  const tempFile = await Deno.makeTempFile();
  const importUrl = new URL("node:fs", import.meta.url);
  await assertCallbackErrorUncaught({
    prelude: `import { open } from ${JSON.stringify(importUrl)}`,
    invocation: `open(${JSON.stringify(tempFile)}, `,
    async cleanup() {
      await Deno.remove(tempFile);
    },
  });
});

Deno.test("SYNC: open file with flag set to 0 (readonly)", async () => {
  const file = Deno.makeTempFileSync();
  const fd = openSync(file, 0);
  closeSync(fd);
  await Deno.remove(file);
});

Deno.test("[std/node/fs] openSync with custom flag", {
  ignore: Deno.build.os === "windows",
}, async () => {
  const file = await Deno.makeTempFile();
  assertThrows(
    () => {
      // Should throw error if path is not a directory
      openSync(file, O_DIRECTORY as number);
    },
    Error,
    `ENOTDIR: not a directory, open '${file}'`,
  );
  await Deno.remove(file);
});

Deno.test("[std/node/fs] open with custom flag", {
  ignore: Deno.build.os === "windows",
}, async () => {
  const file = await Deno.makeTempFile();
  await assertRejects(
    async () => {
      // Should throw error if path is not a directory
      // `openPromise` uses `open` under the hood.
      await openPromise(file, O_DIRECTORY as number);
    },
    Error,
    `ENOTDIR: not a directory, open '${file}'`,
  );
  await Deno.remove(file);
});

let invalidFlag: number | undefined;
// On linux it refers to the `O_TMPFILE` constant in libc.
// It should throw EINVAL when it's not followed by `O_RDWR` or `O_WRONLY`.
// https://docs.rs/libc/latest/libc/constant.O_TMPFILE.html
if (Deno.build.os === "linux" || Deno.build.os === "android") {
  if (Deno.build.arch === "x86_64") {
    invalidFlag = 4_259_840;
  } else {
    invalidFlag = 4_210_688;
  }
} else if (Deno.build.os === "darwin") {
  // On macOS it's a random value that is not a valid flag.
  invalidFlag = 0x7FFFFFFF;
}

Deno.test("[std/node/fs] openSync throws on invalid flags", {
  ignore: Deno.build.os === "windows",
}, async () => {
  const file = await Deno.makeTempFile();
  assertThrows(
    () => {
      openSync(file, invalidFlag as number);
    },
    Error,
    `EINVAL: invalid argument, open '${file}'`,
  );
  await Deno.remove(file);
});

Deno.test("[std/node/fs] open throws on invalid flags", {
  ignore: Deno.build.os === "windows",
}, async () => {
  const file = await Deno.makeTempFile();
  await assertRejects(
    async () => {
      // `openPromise` uses `open` under the hood.
      await openPromise(file, invalidFlag as number);
    },
    Error,
    `EINVAL: invalid argument, open '${file}'`,
  );
  await Deno.remove(file);
});

Deno.test(
  "[std/node/fs] openSync: only enable read permission when a custom flag is not followed by file access flags",
  {
    ignore: Deno.build.os === "windows",
  },
  async () => {
    const path = await Deno.makeTempFile();
    writeFileSync(path, "Hello, world!");

    const fd = openSync(path, O_SYNC);
    readSync(fd, new Uint8Array(1), 0, 1, 0);
    assertThrows(
      () => {
        writeSync(fd, "This should fail");
      },
      Error,
    );

    closeSync(fd);
    await Deno.remove(path);
  },
);

Deno.test(
  "[std/node/fs] open: only enable read permission when a custom flag is not followed by file access flags",
  {
    ignore: Deno.build.os === "windows",
  },
  async () => {
    const path = await Deno.makeTempFile();
    writeFileSync(path, "Hello, world!");

    const fileHandle = await openPromise(path, O_SYNC);
    readSync(fileHandle.fd, new Uint8Array(1), 0, 1, 0);
    assertThrows(
      () => {
        writeSync(fileHandle.fd, "This should fail");
      },
      Error,
    );

    await fileHandle.close();
    await Deno.remove(path);
  },
);

Deno.test(
  "[std/node/fs] open: only enable write permission",
  async () => {
    const path = await Deno.makeTempFile();
    const fileHandle = await openPromise(path, O_WRONLY);

    writeSync(fileHandle.fd, "Hello, world!");
    assertThrows(
      () => {
        readSync(fileHandle.fd, new Uint8Array(1), 0, 1, 0);
      },
      Error,
    );

    await fileHandle.close();
    await Deno.remove(path);
  },
);

Deno.test(
  "[std/node/fs] openSync: only enable write permission",
  async () => {
    const path = await Deno.makeTempFile();
    const fd = openSync(path, O_WRONLY);

    writeSync(fd, "Hello, world!");
    assertThrows(
      () => {
        readSync(fd, new Uint8Array(1), 0, 1, 0);
      },
      Error,
    );

    closeSync(fd);
    await Deno.remove(path);
  },
);

Deno.test({
  name:
    "open with numeric flag `O_RDONLY | O_CREAT` creates file and opens for reading",
  fn() {
    const file = join(tempDir, "o_rdonly_o_creat_test");
    // Ensure file doesn't exist
    try {
      Deno.removeSync(file);
    } catch {
      // ignore
    }
    assertEquals(existsSync(file), false);

    // O_RDONLY | O_CREAT should create the file if it doesn't exist,
    // then open it for reading only (matching POSIX / Node.js behavior)
    const fd = openSync(file, O_RDONLY | O_CREAT);
    assertEquals(typeof fd, "number");
    assertEquals(existsSync(file), true);

    // The file should be empty
    const buf = new Uint8Array(10);
    const bytesRead = readSync(fd, buf, 0, 10, 0);
    assertEquals(bytesRead, 0);

    closeSync(fd);
    Deno.removeSync(file);
  },
});

Deno.test({
  name:
    "open with numeric flag `O_RDONLY | O_CREAT` opens existing file for reading",
  fn() {
    const file = Deno.makeTempFileSync();
    Deno.writeTextFileSync(file, "hello");

    const fd = openSync(file, O_RDONLY | O_CREAT);
    assertEquals(typeof fd, "number");

    // Should be able to read the existing content
    const buf = new Uint8Array(10);
    const bytesRead = readSync(fd, buf, 0, 10, 0);
    assertEquals(bytesRead, 5);

    closeSync(fd);
    Deno.removeSync(file);
  },
});
