import { randomUUID } from "node:crypto";
import { constants } from "node:fs";
import { mkdir, open, readFile, rename, unlink } from "node:fs/promises";
import { dirname, join } from "node:path";
import { dlopen, FFIType } from "bun:ffi";

export type AtomicWriteResult =
  | { state: "conflict" }
  | { state: "committed"; durability: "durable" }
  | { state: "committed"; durability: "indeterminate"; error: unknown };

export class TargetLockError extends Error {
  constructor(readonly code: "busy" | "unavailable", message: string) { super(message); }
}

/** Test-only hooks for deterministic races and post-rename durability failures. */
export interface AtomicWriteHooks {
  beforeCompare?: (target: string) => Promise<void> | void;
  syncDirectory?: (directory: string) => Promise<void>;
}

let hooks: AtomicWriteHooks = {};
export function setAtomicWriteHooksForTest(next: AtomicWriteHooks = {}): void {
  hooks = next;
}

async function sourceAt(path: string): Promise<string | null> {
  try {
    return await readFile(path, "utf8");
  } catch (error) {
    if ((error as NodeJS.ErrnoException).code === "ENOENT") return null;
    throw error;
  }
}

let flockImpl: ((fd: number, operation: number) => number) | undefined;
function flock(fd: number, operation: number): number {
  if (!flockImpl) {
    try {
      const libc = dlopen("libc.so.6", { flock: { args: [FFIType.i32, FFIType.i32], returns: FFIType.i32 } });
      flockImpl = libc.symbols.flock as (fd: number, operation: number) => number;
    } catch (error) {
      throw new TargetLockError("unavailable", `advisory lock unavailable: ${String(error)}`);
    }
  }
  return flockImpl(fd, operation);
}

const LOCK_EX = 2;
const LOCK_NB = 4;
const LOCK_UN = 8;
const O_NOFOLLOW = constants.O_NOFOLLOW ?? 0x10000;

function validateLock(info: { isFile(): boolean; uid: number; nlink: number; mode: number }): void {
  if (!info.isFile() || info.nlink !== 1 || (info.mode & 0o777) !== 0o600) {
    throw new TargetLockError("unavailable", "project color lock has unsafe metadata");
  }
  if (typeof process.getuid === "function" && info.uid !== process.getuid()) {
    throw new TargetLockError("unavailable", "project color lock is not owned by this user");
  }
}

/** Runs an entire transaction under a target-specific, cross-process advisory lock. */
export async function withTargetLock<T>(target: string, operation: () => Promise<T>, waitMs = 2_000): Promise<T> {
  const directory = dirname(target);
  await mkdir(directory, { recursive: true, mode: 0o700 });
  const lockPath = join(directory, `.${target.split("/").pop() ?? "config"}.lock`);
  let handle: Awaited<ReturnType<typeof open>> | undefined;
  let acquired = false;
  try {
    try {
      handle = await open(lockPath, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | O_NOFOLLOW, 0o600);
    } catch (error) {
      if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error;
      handle = await open(lockPath, constants.O_RDONLY | O_NOFOLLOW);
    }
    validateLock(await handle.stat());
    const deadline = Date.now() + waitMs;
    while (flock(handle.fd, LOCK_EX | LOCK_NB) !== 0) {
      if (Date.now() >= deadline) throw new TargetLockError("busy", "project color lock is busy");
      await Bun.sleep(25);
    }
    acquired = true;
  } catch (error) {
    await handle?.close().catch(() => undefined);
    if (error instanceof TargetLockError) throw error;
    throw new TargetLockError("unavailable", `unable to acquire project color lock: ${String(error)}`);
  }
  try {
    return await operation();
  } finally {
    if (acquired && handle) flock(handle.fd, LOCK_UN);
    await handle?.close().catch(() => undefined);
  }
}

/**
 * Replace a file through a synced temporary file.  A directory-sync error happens
 * after rename, so it is explicitly reported as a committed, indeterminate write.
 */
export async function atomicWrite(
  target: string,
  contents: string,
  options: { expectedSource?: string | null; temporaryPrefix?: string } = {},
): Promise<AtomicWriteResult> {
  const directory = dirname(target);
  await mkdir(directory, { recursive: true });
  const temporary = join(directory, `.${options.temporaryPrefix ?? "write"}-${randomUUID()}.tmp`);
  let handle: Awaited<ReturnType<typeof open>> | undefined;
  let renamed = false;
  try {
    handle = await open(temporary, "wx", 0o600);
    await handle.writeFile(contents, "utf8");
    await handle.sync();
    await handle.close();
    handle = undefined;

    if (options.expectedSource !== undefined) {
      await hooks.beforeCompare?.(target);
      if (await sourceAt(target) !== options.expectedSource) return { state: "conflict" };
    }

    await rename(temporary, target);
    renamed = true;
    try {
      if (hooks.syncDirectory) await hooks.syncDirectory(directory);
      else {
        const directoryHandle = await open(directory, "r");
        try { await directoryHandle.sync(); } finally { await directoryHandle.close(); }
      }
      return { state: "committed", durability: "durable" };
    } catch (error) {
      return { state: "committed", durability: "indeterminate", error };
    }
  } catch (error) {
    if (renamed) return { state: "committed", durability: "indeterminate", error };
    throw error;
  } finally {
    if (handle) await handle.close().catch(() => undefined);
    if (!renamed) await unlink(temporary).catch(() => undefined);
  }
}
