/**
 * Task Eval Harness — 업무 산출물 골든셋 자동 채점 (Self Evaluation v1).
 *
 * Self-Evolving OS 마스터 플랜 Phase 3 / Track 3-4. "성장세를 숫자로 증명" 의 핵심:
 * 같은 골든셋을 버전마다 돌려 점수 추이를 비교한다 (검색 평가 하니스가 recall@1
 * 37.5%→75% 를 증명한 것과 같은 방법론을 업무 산출물에 적용).
 *
 * 골든셋: <brain>/.astra/eval/tasks/<task>.golden.jsonl
 *   한 줄 = {"id","query","sourceFile","expectedElements":[label...],"reference","notes"}
 *
 * v1 채점은 결정론적 (LLM-judge 는 후속 증분):
 *   - 요소 커버리지: expectedElements 의 detectPatterns 매치율 (requirementGraph 어휘 재사용)
 *   - 정직성: "(확인 필요)" 류 표시 사용 여부 (지어내기 대신 모름 인정 — T1)
 *   - 길이·구조: 섹션 헤딩 수
 * LLM 호출(생성)은 주입(generate) — 하니스 자체는 순수, 테스트 가능.
 */

import * as fs from 'fs';
import * as path from 'path';
import { DEFAULT_TASK_REQUIREMENTS, TaskRequirement } from './requirementGraph';

export const TASK_GOLDEN_DIR = path.join('.astra', 'eval', 'tasks');

export interface TaskGoldenRecord {
    id: string;
    query: string;
    sourceFile: string;
    meetingTopic?: string;
    expectedElements: string[];
    reference: string;
    notes?: string;
}

export interface TaskGoldenLoadResult {
    records: TaskGoldenRecord[];
    parseErrors: number;
    sourcePath: string;
}

/** 골든셋 로드 — jsonl, `//` 주석·빈 줄 무시, 깨진 줄 카운트만. */
export function loadTaskGoldenSet(brainPath: string, taskFileBase = 'meeting-minutes'): TaskGoldenLoadResult {
    const sourcePath = path.join(brainPath, TASK_GOLDEN_DIR, `${taskFileBase}.golden.jsonl`);
    const result: TaskGoldenLoadResult = { records: [], parseErrors: 0, sourcePath };
    if (!fs.existsSync(sourcePath)) return result;
    const lines = fs.readFileSync(sourcePath, 'utf8').split('\n');
    for (const line of lines) {
        const t = line.trim();
        if (!t || t.startsWith('//')) continue;
        try {
            const obj = JSON.parse(t);
            if (obj && typeof obj.id === 'string' && typeof obj.query === 'string' && Array.isArray(obj.expectedElements)) {
                result.records.push(obj as TaskGoldenRecord);
            } else {
                result.parseErrors++;
            }
        } catch {
            result.parseErrors++;
        }
    }
    return result;
}

/** 요소 label → detectPatterns 매핑 (requirementGraph 정의 재사용, 못 찾으면 label 리터럴). */
function patternsForLabel(label: string, requirements: TaskRequirement[]): RegExp {
    for (const req of requirements) {
        for (const el of req.elements) {
            if (el.label === label) return new RegExp(el.detectPatterns.join('|'), 'iu');
        }
    }
    // 정의에 없는 커스텀 요소 — label 자체를 리터럴 매치 (정규식 특수문자 escape).
    return new RegExp(label.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'iu');
}

export interface TaskScore {
    recordId: string;
    /** 요소 커버리지 0~1. */
    coverageRate: number;
    covered: string[];
    missing: string[];
    /** "(확인 필요)" 류 정직성 마커 사용 수 (지어내기 방지 신호). */
    honestyMarkers: number;
    /** `##` 섹션 헤딩 수 (구조화 정도). */
    sectionCount: number;
    /** 출력 길이 (chars). */
    answerLength: number;
    /** 생성 실패 시 에러 메시지. */
    error?: string;
}

