/**
 * 신규 지식 충돌 스캔 — 지식 정합성의 "쓰기 시점" 게이트.
 *
 * 기존 충돌 감지(검색 시점 [CONFLICT WARNING])는 두 문서가 *우연히 같이 검색될 때*만
 * 동작한다. 이 스캔은 새로 추가·변경된 두뇌 문서를 매일 능동적으로 기존 유사 문서와
 * 대조해, 검색되기 전에 모순을 표면화한다 (Mem0 의 UPDATE/DELETE 판단의 보수적 변형 —
 * 시스템은 감지·보고만 하고 어느 쪽을 유지할지는 사람이 결정: Permission Based Learning).
 *
 * 흐름: [신규/변경 문서 감지(mtime)] → [유사 기존 문서 top-k 검색] → [LLM 모순 비교]
 *      → [충돌 시 .astra/growth/conflict-report.md 기록 + 알림 "기존 A vs 신규 B"]
 *
 * 위키를 쓰는 주체(Datacollect 브리지·수동 편집·Research Agent)와 무관하게 전부 포착
 * — 쓰기 가로채기 대신 사후 스캔을 택한 이유.
 */
import * as fs from 'fs';
import * as path from 'path';
import * as vscode from 'vscode';
import { getConfig } from '../../config';
import { findBrainFiles, getActiveBrainProfile, logError, logInfo } from '../../utils';
import { RetrievalOrchestrator } from '../../retrieval';
import { getBrainTokenIndex } from '../../retrieval/brainIndex';
import { simpleChatCompletion } from '../../intelligence/llmCall';
import { DIGEST_DIR } from './sleepDigest';

const STATE_REL = path.join('.astra', 'growth', 'conflict-scan-state.json');
const REPORT_REL = path.join('.astra', 'growth', 'conflict-report.md');
const RECONCILE_DIR_REL = path.join('.astra', 'growth', 'reconcile');
const MAX_COMPARES_PER_RUN = 5;
const MAX_RECONCILE_PER_RUN = 3;
const NEIGHBOR_K = 2;

/** 스캔 제외 — 소화 노트·레슨·자동 생성 인벤토리는 지식 본문이 아니다. */
export function isScanTarget(relPath: string): boolean {
    return !new RegExp(`(^|[\\\\/])(${DIGEST_DIR}|lessons?|playbooks?|qa[-_]?findings?)([\\\\/])`, 'i').test(relPath)
        && !/기능 인벤토리/.test(relPath)
        && relPath.toLowerCase().endsWith('.md');
}

function loadLastScanMs(brainPath: string): number {
    try {
        const f = path.join(brainPath, STATE_REL);
        if (!fs.existsSync(f)) return 0;
        const v = JSON.parse(fs.readFileSync(f, 'utf8'))?.lastScanMs;
        return typeof v === 'number' ? v : 0;
    } catch { return 0; }
}

function saveLastScanMs(brainPath: string, ms: number): void {
    try {
        const f = path.join(brainPath, STATE_REL);
        fs.mkdirSync(path.dirname(f), { recursive: true });
        fs.writeFileSync(f, JSON.stringify({ lastScanMs: ms }) + '\n', 'utf8');
    } catch { /* 상태 저장 실패 — 다음 런이 더 넓게 스캔할 뿐 */ }
}

// ── 지식 재구성 — 충돌을 감지에서 끝내지 않고 통합 초안까지 (적용은 사람) ──────

const RECONCILE_SYSTEM = [
    '너는 지식 통합 편집자다. 같은 주제에 대해 모순되는 두 위키 문서를 받아, 모순을 해소한 *통합 초안*을 작성한다.',
    '규칙:',
    '1. [신뢰 권고]가 우선하는 쪽의 사실을 기준으로 삼되, 다른 쪽의 유효한(모순되지 않는) 정보는 보존하라.',
    '2. 어느 쪽이 맞는지 문서만으로 판단 불가능한 사실은 양쪽을 병기하고 "(확인 필요: A는 X, B는 Y)" 로 표시하라.',
    '3. 원문에 없는 내용을 지어내지 마라. 출처 문서 제목을 [제목] 으로 표기하라.',
    '4. 마크다운 본문만 출력 (frontmatter·해설 없이).',
].join('\n');

