/**
 * Correction Loop 단위 테스트 — 순수 로직 (감지·프로필·레슨·큐 등록·영속화).
 * LLM 의존 부분(classifyCorrection)은 엔드포인트 실패 → 휴리스틱 fallback 경로만 검증.
 */
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import {
    looksLikeCorrection, classifyCorrection, correctionLessonMarkdown,
    appendCorrectionCase, loadCorrectionCases, registerKnowledgeGap,
    computeWeaknessProfile, saveWeaknessProfile, loadWeaknessProfile,
    buildSelfReviewBlock, formatRegressionReport,
    type CorrectionCase,
} from '../src/intelligence/correctionLoop';
import { loadQueue } from '../src/intelligence/learningQueue';

const tmpBrain = () => fs.mkdtempSync(path.join(os.tmpdir(), 'astra-corr-'));

const CASE = (over: Partial<CorrectionCase> = {}): CorrectionCase => ({
    ts: new Date().toISOString(),
    errorTag: '사실오류',
    question: '회의가 언제였지?',
    wrongAnswer: '5월 10일입니다.',
    correction: '아니야, 6월 10일이야.',
    title: '회의 날짜 오답',
    ...over,
});

describe('looksLikeCorrection — 정정 감지 (보수적)', () => {
    test.each([
        '아니야, 그 회의는 6월이야',
        '틀렸어. 다시 확인해',
        '그게 아니라 화요일이야',
        '담당자는 김OO가 아니라 박OO야',
        '그 수치는 사실과 달라',
        '잘못 알고 있네 — 정정해줄게',
        '지어내지 마',
    ])('정정으로 감지: %s', (p) => expect(looksLikeCorrection(p)).toBe(true));

    test.each([
        '커밋하고 푸쉬해줘',
        '내일 일정 알려줘',
        '아니 그래서 결과가 어떻게 됐어?', // "아니" 단독 추임새는 비정정
        '회의록 요약해줘',
        '',
        '응',
    ])('비정정: %s', (p) => expect(looksLikeCorrection(p)).toBe(false));
});

describe('classifyCorrection — LLM 실패 시 휴리스틱 fallback', () => {
    const deadLlm = { baseUrl: 'http://127.0.0.1:1', model: 'x' };
    test('출처 언급 → 근거누락', async () => {
        const r = await classifyCorrection('q', 'a', '출처도 없이 단정하지 마', deadLlm);
        expect(r.tag).toBe('근거누락');
        expect(r.title.length).toBeGreaterThan(0);
    });
    test('맥락 언급 → 맥락누락', async () => {
        const r = await classifyCorrection('q', 'a', '아까 위에서 말했잖아', deadLlm);
        expect(r.tag).toBe('맥락누락');
    });
    test('기본 → 사실오류', async () => {
        const r = await classifyCorrection('q', 'a', '6월 10일이 맞아', deadLlm);
        expect(r.tag).toBe('사실오류');
    });
});

describe('회귀 케이스 영속화', () => {
    test('append → load 라운드트립 + 필드 길이 제한', () => {
        const brain = tmpBrain();
        const long = 'x'.repeat(2000);
        expect(appendCorrectionCase(brain, CASE({ wrongAnswer: long }))).toBe(true);
        expect(appendCorrectionCase(brain, CASE({ title: '두번째' }))).toBe(true);
        const cases = loadCorrectionCases(brain);
        expect(cases).toHaveLength(2);
        expect(cases[0].wrongAnswer.length).toBeLessThanOrEqual(600);
        expect(cases[1].title).toBe('두번째');
    });
    test('손상 라인은 건너뛴다', () => {
        const brain = tmpBrain();
        appendCorrectionCase(brain, CASE());
        fs.appendFileSync(path.join(brain, '.astra', 'eval', 'corrections.jsonl'), '{broken json\n');
        appendCorrectionCase(brain, CASE({ title: 'b' }));
        expect(loadCorrectionCases(brain)).toHaveLength(2);
    });
});