export function scoreTaskAnswer(
    answer: string,
    record: TaskGoldenRecord,
    requirements: TaskRequirement[] = DEFAULT_TASK_REQUIREMENTS,
): TaskScore {
    const covered: string[] = [];
    const missing: string[] = [];
    for (const label of record.expectedElements) {
        if (patternsForLabel(label, requirements).test(answer)) covered.push(label);
        else missing.push(label);
    }
    const honesty = answer.match(/\(확인 필요\)|\(담당자? 미정\)|\(기한 미정\)/g);
    const sections = answer.match(/^#{1,3}\s+/gm);
    return {
        recordId: record.id,
        coverageRate: record.expectedElements.length === 0 ? 1 : covered.length / record.expectedElements.length,
        covered,
        missing,
        honestyMarkers: honesty ? honesty.length : 0,
        sectionCount: sections ? sections.length : 0,
        answerLength: answer.length,
    };
}

export interface TaskEvalRunResult {
    scores: TaskScore[];
    avgCoverage: number;
    perfectCount: number;
}

/**
 * 골든셋 전체 평가 — 레코드별로 원자료를 읽어(readSource 주입) 생성(generate 주입)
 * 후 채점. 한 레코드 실패가 전체를 막지 않음.
 */
export async function runTaskEval(params: {
    records: TaskGoldenRecord[];
    /** 원자료 파일 내용 로드 — 보통 fs.readFileSync, 테스트에선 fake. */
    readSource: (sourceFile: string) => string;
    /** 산출물 생성 — (query, sourceContent, expectedElements) → 답변. */
    generate: (record: TaskGoldenRecord, sourceContent: string) => Promise<string>;
    /** 원자료 최대 길이 (chars) — 로컬 모델 컨텍스트 보호. 기본 20000. */
    maxSourceChars?: number;
    onProgress?: (done: number, total: number) => void;
}): Promise<TaskEvalRunResult> {
    const maxChars = params.maxSourceChars ?? 20000;
    const scores: TaskScore[] = [];
    let done = 0;
    for (const record of params.records) {
        try {
            let source = params.readSource(record.sourceFile);
            if (source.length > maxChars) source = source.slice(0, maxChars) + '\n…(잘림)';
            const answer = await params.generate(record, source);
            scores.push(scoreTaskAnswer(answer, record));
        } catch (e: any) {
            scores.push({
                recordId: record.id, coverageRate: 0, covered: [], missing: record.expectedElements.slice(),
                honestyMarkers: 0, sectionCount: 0, answerLength: 0,
                error: e?.message || String(e),
            });
        }
        done++;
        params.onProgress?.(done, params.records.length);
    }
    const valid = scores.filter((s) => !s.error);
    const avgCoverage = valid.length === 0 ? 0 : valid.reduce((s, r) => s + r.coverageRate, 0) / valid.length;
    return { scores, avgCoverage, perfectCount: valid.filter((s) => s.coverageRate === 1).length };
}

export function formatTaskEvalReport(
    result: TaskEvalRunResult,
    meta: { taskLabel: string; brainName: string; dateStr: string; modelName: string; notes?: string },
): string {
    const lines: string[] = [];
    lines.push(`# 업무 평가 리포트 — ${meta.taskLabel}`);
    lines.push('');
    lines.push(`- 두뇌: ${meta.brainName}`);
    lines.push(`- 일시: ${meta.dateStr}`);
    lines.push(`- 모델: ${meta.modelName}`);
    if (meta.notes) lines.push(`- 비고: ${meta.notes}`);
    lines.push('');
    lines.push(`## 요약 — 평균 요소 커버리지 **${(result.avgCoverage * 100).toFixed(1)}%** · 전 요소 충족 ${result.perfectCount}/${result.scores.length}건`);
    lines.push('');
    lines.push('| 레코드 | 커버리지 | 누락 요소 | 정직성 표시 | 섹션 수 | 길이 |');
    lines.push('|---|---|---|---|---|---|');
    for (const s of result.scores) {
        if (s.error) {
            lines.push(`| ${s.recordId} | — | (실패: ${s.error.slice(0, 60)}) | — | — | — |`);
        } else {
            lines.push(`| ${s.recordId} | ${(s.coverageRate * 100).toFixed(0)}% | ${s.missing.join(', ') || '없음'} | ${s.honestyMarkers} | ${s.sectionCount} | ${s.answerLength} |`);
        }
    }
    lines.push('');
    lines.push('> 같은 골든셋으로 버전마다 측정해 커버리지 추이를 비교하세요 — 이 숫자의 상승이 곧 성장세입니다.');
    lines.push('');
    return lines.join('\n');
}
