import { type Request, type Response } from 'express';
import { join, resolve, sep } from 'path';

export function getLocalWorkspacePath(worktreeDir: string | undefined, taskId: number): string {
  const base = worktreeDir ?? '/tmp/maestro/workspaces';
  return join(base, 'local', String(taskId));
}

export function ensurePathWithin(baseDir: string, requestedPath: string): string {
  const resolvedBase = resolve(baseDir);
  const resolvedPath = resolve(baseDir, requestedPath);
  if (!resolvedPath.startsWith(resolvedBase + sep) && resolvedPath !== resolvedBase) {
    throw new Error('Path escapes workspace');
  }
  return resolvedPath;
}

/**
 * Harden a response that serves user/agent-authored workspace bytes.
 *
 * Workspace files can hold attacker-influenced HTML/SVG (an agent driven by a
 * poisoned web page, or another user's shared task). Without these headers the
 * browser renders such a file inline on the app origin and any embedded
 * `<script>` runs with the viewer's session — stored XSS, and via the
 * unauthenticated share endpoint it crosses to other users. `Content-Security-Policy:
 * sandbox` forces an opaque origin and disables script execution while still
 * letting the in-app preview render the markup; `nosniff` stops the browser from
 * sniffing a text/* file back into executable HTML.
 */
export function setUntrustedFileResponseHeaders(res: Response): void {
  res.setHeader('X-Content-Type-Options', 'nosniff');
  res.setHeader('Content-Security-Policy', 'sandbox');
}

/** True when the error came from ensurePathWithin's traversal guard. */
export function isPathEscapeError(err: unknown): boolean {
  return err instanceof Error && err.message === 'Path escapes workspace';
}

export function serializeLocalFileEntry(relativePath: string, name: string, isDirectory: boolean, size: number, mtime: Date) {
  return {
    name,
    path: relativePath ? `${relativePath}/${name}` : name,
    kind: isDirectory ? 'directory' : 'file',
    size,
    modifiedAt: mtime.toISOString(),
  };
}

export function getOwnerFilter(req: Request): { ownerId?: string } {
  if (!req.user) return {};
  if (req.user.role === 'admin') return {};
  return { ownerId: req.user.id };
}

export function checkTaskOwnership(req: Request, res: Response, task: { ownerId?: string | null } | null): boolean {
  if (!task) { res.status(404).json({ error: 'Task not found' }); return false; }
  if (req.user && req.user.role !== 'admin' && task.ownerId !== req.user?.id) {
    res.status(404).json({ error: 'Task not found' });
    return false;
  }
  return true;
}

type TaskLike = {
  ownerId?: string | null;
  visibility?: 'private' | 'org' | 'public' | null;
  visibilityScopeOrgId?: string | null;
};

// Read-side permission check honoring the full visibility model.
// Writes should continue to use checkTaskOwnership (owner-or-admin only).
export function canViewTask(req: Request, res: Response, task: TaskLike | null): boolean {
  if (!task) { res.status(404).json({ error: 'Task not found' }); return false; }
  const user = req.user as Express.User | undefined;
  if (!user) return true;
  if (user.role === 'admin') return true;
  if (task.ownerId && task.ownerId === user.id) return true;
  if (task.visibility === 'public') return true;
  if (task.visibility === 'org' && task.visibilityScopeOrgId && user.orgIds?.includes(task.visibilityScopeOrgId)) return true;
  res.status(404).json({ error: 'Task not found' });
  return false;
}
