/**
 * scan.test.ts — Code extraction regex + tree scan against a temp fixture.
 */
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { mkdtemp, rm, mkdir, writeFile } from 'node:fs/promises';
import path from 'node:path';
import os from 'node:os';
import { extractCodes, scanTree, scan, sectionFolderToCode } from '../scan.js';

// ---------------------------------------------------------------------------
// extractCodes
// ---------------------------------------------------------------------------

describe('extractCodes', () => {
  it('extracts a heading code with its title', () => {
    const content = '### UC-CRM-PIPELINE-OPPORTUNITES-001 — Créer une opportunité\n';
    const codes = extractCodes(content, '/tmp/use-case.md');
    expect(codes).toHaveLength(1);
    expect(codes[0].full).toBe('UC-CRM-PIPELINE-OPPORTUNITES-001');
    expect(codes[0].kind).toBe('UC');
    expect(codes[0].app).toBe('CRM');
    expect(codes[0].mod).toBe('PIPELINE');
    expect(codes[0].sec).toBe('OPPORTUNITES');
    expect(codes[0].num).toBe('001');
    expect(codes[0].title).toBe('Créer une opportunité');
  });

  it('extracts a body cite (no heading) with no title', () => {
    const content = '- **Règles liées** : BR-CRM-PIPELINE-OPPORTUNITES-003\n';
    const codes = extractCodes(content, '/tmp/use-case.md');
    expect(codes).toHaveLength(1);
    expect(codes[0].kind).toBe('BR');
    expect(codes[0].title).toBeUndefined();
  });

  it('extracts multiple codes from the same paragraph', () => {
    const content =
      '- Voir UC-A-B-C-001 et UC-A-B-C-002 et SCR-A-B-C-001 pour le détail.\n';
    const codes = extractCodes(content, '/tmp/x.md');
    expect(codes.map((c) => c.full)).toEqual([
      'UC-A-B-C-001',
      'UC-A-B-C-002',
      'SCR-A-B-C-001',
    ]);
  });

  it('ignores non-matching strings', () => {
    const content = 'See UC- or UC-XXX or UC-XX-YY (incomplete codes are not codes).';
    const codes = extractCodes(content, '/tmp/x.md');
    expect(codes).toHaveLength(0);
  });
});

// ---------------------------------------------------------------------------
// sectionFolderToCode
// ---------------------------------------------------------------------------

describe('sectionFolderToCode', () => {
  it('uppercases and replaces dashes with underscores', () => {
    expect(sectionFolderToCode('exchange-history')).toBe('EXCHANGE_HISTORY');
    expect(sectionFolderToCode('opportunites')).toBe('OPPORTUNITES');
  });
});

// ---------------------------------------------------------------------------
// scanTree + scan — fixture-based
// ---------------------------------------------------------------------------

describe('scanTree (fixture)', () => {
  let tmp: string;

  beforeEach(async () => {
    tmp = await mkdtemp(path.join(os.tmpdir(), 'reconcile-scan-'));
    // Tree: CRM/PIPELINE/{opportunites,clients}/index.md
    await mkdir(path.join(tmp, 'CRM'), { recursive: true });
    await writeFile(path.join(tmp, 'CRM', 'index.md'), '<!-- app -->\n');
    await mkdir(path.join(tmp, 'CRM', 'PIPELINE'), { recursive: true });
    await writeFile(path.join(tmp, 'CRM', 'PIPELINE', 'index.md'), '<!-- module -->\n');
    await mkdir(path.join(tmp, 'CRM', 'PIPELINE', 'opportunites'), { recursive: true });
    await writeFile(
      path.join(tmp, 'CRM', 'PIPELINE', 'opportunites', 'index.md'),
      '<!-- section -->\n',
    );
    await mkdir(path.join(tmp, 'CRM', 'PIPELINE', 'clients'), { recursive: true });
    await writeFile(
      path.join(tmp, 'CRM', 'PIPELINE', 'clients', 'index.md'),
      '<!-- section -->\n',
    );
  });

  afterEach(async () => {
    await rm(tmp, { recursive: true, force: true });
  });

  it('discovers all sections under modules', async () => {
    const { existingSections, existingModules } = await scanTree(tmp);
    expect(Array.from(existingSections).sort()).toEqual([
      'CRM/PIPELINE/CLIENTS',
      'CRM/PIPELINE/OPPORTUNITES',
    ]);
    expect(Array.from(existingModules)).toEqual(['CRM/PIPELINE']);
  });

  it('skips _audit / _plan metadata folders', async () => {
    await mkdir(path.join(tmp, 'CRM', 'PIPELINE', '_audit'), { recursive: true });
    await writeFile(
      path.join(tmp, 'CRM', 'PIPELINE', '_audit', 'index.md'),
      '<!-- audit -->\n',
    );
    const { existingSections } = await scanTree(tmp);
    expect(Array.from(existingSections).sort()).toEqual([
      'CRM/PIPELINE/CLIENTS',
      'CRM/PIPELINE/OPPORTUNITES',
    ]);
  });
});

describe('scan (fixture with use-case.md)', () => {
  let tmp: string;

  beforeEach(async () => {
    tmp = await mkdtemp(path.join(os.tmpdir(), 'reconcile-scan-uc-'));
    await mkdir(path.join(tmp, 'CRM', 'PIPELINE', 'opportunites'), { recursive: true });
    await writeFile(path.join(tmp, 'CRM', 'index.md'), '<!-- app -->\n');
    await writeFile(path.join(tmp, 'CRM', 'PIPELINE', 'index.md'), '<!-- module -->\n');
    await writeFile(
      path.join(tmp, 'CRM', 'PIPELINE', 'opportunites', 'index.md'),
      '<!-- section -->\n',
    );
    await writeFile(
      path.join(tmp, 'CRM', 'PIPELINE', 'opportunites', 'use-case.md'),
      [
        '### UC-CRM-PIPELINE-OPPORTUNITES-001 — Créer',
        '- **Acteur principal** : BA-001-AC-001',
        '',
        '### UC-CRM-PIPELINE-LEGACY-001 — Stale',
        '- **Acteur principal** : BA-001-AC-002',
        '',
      ].join('\n'),
    );
  });

  afterEach(async () => {
    await rm(tmp, { recursive: true, force: true });
  });

  it('produces a referencedSections set that includes a ghost', async () => {
    const result = await scan(tmp);
    expect(result.existingSections.has('CRM/PIPELINE/OPPORTUNITES')).toBe(true);
    expect(result.referencedSections.has('CRM/PIPELINE/OPPORTUNITES')).toBe(true);
    expect(result.referencedSections.has('CRM/PIPELINE/LEGACY')).toBe(true);
    expect(result.existingSections.has('CRM/PIPELINE/LEGACY')).toBe(false);
  });
});
