import path from 'path';
import fs from 'fs/promises';

/**
 * Validates that a file path is safe and within the allowed base directory
 * Prevents path traversal attacks via:
 * - Null byte injection
 * - Directory traversal sequences (../)
 * - Symlink attacks
 *
 * @param basePath - The allowed base directory (must be absolute)
 * @param relativePath - The relative path to validate
 * @returns The validated absolute path
 * @throws Error if path validation fails
 */
export async function validateFilePath(
  basePath: string,
  relativePath: string
): Promise<string> {
  // Check for null bytes
  if (relativePath.includes('\0')) {
    throw new Error('Invalid file path: contains null byte');
  }

  // Resolve to absolute path
  const fullPath = path.resolve(basePath, relativePath);

  // Verify it's still within base directory
  const resolvedBase = path.resolve(basePath);
  if (!fullPath.startsWith(resolvedBase)) {
    throw new Error('Invalid file path: directory traversal detected');
  }

  // Check for symlinks (resolve real path)
  try {
    const realPath = await fs.realpath(fullPath);
    if (!realPath.startsWith(resolvedBase)) {
      throw new Error('Invalid file path: symlink traversal detected');
    }
  } catch (err: any) {
    if (err.code === 'ENOENT') {
      // File doesn't exist yet, verify parent directory
      const parent = path.dirname(fullPath);
      try {
        const realParent = await fs.realpath(parent);
        if (!realParent.startsWith(resolvedBase)) {
          throw new Error('Invalid file path: parent traversal detected');
        }
      } catch (parentErr: any) {
        if (parentErr.code === 'ENOENT') {
          // Parent also doesn't exist, just verify the path is within base
          // This is already checked above
        } else {
          throw parentErr;
        }
      }
    } else {
      throw err;
    }
  }

  return fullPath;
}