/** 통합 초안 파일 본문 (순수 — 테스트 가능). 적용은 사람이 검토 후 직접. */
export function reconcileDraftMarkdown(f: ConflictFinding, body: string, nowIso: string): string {
    return [
        '---',
        'type: reconcile-draft',
        `created_at: ${nowIso}`,
        `conflict_new: "${f.newDoc.replace(/"/g, "'")}"`,
        `conflict_existing: "${f.existingDoc.replace(/"/g, "'")}"`,
        'status: pending-review',
        '---',
        '',
        `# 통합 초안: ${path.basename(f.newDoc, '.md')} ↔ ${path.basename(f.existingDoc, '.md')}`,
        '',
        `> ⚔️ 모순: ${f.summary}`,
        `> ${f.recommend}`,
        '> ',
        '> ✋ **이 초안은 자동 반영되지 않습니다.** 검토 후 승인하면 아래 본문을 대상 문서에 직접 반영하고,',
        '> 다른 쪽 문서를 수정/삭제하세요. 거부하면 이 파일을 삭제하면 됩니다.',
        '',
        body.trim(),
        '',
    ].join('\n');
}

const COMPARE_SYSTEM = [
    '너는 지식 정합성 검사기다. [신규 문서]와 [기존 문서]가 같은 주제에 대해 *모순되는 사실*을 말하는지 판정하라.',
    '모순 = 같은 대상에 대해 양립 불가능한 수치·날짜·결론·정의. 관점 차이·세부 수준 차이·다른 주제는 모순이 아니다.',
    '반드시 JSON 한 줄만 출력: {"conflict":true|false,"summary":"<모순 내용 한 문장, 없으면 빈 문자열>"}',
].join('\n');

export interface ConflictFinding {
    newDoc: string;
    existingDoc: string;
    summary: string;
    /** 신뢰도 비교 기반 우선 권고 (자동 삭제는 하지 않음 — 결정은 사람). */
    recommend: string;
    /** 생성된 통합 초안의 두뇌 상대 경로 (생성 실패/상한 초과 시 없음). */
    draftPath?: string;
}

// ── 신뢰도 비교 — frontmatter(source_trust_level·confidence_score) + 최신성 ──

export interface DocTrust {
    trustLevel: string;   // S/A/B/C/D 또는 ''
    confidence: number;   // 0~1, 미상 -1
    mtimeMs: number;
}

const TRUST_RANK: Record<string, number> = { S: 4, A: 3, B: 2, C: 1, D: 0 };

