import type { FastifyInstance } from 'fastify';
import { readdir, stat } from 'node:fs/promises';
import { existsSync } from 'node:fs';
import { homedir } from 'node:os';
import { join, dirname, resolve } from 'node:path';
import type { FsEntry, FsListResponse } from '@arboretum/shared';

/**
 * Navigation du système de fichiers pour le sélecteur de dossier côté web.
 * Ne renvoie QUE des noms de sous-dossiers (jamais de contenu de fichier) ; authentifié
 * par le hook preValidation global comme tout /api/**. Un utilisateur authentifié dispose
 * déjà d'un terminal (RCE par conception) : lister des dossiers n'élargit pas le modèle de menace.
 *
 * GET /api/v1/fs/list?path=<abs>&markRepos=1&showHidden=1
 * - path absent → home de l'utilisateur
 * - markRepos   → annote les sous-dossiers qui sont des dépôts git (présence d'un `.git`)
 * - showHidden  → inclut les dotfiles (masqués par défaut)
 */
export function registerFsRoutes(app: FastifyInstance): void {
  app.get('/api/v1/fs/list', async (req, reply) => {
    const q = req.query as { path?: string; markRepos?: string; showHidden?: string };
    const raw = q.path && q.path.length > 0 ? q.path : homedir();
    if (!raw.startsWith('/')) {
      return reply.status(400).send({ error: { code: 'BAD_PATH', message: 'path must be absolute' } });
    }
    // resolve() normalise et clampe à la racine : aucun échappement d'arborescence via `..`.
    const abs = resolve(raw);

    let st;
    try {
      st = await stat(abs);
    } catch (err) {
      const code = (err as NodeJS.ErrnoException).code;
      if (code === 'ENOENT') return reply.status(404).send({ error: { code: 'NOT_FOUND', message: `No such directory: ${abs}` } });
      if (code === 'EACCES') return reply.status(403).send({ error: { code: 'FORBIDDEN', message: `Permission denied: ${abs}` } });
      return reply.status(400).send({ error: { code: 'BAD_PATH', message: (err as Error).message } });
    }
    if (!st.isDirectory()) {
      return reply.status(400).send({ error: { code: 'NOT_A_DIRECTORY', message: `Not a directory: ${abs}` } });
    }

    const markRepos = q.markRepos === '1' || q.markRepos === 'true';
    const showHidden = q.showHidden === '1' || q.showHidden === 'true';

    let dirents;
    try {
      dirents = await readdir(abs, { withFileTypes: true });
    } catch (err) {
      const code = (err as NodeJS.ErrnoException).code;
      if (code === 'EACCES') return reply.status(403).send({ error: { code: 'FORBIDDEN', message: `Permission denied: ${abs}` } });
      return reply.status(400).send({ error: { code: 'BAD_PATH', message: (err as Error).message } });
    }

    const entries: FsEntry[] = [];
    for (const d of dirents) {
      if (!showHidden && d.name.startsWith('.')) continue;
      let isDir = d.isDirectory();
      // symlink éventuel vers un dossier : résolu par stat (tolérant aux liens cassés).
      if (!isDir && d.isSymbolicLink()) {
        try {
          isDir = (await stat(join(abs, d.name))).isDirectory();
        } catch {
          isDir = false;
        }
      }
      if (!isDir) continue;
      const full = join(abs, d.name);
      const entry: FsEntry = { name: d.name, path: full };
      if (markRepos && existsSync(join(full, '.git'))) entry.isRepo = true;
      entries.push(entry);
    }
    entries.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase()));

    const res: FsListResponse = {
      path: abs,
      parent: abs === '/' ? null : dirname(abs),
      home: homedir(),
      entries,
    };
    return reply.send(res);
  });
}
