import { describe, it, expect } from 'vitest';
import { assembleTestFile, assembleWithMap } from '../src/core/irScaffold';
import { validateTestFile, extractCases } from '../src/core/pipeline';
import type { FeatureNode } from '../src/core/types';

const feature: FeatureNode = {
  id: 'F1',
  name: "User's Identity",
  description: 'identity management',
  parentId: null,
  deps: [],
  files: ['src/user_manager.ts'],
  testFile: 'tests/f1-user.test.ts',
  edgeCases: ['empty username'],
  exports: [{ file: 'src/user_manager.ts', symbol: 'createUser' }],
  envVars: [],
  stubs: [{ path: 'src/user_manager.ts', content: '' }],
};

describe('IR scaffolding', () => {
  it('assembled suite passes the deterministic gate by construction', () => {
    const content = assembleTestFile(
      feature,
      [{ from: 'src/user_manager', symbols: ['createUser'] }],
      [
        { name: 'creates a user', kind: 'behavior', body: 'const u = createUser("a");\nexpect(u.name).toBe("a");' },
        { name: 'empty username rejected', kind: 'edge', body: 'expect(() => createUser("")).toThrow();' },
      ],
    );
    expect(validateTestFile(content)).toEqual([]);
  });

  it('prefixes kinds, normalizes modules, handles await and quotes', () => {
    const content = assembleTestFile(
      feature,
      [
        { from: '../src/user_manager.ts', symbols: ['createUser'] },
        { from: 'src/user_manager', symbols: ['createUser'] }, // dupe module+symbol
        { from: 'vitest', symbols: ['describe'] },             // rejected: scaffolded
        { from: 'src/x', symbols: ['bad name!'] },             // rejected: not an identifier
      ],
      [
        { name: 'async path', kind: 'behavior', body: 'const r = await createUser("x");\nexpect(r).toBeTruthy();' },
        { name: "quote 'n roll", kind: 'edge', body: 'expect(1).toBe(1);' },
        { name: 'integration: already prefixed', kind: 'integration', body: 'expect(1).toBe(1);' },
      ],
    );
    // single deduped import line, vitest/bad ones dropped
    expect(content.match(/^import \{ createUser \} from '\.\.\/src\/user_manager';$/m)).toBeTruthy();
    expect(content).not.toContain("from 'vitest';\nimport { describe }");
    expect(content).not.toContain('bad name!');
    // await body → async callback
    expect(content).toContain("it('async path', async () => {");
    // kind prefixes applied once, quotes escaped
    expect(content).toContain("it('edge: quote \\'n roll'");
    expect(content).toContain("it('integration: already prefixed'");
    expect(content).not.toContain('integration: integration:');
    // still gate-clean and cases extractable
    expect(validateTestFile(content)).toEqual([]);
    const cases = extractCases('F1', content);
    expect(cases.map(c => c.kind).sort()).toEqual(['behavior', 'edge', 'integration']);
  });

  it('line-map points every case body line to the owning case (multi-line bodies)', () => {
    const cases = [
      { name: 'first', kind: 'behavior' as const, body: 'const a = 1;\nconst b = 2;\nexpect(a + b).toBe(3);' },
      { name: 'second', kind: 'edge' as const, body: 'expect(() => createUser("")).toThrow();' },
      { name: 'third', kind: 'integration' as const, body: 'const u = await createUser("x");\nexpect(u).toBeTruthy();' },
    ];
    const map = assembleWithMap(feature, [{ from: 'src/user_manager', symbols: ['createUser'] }], cases);
    const lines = map.content.split('\n');

    // Every case range must actually bracket that case's it() and body text,
    // and never overlap the next case — this is what per-case repair relies on.
    map.caseRanges.forEach((cr, i) => {
      expect(lines[cr.start - 1]).toContain(`it(`);            // start line is the it(...)
      expect(lines[cr.end - 1].trim()).toBe('});');            // end line closes the callback
      // a distinctive token from each body must fall inside its own range
      const token = ['const a = 1;', 'toThrow', 'await createUser'][i];
      const inside = lines.slice(cr.start - 1, cr.end).some(l => l.includes(token));
      expect(inside).toBe(true);
      if (i > 0) expect(cr.start).toBeGreaterThan(map.caseRanges[i - 1].end); // no overlap
    });
    // import block maps to the vitest + impl-import lines only
    expect(map.importRange.start).toBe(1);
    expect(lines[map.importRange.end - 1]).toContain("from '../src/user_manager'");
  });
});
