import { execFile } from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
import { validatePath } from '../../security';
import { detectPython } from './calculate';
import type { HandlerContext } from './types';

/**
 * <run_code> — 작성한 코드를 실제 실행해 결과를 확인하는 액션 (ACTION 17).
 *
 * 배경: 소형 모델 추론 강화 2축 "코딩 피드백 루프". run_command 는 터미널로만
 * 보내고 stdout 을 회수하지 못해 모델이 실행 결과를 볼 수 없었다. 이 액션은
 * 워크스페이스 안의 파일을 인터프리터로 직접 실행하고 stdout/stderr/종료코드를
 * 대화 컨텍스트로 재주입한다 → "생성 → 실행 → 에러 확인 → 수정" 루프가
 * 모델 주도로 성립한다 (calculate 와 동일한 재주입 메커니즘).
 *
 * 안전장치: validatePath 로 워크스페이스 밖 실행 차단, 15초 타임아웃,
 * 회당 2회 상한, 출력 4천자 상한, 지원 확장자 화이트리스트(.py/.js/.mjs/.cjs).
 */

const RUN_TIMEOUT_MS = 15_000;
const MAX_RUNS_PER_TURN = 2;
const MAX_OUTPUT_CHARS = 4_000;

function execFileP(cmd: string, args: string[], cwd: string): Promise<{ code: number; stdout: string; stderr: string; timedOut: boolean }> {
    return new Promise((resolve) => {
        execFile(cmd, args, { cwd, timeout: RUN_TIMEOUT_MS, maxBuffer: 1024 * 1024 }, (err: any, stdout, stderr) => {
            resolve({
                code: err ? (typeof err.code === 'number' ? err.code : 1) : 0,
                stdout: String(stdout || ''),
                stderr: String(stderr || ''),
                timedOut: !!err?.killed,
            });
        });
    });
}

const cap = (s: string) => (s.length > MAX_OUTPUT_CHARS ? s.slice(0, MAX_OUTPUT_CHARS) + '\n… (truncated)' : s);

export async function applyRunCodeActions(ctx: HandlerContext): Promise<void> {
    const { aiMessage, rootPath, report } = ctx;
    const runRegex = /<run_code\s+path=['"]?([^'">\s]+)['"]?\s*\/?>(?:<\/run_code>)?/gi;
    let match: RegExpExecArray | null;
    let ran = 0;

    while ((match = runRegex.exec(aiMessage)) !== null) {
        if (ran >= MAX_RUNS_PER_TURN) {
            report.push(`⚠️ run_code skipped: 한 턴 실행 한도(${MAX_RUNS_PER_TURN}회) 초과`);
            break;
        }
        const relPath = match[1].trim();
        ran++;
        try {
            const absPath = validatePath(rootPath, relPath);
            if (!fs.existsSync(absPath)) {
                report.push(`❌ run_code: 파일 없음 — ${relPath}`);
                ctx.chatHistory.push({
                    role: 'system', internal: true,
                    content: `[Result of run_code — ERROR]\n파일이 존재하지 않습니다: ${relPath}. 경로를 확인하거나 <list_files> 로 실제 파일명을 확인하십시오.`,
                });
                continue;
            }

            const ext = path.extname(absPath).toLowerCase();
            let cmd: string;
            let args: string[];
            if (ext === '.py') {
                const py = await detectPython();
                if (!py) {
                    report.push('❌ run_code: Python 미설치');
                    ctx.chatHistory.push({
                        role: 'system', internal: true,
                        content: '[Result of run_code — UNAVAILABLE]\nPython 이 설치되어 있지 않아 .py 파일을 실행할 수 없습니다. 실행 없이 코드 리뷰로만 검증하십시오.',
                    });
                    continue;
                }
                cmd = py; args = [absPath];
            } else if (ext === '.js' || ext === '.mjs' || ext === '.cjs') {
                cmd = 'node'; args = [absPath];
            } else {
                report.push(`⚠️ run_code: 지원하지 않는 확장자(${ext || '없음'}) — ${relPath}`);
                ctx.chatHistory.push({
                    role: 'system', internal: true,
                    content: `[Result of run_code — UNSUPPORTED]\n<run_code> 는 .py/.js/.mjs/.cjs 만 실행합니다. ${relPath} 는 <run_command> 로 터미널에서 실행하십시오 (단, 그 경우 출력은 회수되지 않습니다).`,
                });
                continue;
            }

            const rel = path.relative(rootPath, absPath) || relPath;
            const res = await execFileP(cmd, args, rootPath);
            const outBlock = [
                res.stdout.trim() ? `--- stdout ---\n${cap(res.stdout.trim())}` : '(stdout 없음)',
                res.stderr.trim() ? `--- stderr ---\n${cap(res.stderr.trim())}` : '',
            ].filter(Boolean).join('\n');

            if (res.timedOut) {
                report.push(`❌ run_code timeout: ${rel}`);
                ctx.chatHistory.push({
                    role: 'system', internal: true,
                    content: `[Result of run_code — TIMEOUT]\n${rel} 실행이 ${RUN_TIMEOUT_MS / 1000}초 안에 끝나지 않아 중단되었습니다.\n${outBlock}\n무한 루프·대기 입력 여부를 점검해 <edit_file> 로 수정한 뒤 다시 실행하십시오.`,
                });
            } else if (res.code === 0) {
                report.push(`▶️ run_code OK: ${rel}`);
                ctx.chatHistory.push({
                    role: 'system', internal: true,
                    content: `[Result of run_code] ${rel} — exit 0\n${outBlock}\n실행 결과가 의도와 일치하는지 확인하고, 이 실제 출력을 근거로 답하십시오 (추측 금지).`,
                });
            } else {
                report.push(`❌ run_code FAIL(exit ${res.code}): ${rel}`);
                ctx.chatHistory.push({
                    role: 'system', internal: true,
                    content: `[Result of run_code — ERROR] ${rel} — exit ${res.code}\n${outBlock}\n에러를 분석해 <edit_file> 로 코드를 수정한 뒤, 다시 <run_code> 로 실행해 확인하십시오 (수정→실행→확인 루프).`,
                });
            }
        } catch (err: any) {
            report.push(`❌ run_code blocked: ${err.message}`);
        }
    }
}
