/**
 * Knowledge Validation / Belief Revision / Decay / Debt
 * (Self-Evolving OS Phase 4 — 지식 운영) 테스트.
 */
import {
    validateKnowledgeCandidate,
    jaccardSimilarity,
    ExistingKnowledgeRef,
} from '../src/intelligence/knowledgeValidation';
import {
    auditKnowledgeDecay,
    classifyDecayRule,
    decayFactor,
    formatDecayReport,
} from '../src/intelligence/knowledgeDecay';
import { computeKnowledgeDebt, formatNeedsMarkdown } from '../src/intelligence/needEngine';
import type { ReflectionRecord } from '../src/intelligence/reflectionStore';

const NOW = Date.parse('2026-06-11T00:00:00Z');
const DAY = 86400000;

describe('knowledgeValidation', () => {
    const existing: ExistingKnowledgeRef[] = [
        {
            title: 'GA4 전환율 가이드',
            content: 'GA4 전환율 계산은 전환수 나누기 세션수 기준이며 보고서는 탐색 분석에서 본다',
            lastUpdated: NOW - 100 * DAY,
        },
    ];

    it('출처 있고 중복/충돌 없는 신선한 후보 → accept', () => {
        const r = validateKnowledgeCandidate(
            { title: '쿠팡 SEO', content: '쿠팡 검색 알고리즘은 판매량과 리뷰 점수를 핵심 신호로 사용한다', source: 'https://example.com', collectedAt: '2026-06-10T00:00:00Z' },
            existing, { nowMs: NOW },
        );
        expect(r.verdict).toBe('accept');
        expect(r.beliefRevision).toBe('add');
    });

    it('출처 없으면 자동 수용 불가 (review)', () => {
        const r = validateKnowledgeCandidate(
            { title: 't', content: '완전히 새로운 내용의 지식 후보입니다 검증 테스트' },
            existing, { nowMs: NOW },
        );
        expect(r.verdict).toBe('review');
        expect(r.checks.hasSource).toBe(false);
    });

    it('거의 동일한 내용 → 중복 reject', () => {
        const r = validateKnowledgeCandidate(
            { title: 'GA4', content: 'GA4 전환율 계산은 전환수 나누기 세션수 기준이며 보고서는 탐색 분석에서 본다', source: 's', collectedAt: '2026-06-10T00:00:00Z' },
            existing, { nowMs: NOW },
        );
        expect(r.verdict).toBe('reject');
        expect(r.checks.duplicateOf).toBe('GA4 전환율 가이드');
    });

    it('관련/충돌 + 후보가 더 최신 → review + update 권고 (Belief Revision)', () => {
        const r = validateKnowledgeCandidate(
            { title: 'GA4 변경', content: 'GA4 전환율 계산은 이제 전환수 나누기 사용자수 기준으로 변경되었다 보고서 위치도 다르다', source: 's', collectedAt: '2026-06-01T00:00:00Z' },
            existing, { nowMs: NOW },
        );
        expect(r.verdict).toBe('review');
        expect(r.checks.conflictsWith).toBe('GA4 전환율 가이드');
        expect(r.beliefRevision).toBe('update');
    });

    it('수집일이 1년 이상 경과 → stale review', () => {
        const r = validateKnowledgeCandidate(
            { title: 't', content: '전혀 다른 주제의 오래된 지식 항목', source: 's', collectedAt: '2024-01-01T00:00:00Z' },
            existing, { nowMs: NOW },
        );
        expect(r.verdict).toBe('review');
        expect(r.checks.freshness).toBe('stale');
    });

    it('jaccardSimilarity — 동일 1.0, 무관 ~0', () => {
        expect(jaccardSimilarity('같은 문장 테스트', '같은 문장 테스트')).toBe(1);
        expect(jaccardSimilarity('완전히 다른 내용', '전혀 무관한 주제')).toBe(0);
    });
});

