import { mkdir, readFile, readdir, stat, writeFile } from 'node:fs/promises';
import path from 'node:path';

import { env } from './env';
import { assertContainedRealPath } from './path-containment';
import {
  ensureUserContainer,
  inspectUserBoxStatus,
  stopWorkspaceContainer,
  userContainerName,
} from './runtime/docker';
import { resetBox } from './user-container';

// Matches FileTreeNode in apps/next/src/components/agent-workspace/types.ts.
type FileTreeNode = {
  name: string;
  path: string;
  type: 'file' | 'directory';
  children?: FileTreeNode[];
};

// Per-user box home layout, shared by the HTTP routes and the terminal WS bridge
// so path derivation lives in exactly one place (must match worker.ts).
export const boxHomePaths = (username: string) => ({
  homeDir: path.resolve(env.workdir, 'homes', username),
  containerHome: path.posix.join('/home', username),
});

export type BoxStatus = {
  running: boolean;
  image: string | null;
  startedAt: string | null;
  memoryLimitBytes: number | null;
  containerName: string;
};

export const getBoxStatus = async (username: string): Promise<BoxStatus> => {
  const status = await inspectUserBoxStatus(username);
  return { ...status, containerName: userContainerName(username) };
};

export const startBox = async (username: string): Promise<void> => {
  const { homeDir, containerHome } = boxHomePaths(username);
  await ensureUserContainer({ username, workdir: homeDir, containerHome });
};

export const stopBox = async (username: string): Promise<void> => {
  await stopWorkspaceContainer(userContainerName(username));
  resetBox(username);
};

export const restartBox = async (username: string): Promise<void> => {
  await stopBox(username);
  await startBox(username);
};

// Directories never surfaced in the box file tree — VCS internals, package
// caches and build output that would bloat (or hang) the walk.
const BOX_TREE_IGNORE = new Set([
  '.git',
  'node_modules',
  '.cache',
  '.npm',
  '.bun',
  '.cargo',
  'dist',
  'build',
  '.next',
]);

// Home-relative paths (POSIX) that are always skipped regardless of depth.
const BOX_TREE_IGNORE_PATHS = new Set(['.local/share/Trash']);

// Guard against a huge home hanging the request; stop descending past this.
const BOX_TREE_MAX_NODES = 5000;

// Realpath-checked absolute path inside the user's home. Rejects lexical `..`
// escapes and symlinks that resolve outside home (reuses the Phase-1
// containment helper); for writes, also rejects a symlinked final target.
const safeHomePath = (
  homeDir: string,
  relPath: string,
  opts: { forWrite?: boolean } = {},
): Promise<string> =>
  assertContainedRealPath(homeDir, relPath, { ...opts, label: 'home' });

export const listBoxTree = async (username: string): Promise<FileTreeNode> => {
  const { homeDir } = boxHomePaths(username);
  let count = 0;

  const buildChildren = async (
    absoluteDir: string,
    relativeDir: string,
  ): Promise<FileTreeNode[]> => {
    const entries = await readdir(absoluteDir);
    const nodes = await Promise.all(
      entries
        .sort((a, b) => a.localeCompare(b))
        .map(async (entry): Promise<FileTreeNode | null> => {
          const relativePath = relativeDir ? `${relativeDir}/${entry}` : entry;
          if (
            BOX_TREE_IGNORE.has(entry) ||
            BOX_TREE_IGNORE_PATHS.has(relativePath)
          ) {
            return null;
          }
          if (count >= BOX_TREE_MAX_NODES) return null;
          count += 1;
          const absolutePath = path.join(absoluteDir, entry);
          const stats = await stat(absolutePath).catch(() => null);
          if (!stats) return null;
          if (stats.isDirectory()) {
            return {
              name: entry,
              path: relativePath,
              type: 'directory',
              children: await buildChildren(absolutePath, relativePath),
            };
          }
          return { name: entry, path: relativePath, type: 'file' };
        }),
    );
    return nodes.filter((node): node is FileTreeNode => Boolean(node));
  };

  return {
    name: '~',
    path: '',
    type: 'directory',
    children: await buildChildren(homeDir, ''),
  };
};

export const readBoxFile = async (
  username: string,
  relPath: string,
): Promise<string> => {
  const { homeDir } = boxHomePaths(username);
  const target = await safeHomePath(homeDir, relPath);
  return await readFile(target, 'utf8');
};

export const writeBoxFile = async (
  username: string,
  relPath: string,
  content: string,
): Promise<{ success: true }> => {
  const { homeDir } = boxHomePaths(username);
  const target = await safeHomePath(homeDir, relPath, { forWrite: true });
  await mkdir(path.dirname(target), { recursive: true });
  await writeFile(target, content);
  return { success: true };
};
