/**
 * <run_code> 액션 — 코딩 피드백 루프("생성→실행→에러 확인→수정")의 실행 축 검증.
 * stdout/stderr/종료코드가 internal system 메시지로 재주입되어 자동 후속 턴을
 * 트리거하는 계약 + 샌드박스(워크스페이스 밖 차단)·확장자 화이트리스트 검증.
 */
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { applyRunCodeActions } from '../src/agent/actions/runCode';
import { buildSyntaxFixInstruction } from '../src/features/selfReflector/selfReflectorExecution';

// vscode 는 jest.config.js 의 moduleNameMapper(tests/mocks/vscode.js)가 전역 mock 으로 처리한다.

let root: string;
beforeAll(() => {
    root = fs.mkdtempSync(path.join(os.tmpdir(), 'astra-runcode-'));
});
afterAll(() => {
    try { fs.rmSync(root, { recursive: true, force: true }); } catch { /* noop */ }
});

function makeCtx(aiMessage: string): any {
    return { aiMessage, rootPath: root, report: [], chatHistory: [] };
}

describe('applyRunCodeActions', () => {
    it('정상 실행: stdout 이 exit 0 과 함께 재주입된다 (node)', async () => {
        fs.writeFileSync(path.join(root, 'ok.js'), "console.log('HELLO_' + (40 + 2));");
        const ctx = makeCtx('<run_code path="ok.js"/>');
        await applyRunCodeActions(ctx);
        expect(ctx.report.some((r: string) => r.includes('run_code OK'))).toBe(true);
        expect(ctx.chatHistory[0].internal).toBe(true);
        expect(ctx.chatHistory[0].content).toContain('exit 0');
        expect(ctx.chatHistory[0].content).toContain('HELLO_42');
    });

    it('실행 에러: stderr + 수정→실행→확인 루프 지시가 재주입된다', async () => {
        fs.writeFileSync(path.join(root, 'boom.js'), "throw new Error('KABOOM');");
        const ctx = makeCtx('<run_code path="boom.js"/>');
        await applyRunCodeActions(ctx);
        expect(ctx.report.some((r: string) => r.includes('FAIL'))).toBe(true);
        expect(ctx.chatHistory[0].content).toContain('[Result of run_code — ERROR]');
        expect(ctx.chatHistory[0].content).toContain('KABOOM');
        expect(ctx.chatHistory[0].content).toContain('<edit_file>');
    });

    it('워크스페이스 밖 경로는 차단된다', async () => {
        const ctx = makeCtx('<run_code path="../../etc/hosts.js"/>');
        await applyRunCodeActions(ctx);
        expect(ctx.report.some((r: string) => r.includes('blocked'))).toBe(true);
        expect(ctx.chatHistory).toEqual([]);
    });

    it('지원하지 않는 확장자는 run_command 안내와 함께 거절된다', async () => {
        fs.writeFileSync(path.join(root, 'note.md'), '# hi');
        const ctx = makeCtx('<run_code path="note.md"/>');
        await applyRunCodeActions(ctx);
        expect(ctx.chatHistory[0].content).toContain('UNSUPPORTED');
        expect(ctx.chatHistory[0].content).toContain('run_command');
    });

    it('존재하지 않는 파일이면 list_files 안내를 재주입한다', async () => {
        const ctx = makeCtx('<run_code path="ghost.js"/>');
        await applyRunCodeActions(ctx);
        expect(ctx.chatHistory[0].content).toContain('존재하지 않습니다');
    });

    it('한 턴 실행 한도(2회)를 초과하면 건너뛴다', async () => {
        fs.writeFileSync(path.join(root, 'a.js'), "console.log('a');");
        const tags = '<run_code path="a.js"/>'.repeat(3);
        const ctx = makeCtx(tags);
        await applyRunCodeActions(ctx);
        const results = ctx.chatHistory.filter((m: any) => m.content.includes('[Result of run_code]'));
        expect(results.length).toBe(2);
        expect(ctx.report.some((r: string) => r.includes('한도'))).toBe(true);
    });
});

describe('buildSyntaxFixInstruction — 문법 오류 자가수정 루프 메시지', () => {
    it('실패 라인들과 edit_file 수정 지시를 담는다', () => {
        const msg = buildSyntaxFixInstruction(['❌ node --check FAIL: a.js — SyntaxError: x']);
        expect(msg).toContain('SYNTAX ERRORS');
        expect(msg).toContain('a.js');
        expect(msg).toContain('<edit_file>');
        expect(msg).toContain('수동 수정을 요구하지 마라');
    });
});
