import { describe, expect, it, afterEach } from 'vitest';
import { mkdtempSync, mkdirSync, rmSync, symlinkSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { scanForRepos } from '../src/core/repo-scanner.js';

const dirs: string[] = [];
afterEach(() => {
  for (const d of dirs.splice(0)) rmSync(d, { recursive: true, force: true });
});

function tmpRoot(): string {
  const d = mkdtempSync(join(tmpdir(), 'arb-scan-'));
  dirs.push(d);
  return d;
}
// un « repo » pour le scanner = un dossier contenant `.git` (le scanner ne lance jamais git).
function makeRepo(...segs: string[]): void {
  mkdirSync(join(...segs, '.git'), { recursive: true });
}

const limits = { maxDepth: 6, maxRepos: 2000 };

describe('scanForRepos', () => {
  it('trouve un repo simple', async () => {
    const root = tmpRoot();
    makeRepo(root, 'proj');
    const { paths, truncated } = await scanForRepos([root], limits);
    expect(paths).toEqual([join(root, 'proj')]);
    expect(truncated).toBe(false);
  });

  it('ne descend pas dans un repo trouvé (sous-repo ignoré)', async () => {
    const root = tmpRoot();
    makeRepo(root, 'a');
    makeRepo(root, 'a', 'sub'); // imbriqué : doit être ignoré
    const { paths } = await scanForRepos([root], limits);
    expect(paths).toEqual([join(root, 'a')]);
  });

  it('descend dans une racine qui est elle-même un repo (workspace + sous-repos)', async () => {
    const root = tmpRoot();
    makeRepo(root); // la racine fournie contient elle-même un .git (cas WebstormProjects)
    makeRepo(root, 'a');
    makeRepo(root, 'b');
    const { paths } = await scanForRepos([root], limits);
    // la racine ET ses dépôts internes sont découverts (la racine n'arrête pas le scan)
    expect([...paths].sort()).toEqual([root, join(root, 'a'), join(root, 'b')].sort());
  });

  it('ignore node_modules et les dotdirs', async () => {
    const root = tmpRoot();
    makeRepo(root, 'node_modules', 'pkg');
    makeRepo(root, '.hidden', 'x');
    makeRepo(root, 'real');
    const { paths } = await scanForRepos([root], limits);
    expect(paths).toEqual([join(root, 'real')]);
  });

  it('respecte maxDepth', async () => {
    const root = tmpRoot();
    makeRepo(root, 'a', 'b', 'c', 'deep'); // repo à profondeur 4
    const shallow = await scanForRepos([root], { maxDepth: 2, maxRepos: 2000 });
    expect(shallow.paths).toEqual([]);
    const deep = await scanForRepos([root], { maxDepth: 4, maxRepos: 2000 });
    expect(deep.paths).toEqual([join(root, 'a', 'b', 'c', 'deep')]);
  });

  it('ne suit pas les symlinks (cycle terminé, pas de doublon)', async () => {
    const root = tmpRoot();
    makeRepo(root, 'proj');
    try {
      symlinkSync(root, join(root, 'loop')); // cycle vers la racine
    } catch {
      /* symlink non autorisé sous certains CI : le test reste valide sans le lien */
    }
    const { paths } = await scanForRepos([root], limits);
    expect(paths).toEqual([join(root, 'proj')]);
  });

  it('tronque à maxRepos', async () => {
    const root = tmpRoot();
    makeRepo(root, 'r1');
    makeRepo(root, 'r2');
    makeRepo(root, 'r3');
    const { paths, truncated } = await scanForRepos([root], { maxDepth: 6, maxRepos: 2 });
    expect(truncated).toBe(true);
    expect(paths.length).toBeLessThanOrEqual(2);
  });

  it('ignore une racine inexistante sans lever', async () => {
    const { paths } = await scanForRepos(['/nope/does/not/exist'], limits);
    expect(paths).toEqual([]);
  });

  it('scanne plusieurs racines', async () => {
    const r1 = tmpRoot();
    const r2 = tmpRoot();
    makeRepo(r1, 'one');
    makeRepo(r2, 'two');
    const { paths } = await scanForRepos([r1, r2], limits);
    expect([...paths].sort()).toEqual([join(r1, 'one'), join(r2, 'two')].sort());
  });
});