describe('knowledgeDecay', () => {
    it('분야 분류 — AI 30일, SEO 90일, 기본 365일', () => {
        expect(classifyDecayRule('Topics/RAG_청킹_전략.md').halfLifeDays).toBe(30);
        expect(classifyDecayRule('Topics/네이버_SEO_가이드.md').halfLifeDays).toBe(90);
        expect(classifyDecayRule('Topics/요리_레시피.md').halfLifeDays).toBe(365);
    });

    it('decayFactor — 반감기 경과 시 0.5', () => {
        expect(decayFactor(NOW - 30 * DAY, 30, NOW)).toBeCloseTo(0.5, 2);
        expect(decayFactor(NOW, 30, NOW)).toBe(1);
    });

    it('audit — stale 우선 정렬 + 상태 판정', () => {
        const items = auditKnowledgeDecay([
            { relPath: 'ai_guide.md', lastUpdated: NOW - 90 * DAY },   // AI 30일 반감 → 0.125 stale
            { relPath: '요리.md', lastUpdated: NOW - 30 * DAY },        // 일반 365일 → ~0.94 active
        ], { nowMs: NOW });
        expect(items[0].relPath).toBe('ai_guide.md');
        expect(items[0].status).toBe('stale');
        expect(items[1].status).toBe('active');
    });

    it('formatDecayReport — 요약·권고 포함, 자동 삭제 없음 명시', () => {
        const items = auditKnowledgeDecay([{ relPath: 'ai.md', lastUpdated: NOW - 200 * DAY }], { nowMs: NOW });
        const md = formatDecayReport(items, { brainName: 'B', dateStr: 'now' });
        expect(md).toContain('노후 1');
        expect(md).toContain('자동 이동/삭제 없음');
    });
});

describe('knowledgeDebt', () => {
    function mk(partial: Partial<ReflectionRecord>): ReflectionRecord {
        return {
            ts: '2026-06-11T10:00:00.000Z', taskId: 'market-research', taskLabel: '시장조사',
            confidenceScore: 50, confidenceBand: 'low', missing: [], escalated: false,
            criticIssues: null, promptPreview: 'p', weakGrounding: true, gapSeverity: 'high',
            ...partial,
        };
    }

    it('근거 없는 수행 turn 을 업무별로 집계, debtScore 정렬', () => {
        const debt = computeKnowledgeDebt([
            mk({}), mk({}), mk({ gapSeverity: 'medium' }),
            mk({ taskId: 'meeting-minutes', taskLabel: '회의록', gapSeverity: 'low' }),
            mk({ taskId: 'meeting-minutes', taskLabel: '회의록', weakGrounding: false }), // 부채 아님
        ]);
        expect(debt[0].taskId).toBe('market-research');
        expect(debt[0].blockedTurns).toBe(3);
        expect(debt[0].impact).toBeGreaterThan(5);
        expect(debt.find((d) => d.taskId === 'meeting-minutes')!.blockedTurns).toBe(1);
    });

    it('formatNeedsMarkdown 에 Debt 섹션 포함', () => {
        const debt = computeKnowledgeDebt([mk({})]);
        const md = formatNeedsMarkdown([], [], debt);
        expect(md).toContain('Knowledge Debt');
        expect(md).toContain('시장조사');
    });
});

describe('orgMemoryBlock (P5)', () => {
    const fsMod = require('fs');
    const osMod = require('os');
    const pathMod = require('path');
    const { buildOrgMemoryBlock, ORG_MEMORY_REL_PATH } = require('../src/intelligence/orgMemoryBlock');

    it('organization.md 가 있으면 블록 주입 + Human Override 명시', () => {
        const brain = fsMod.mkdtempSync(pathMod.join(osMod.tmpdir(), 'astra-test-org-'));
        const file = pathMod.join(brain, ORG_MEMORY_REL_PATH);
        fsMod.mkdirSync(pathMod.dirname(file), { recursive: true });
        fsMod.writeFileSync(file, '## 업무 방식\n- 속도 우선, 완벽주의 지양', 'utf8');
        const block = buildOrgMemoryBlock(brain);
        expect(block).toContain('[ORGANIZATIONAL MEMORY]');
        expect(block).toContain('속도 우선');
        expect(block).toContain('사용자 지시 우선');
    });

    it('파일 없으면 빈 문자열 (no-op)', () => {
        const brain = fsMod.mkdtempSync(pathMod.join(osMod.tmpdir(), 'astra-test-org-'));
        expect(buildOrgMemoryBlock(brain)).toBe('');
    });

    it('본문이 길면 cap + 잘림 안내', () => {
        const brain = fsMod.mkdtempSync(pathMod.join(osMod.tmpdir(), 'astra-test-org-'));
        const file = pathMod.join(brain, ORG_MEMORY_REL_PATH);
        fsMod.mkdirSync(pathMod.dirname(file), { recursive: true });
        fsMod.writeFileSync(file, 'x'.repeat(5000), 'utf8');
        const block = buildOrgMemoryBlock(brain, { maxBodyLength: 1000 });
        expect(block).toContain('잘림');
        expect(block.length).toBeLessThan(2000);
    });
});
