import { realpathSync } from "node:fs";
import { homedir } from "node:os";
import { basename, dirname, join, resolve, sep } from "node:path";
import { config } from "../config.js";

/**
 * Gather allowed filesystem roots for path validation.
 * Includes: user home directory, process cwd, config-provided paths,
 * and optionally active session cwds.
 */
export function getAllowedRoots(getSessionCwds?: () => string[]): string[] {
  const roots = new Set<string>();

  roots.add(resolve(homedir()));
  roots.add(resolve(process.cwd()));

  for (const p of config.allowedPaths) {
    roots.add(resolve(p));
  }

  if (getSessionCwds) {
    for (const cwd of getSessionCwds()) {
      if (cwd) roots.add(resolve(cwd));
    }
  }

  return Array.from(roots);
}

/**
 * Check whether a resolved path falls within any of the allowed roots.
 * The path must either equal a root exactly or be a child of it.
 */
export function isPathAllowed(resolvedPath: string, allowedRoots: string[]): boolean {
  const normalized = resolve(resolvedPath);
  return allowedRoots.some((root) => {
    return normalized === root || normalized.startsWith(root + sep);
  });
}

export interface FsAccessOk {
  ok: true;
  resolved: string;
}

export interface FsAccessDenied {
  ok: false;
  error: string;
}

/**
 * Validate filesystem access for a given raw path.
 * Uses realpathSync() to follow symlinks before checking boundaries.
 * For paths that don't exist yet (new file creation), resolves the parent directory.
 * Returns the resolved real path when allowed, or an error message.
 */
export function validateFsAccess(
  rawPath: string,
  allowedRoots: string[],
): FsAccessOk | FsAccessDenied {
  const resolved = resolve(rawPath);

  let realPath: string;
  try {
    // Follow symlinks to get the true filesystem path
    realPath = realpathSync(resolved);
  } catch {
    // Path doesn't exist yet (e.g., creating a new file) — resolve the parent
    const parentDir = dirname(resolved);
    try {
      const realParent = realpathSync(parentDir);
      realPath = join(realParent, basename(resolved));
    } catch {
      return {
        ok: false,
        error: `Access denied: parent directory "${parentDir}" does not exist`,
      };
    }
  }

  if (isPathAllowed(realPath, allowedRoots)) {
    return { ok: true, resolved: realPath };
  }

  return {
    ok: false,
    error: `Access denied: path "${realPath}" is outside allowed directories`,
  };
}