/** 문서 머리 frontmatter 에서 신뢰 메타데이터 추출 (없으면 빈 값). */
export function parseDocTrust(content: string, mtimeMs: number): DocTrust {
    const head = (content || '').slice(0, 1200);
    const trust = head.match(/^source_trust_level:\s*["']?([SABCD])["']?\s*$/mi)?.[1]?.toUpperCase() || '';
    const confRaw = head.match(/^confidence_score:\s*["']?([\d.]+)["']?\s*$/mi)?.[1];
    const confidence = confRaw !== undefined && Number.isFinite(Number(confRaw)) ? Math.max(0, Math.min(1, Number(confRaw))) : -1;
    return { trustLevel: trust, confidence, mtimeMs };
}

const fmtTrust = (t: DocTrust) =>
    `${t.trustLevel || '등급없음'}·${t.confidence >= 0 ? t.confidence.toFixed(1) : '점수없음'}`;

/**
 * 충돌 양쪽의 신뢰도를 비교해 우선 권고 문자열 생성.
 * 점수 = 신뢰등급(0~4)×2 + confidence(0~1)×2 + 최신성(+1). 메타데이터가 양쪽 다
 * 없으면 권고 보류 — 근거 없는 권고는 하지 않는다.
 */
export function buildTrustRecommendation(newer: DocTrust, older: DocTrust): string {
    const hasMetaN = !!newer.trustLevel || newer.confidence >= 0;
    const hasMetaO = !!older.trustLevel || older.confidence >= 0;
    if (!hasMetaN && !hasMetaO) return '권고 보류 — 양쪽 모두 신뢰 메타데이터 없음 (내용으로 직접 판단 필요)';
    const score = (t: DocTrust, isNewer: boolean) =>
        (TRUST_RANK[t.trustLevel] ?? 0) * 2 + (t.confidence >= 0 ? t.confidence : 0) * 2 + (isNewer ? 1 : 0);
    const sNew = score(newer, true), sOld = score(older, false);
    if (Math.abs(sNew - sOld) < 1) return `권고 보류 — 신뢰도 비등 (신규 ${fmtTrust(newer)} vs 기존 ${fmtTrust(older)})`;
    return sNew > sOld
        ? `권고: **신규 우선** (신규 ${fmtTrust(newer)}·최신 vs 기존 ${fmtTrust(older)})`
        : `권고: **기존 우선** (기존 ${fmtTrust(older)} vs 신규 ${fmtTrust(newer)}·최신)`;
}

/** 1회 스캔 실행. 반환: 요약 문자열. */
export async function runConflictScanOnce(): Promise<string> {
    const brain = getActiveBrainProfile();
    if (!brain?.localBrainPath || !fs.existsSync(brain.localBrainPath)) return '두뇌 없음 — skip';
    const config = getConfig();
    if (!config.defaultModel || !config.ollamaUrl) return 'LLM 미설정 — skip';

    const brainPath = brain.localBrainPath;
    const lastScanMs = loadLastScanMs(brainPath);
    const scanStartMs = Date.now();
    const allFiles = findBrainFiles(brainPath);

    // 신규/변경 대상 (최초 실행은 최근 24h 만 — 두뇌 전체 재스캔 방지)
    const cutoff = lastScanMs > 0 ? lastScanMs : scanStartMs - 86_400_000;
    const targets = allFiles
        .map(f => { try { return { f, m: fs.statSync(f).mtimeMs }; } catch { return null; } })
        .filter((x): x is { f: string; m: number } => !!x && x.m > cutoff)
        .filter(x => isScanTarget(path.relative(brainPath, x.f)))
        .sort((a, b) => b.m - a.m)
        .slice(0, MAX_COMPARES_PER_RUN);

    if (targets.length === 0) { saveLastScanMs(brainPath, scanStartMs); return '신규 문서 없음'; }

    getBrainTokenIndex(brainPath, allFiles);
    const orchestrator = new RetrievalOrchestrator();
    const findings: ConflictFinding[] = [];
    let compared = 0;
    let reconciled = 0;

    for (const t of targets) {
        try {
            const rel = path.relative(brainPath, t.f);
            const content = fs.readFileSync(t.f, 'utf8');
            const title = path.basename(t.f, '.md');
            // 유사 기존 문서 — 자신 제외 상위 NEIGHBOR_K
            const neighbors = orchestrator
                .rankBrainForEval(`${title} ${content.slice(0, 500)}`, brain, { limit: NEIGHBOR_K + 3 })
                .filter(r => path.resolve(r.filePath) !== path.resolve(t.f))
                .slice(0, NEIGHBOR_K);
            for (const n of neighbors) {
                let existing = '';
                try { existing = fs.readFileSync(n.filePath, 'utf8'); } catch { continue; }
                const raw = await simpleChatCompletion(
                    COMPARE_SYSTEM,
                    `[신규 문서: ${title}]\n${content.slice(0, 3500)}\n\n[기존 문서: ${path.basename(n.relativePath, '.md')}]\n${existing.slice(0, 3500)}`,
                    { baseUrl: config.ollamaUrl, model: config.defaultModel, temperature: 0.1, maxTokens: 150, timeoutMs: 90000 },
                );
                compared++;
                const m = raw.match(/\{[\s\S]*?\}/);
                if (!m) continue;
                const parsed = JSON.parse(m[0]);
                if (parsed?.conflict === true && String(parsed?.summary || '').trim()) {
                    let existingMtime = 0;
                    try { existingMtime = fs.statSync(n.filePath).mtimeMs; } catch { /* 0 유지 */ }
                    const finding: ConflictFinding = {
                        newDoc: rel,
                        existingDoc: n.relativePath,
                        summary: String(parsed.summary).slice(0, 200),
                        recommend: buildTrustRecommendation(parseDocTrust(content, t.m), parseDocTrust(existing, existingMtime)),
                    };
                    findings.push(finding);

                    // [지식 재구성] 감지에서 끝내지 않고 통합 초안 생성 → 사람 승인 대기.
                    // 양쪽 본문이 지금 스코프에 있을 때 바로 생성 (런당 상한 — LLM 시간 보호).
                    if (reconciled < MAX_RECONCILE_PER_RUN) {
                        try {
                            const draftBody = await simpleChatCompletion(
                                RECONCILE_SYSTEM,
                                [
                                    `[모순] ${finding.summary}`,
                                    `[신뢰 권고] ${finding.recommend}`,
                                    `[문서 A — 신규: ${path.basename(rel, '.md')}]\n${content.slice(0, 4000)}`,
                                    `[문서 B — 기존: ${path.basename(n.relativePath, '.md')}]\n${existing.slice(0, 4000)}`,
                                ].join('\n\n'),
                                { baseUrl: config.ollamaUrl, model: config.defaultModel, temperature: 0.2, maxTokens: 1600, timeoutMs: 180000 },
                            );
                            if (draftBody.trim()) {
                                const dir = path.join(brainPath, RECONCILE_DIR_REL);
                                fs.mkdirSync(dir, { recursive: true });
                                const slug = path.basename(rel, '.md').replace(/[^a-zA-Z0-9가-힣_-]+/g, '-').slice(0, 50);
                                const draftFile = path.join(dir, `${new Date().toISOString().slice(0, 10)}-${slug}.md`);
                                fs.writeFileSync(draftFile, reconcileDraftMarkdown(finding, draftBody, new Date().toISOString()), 'utf8');
                                finding.draftPath = path.relative(brainPath, draftFile);
                                reconciled++;
                            }
                        } catch (e: any) {
                            logError('통합 초안 생성 실패 (감지 결과는 유지).', { error: e?.message ?? String(e) });
                        }
                    }
                }
            }
        } catch (e: any) {
            logError('충돌 스캔: 문서 비교 실패 (계속).', { file: t.f, error: e?.message ?? String(e) });
        }
    }

    saveLastScanMs(brainPath, scanStartMs);

    if (findings.length > 0) {
        const reportFile = path.join(brainPath, REPORT_REL);
        const block = [
            `\n## ${new Date().toLocaleString()} — 충돌 ${findings.length}건`,
            ...findings.map(f => `- ⚔️ 신규 **${f.newDoc}** ↔ 기존 **${f.existingDoc}**: ${f.summary}\n  → ${f.recommend}${f.draftPath ? `\n  → 📝 통합 초안: \`${f.draftPath}\` (검토 후 승인 시 직접 반영)` : ''}\n  → 어느 쪽을 유지할지 결정 후 다른 쪽을 수정/삭제하세요 (시스템은 덮어쓰지 않음).`),
        ].join('\n');
        try {
            fs.mkdirSync(path.dirname(reportFile), { recursive: true });
            if (!fs.existsSync(reportFile)) fs.writeFileSync(reportFile, '# 지식 충돌 리포트\n시스템은 감지·보고만 합니다 — 유지/수정 결정은 사람.\n', 'utf8');
            fs.appendFileSync(reportFile, block + '\n', 'utf8');
        } catch { /* 리포트 실패해도 알림은 전달 */ }
        void vscode.window.showWarningMessage(
            `⚔️ 지식 충돌 ${findings.length}건 감지 — 예: "${findings[0].newDoc}" ↔ "${findings[0].existingDoc}". conflict-report.md 에서 확인하세요.`,
        );
    }
    const summary = `신규 ${targets.length}건 스캔 · 비교 ${compared}회 · 충돌 ${findings.length}건${reconciled ? ` · 통합 초안 ${reconciled}건 (reconcile/)` : ''}`;
    logInfo('지식 충돌 스캔 완료.', { summary });
    return summary;
}
