import {
  copyFileSync,
  existsSync,
  mkdtempSync,
  readFileSync,
  renameSync,
  rmSync,
  unlinkSync,
  writeFileSync,
} from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { type AtomicWriteFs, atomicWriteSync } from "../src/core/atomic-write.js";

const realFs: AtomicWriteFs = {
  writeFileSync,
  chmodSync: () => {},
  renameSync,
  copyFileSync,
  unlinkSync,
};

describe("atomicWriteSync", () => {
  let dir: string;
  beforeEach(() => {
    dir = mkdtempSync(join(tmpdir(), "atomic-write-"));
  });
  afterEach(() => {
    rmSync(dir, { recursive: true, force: true });
  });

  it("writes via rename on the happy path and leaves no tmp behind", () => {
    const target = join(dir, "config.json");
    const tmp = `${target}.tmp`;
    atomicWriteSync(target, '{"a":1}', tmp);
    expect(readFileSync(target, "utf8")).toBe('{"a":1}');
    expect(existsSync(tmp)).toBe(false);
  });

  it("falls back to copy on EXDEV (OneDrive / NTFS reparse points, #1738)", () => {
    const target = join(dir, "config.json");
    const tmp = `${target}.tmp`;
    let renameAttempted = false;
    const fs: AtomicWriteFs = {
      ...realFs,
      renameSync: () => {
        renameAttempted = true;
        const err = new Error("EXDEV: cross-device link not permitted") as NodeJS.ErrnoException;
        err.code = "EXDEV";
        err.errno = -4037;
        throw err;
      },
    };
    atomicWriteSync(target, '{"a":1}', tmp, 0o600, fs);
    expect(renameAttempted).toBe(true);
    expect(readFileSync(target, "utf8")).toBe('{"a":1}');
    expect(existsSync(tmp)).toBe(false);
  });

  it("rethrows non-EXDEV rename errors and cleans up tmp", () => {
    const target = join(dir, "config.json");
    const tmp = `${target}.tmp`;
    const fs: AtomicWriteFs = {
      ...realFs,
      renameSync: () => {
        const err = new Error("EACCES: permission denied") as NodeJS.ErrnoException;
        err.code = "EACCES";
        throw err;
      },
    };
    expect(() => atomicWriteSync(target, '{"a":1}', tmp, 0o600, fs)).toThrow(/EACCES/);
    expect(existsSync(tmp)).toBe(false);
  });
});