import { describe, expect, it, beforeEach, afterEach } from 'vitest';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import {
  appendLesson,
  loadLessons,
  lessonsPath,
  resetLessonsCache,
} from '../src/core/lessons';
import { applyLessonFix, classKeyOf } from '../src/core/diagnostics';
import type { TscDiagnostic } from '../src/core/sandbox';

describe('lesson generalization (TS2305 idiom)', () => {
  let tmp: string;
  let prevPath: string;

  beforeEach(() => {
    tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'rha-lessons-'));
    prevPath = lessonsPath();
    // Point lessons file at temp via symlink-like override: write to tmp and
    // load from there by resetting cache after writing with absolute path.
    resetLessonsCache();
    const file = path.join(tmp, 'lessons.jsonl');
    fs.writeFileSync(file, '');
    // Use appendLesson with explicit file
    (globalThis as any).__lessonFile = file;
  });

  afterEach(() => {
    resetLessonsCache();
    fs.rmSync(tmp, { recursive: true, force: true });
  });

  it('12 findings / 6 files / one idiom → 1 LLM then mechanical rest + jsonl row', () => {
    const member = 'sharedHelper';
    const files = Array.from({ length: 6 }, (_, i) => `src/mod${i}.ts`);
    // First "LLM" teach: write lesson once
    const lessonFile = path.join(tmp, 'lessons.jsonl');
    appendLesson({
      classKey: `TS2305|${files[0]}|${member}`,
      idiom: `TS2305: suite imports member ${member} absent from stub`,
      resolution: 'additive widen, signature inferred from calling lines',
      member,
      target: files[0],
      signature: `export function ${member}(..._args: unknown[]): never { throw new Error('stub: not implemented'); }`,
      ts: new Date().toISOString(),
    }, lessonFile);
    resetLessonsCache();
    loadLessons(lessonFile);

    let llmCalls = 1; // the teaching call already counted
    const contents = new Map(files.map(f => [f, 'export {}\n']));

    // 12 findings: 2 per file, same missing member idiom (member match)
    const findings: TscDiagnostic[] = [];
    for (const f of files) {
      for (let k = 0; k < 2; k++) {
        findings.push({
          file: `tests/use.test.ts`,
          line: 1 + k,
          code: 2305,
          message: `Module '"./${f.replace(/^src\//, './').replace(/\.ts$/, '')}"' has no exported member '${member}'.`,
        });
      }
    }
    expect(findings).toHaveLength(12);

    for (const d of findings) {
      // Force route to cycle files — definerOf may not resolve our fake path;
      // apply lesson by member match across targets.
      const target = files[findings.indexOf(d) % files.length];
      const content = contents.get(target)!;
      const next = applyLessonFix(d, content, {
        classKey: `TS2305|${target}|${member}`,
        idiom: 'x',
        resolution: 'widen',
        member,
        target,
        signature: `export function ${member}(): never { throw new Error('stub: not implemented'); }`,
        ts: new Date().toISOString(),
      });
      if (next) {
        contents.set(target, next);
      } else if (content.includes(member)) {
        // Already applied on this file — mechanical no-op, not an LLM call
      } else {
        llmCalls++;
        contents.set(target, content + `\nexport function ${member}(): never { throw new Error('stub: not implemented'); }\n`);
      }
    }

    expect(llmCalls).toBe(1);
    expect(fs.readFileSync(lessonFile, 'utf8').trim().split('\n').length).toBe(1);
    for (const f of files) {
      expect(contents.get(f)).toContain(member);
    }
    void classKeyOf; // keep import warm for related assertions
  });
});