describe('correctionLessonMarkdown', () => {
    test('error-tag frontmatter + Ground Truth 포함', () => {
        const md = correctionLessonMarkdown(CASE(), '2026-06-11');
        expect(md).toContain('error-tag: 사실오류');
        expect(md).toContain('source: user-correction');
        expect(md).toContain('아니야, 6월 10일이야.');
        expect(md).toMatch(/^---\ntype: lesson/);
    });
});

describe('registerKnowledgeGap — 학습 큐 자동 proposed', () => {
    test('등록 + 같은 질문 중복 차단', () => {
        const brain = tmpBrain();
        expect(registerKnowledgeGap(brain, 'CRAG 교정 검색이 뭐야?', 0.12)).toBe(true);
        expect(registerKnowledgeGap(brain, 'CRAG 교정 검색이 뭐야?', 0.2)).toBe(false);
        const q = loadQueue(brain);
        expect(q).toHaveLength(1);
        expect(q[0].status).toBe('proposed');
        expect(q[0].id).toMatch(/^gap-/);
        expect(q[0].reason).toContain('0.12');
    });
    test('짧은 질문 무시 + 20건 폭주 방지', () => {
        const brain = tmpBrain();
        expect(registerKnowledgeGap(brain, '뭐야?', 0)).toBe(false);
        for (let i = 0; i < 25; i++) registerKnowledgeGap(brain, `지식 공백 질문 번호 ${i} 에 대한 상세 내용`, 0.1);
        expect(loadQueue(brain).length).toBeLessThanOrEqual(20);
    });
});

describe('약점 프로필', () => {
    const now = Date.parse('2026-06-11T00:00:00Z');
    test('윈도우 필터 + 태그 집계 내림차순', () => {
        const cases = [
            CASE({ ts: '2026-06-01T00:00:00Z', errorTag: '사실오류' }),
            CASE({ ts: '2026-06-05T00:00:00Z', errorTag: '사실오류', title: '최신 예시' }),
            CASE({ ts: '2026-06-03T00:00:00Z', errorTag: '근거누락' }),
            CASE({ ts: '2025-01-01T00:00:00Z', errorTag: '형식오류' }), // 윈도우 밖
        ];
        const p = computeWeaknessProfile(cases, now, 60);
        expect(p.totalCases).toBe(3);
        expect(p.tagCounts[0]).toMatchObject({ tag: '사실오류', count: 2, example: '최신 예시' });
        expect(p.tagCounts.find(t => t.tag === '형식오류')).toBeUndefined();
    });
    test('save → load 라운드트립 + 자기검토 블록 (2회 이상만)', () => {
        const brain = tmpBrain();
        const p = computeWeaknessProfile([
            CASE({ errorTag: '사실오류' }), CASE({ errorTag: '사실오류' }), CASE({ errorTag: '근거누락' }),
        ], Date.now(), 60);
        expect(saveWeaknessProfile(brain, p)).toBe(true);
        const block = buildSelfReviewBlock(loadWeaknessProfile(brain));
        expect(block).toContain('[자기검토');
        expect(block).toContain('사실오류');
        expect(block).not.toContain('근거누락'); // 1회짜리는 미주입
    });
    test('데이터 없으면 빈 블록', () => {
        expect(buildSelfReviewBlock(null)).toBe('');
        expect(buildSelfReviewBlock(computeWeaknessProfile([], Date.now()))).toBe('');
    });
});

describe('formatRegressionReport', () => {
    test('재발/통과/판정불가 마크', () => {
        const md = formatRegressionReport([
            { question: 'q1', errorTag: '사실오류', repeated: true, note: 'n1' },
            { question: 'q2', errorTag: '근거누락', repeated: false, note: 'n2' },
            { question: 'q3', errorTag: '기타', repeated: null, note: 'n3' },
        ], { dateStr: '2026-06-11' });
        expect(md).toContain('❌ 재발');
        expect(md).toContain('✅ 통과');
        expect(md).toContain('⚠️ 판정불가');
    });
});
