/**
 * Sleep-time 지식 사전 소화 (Sleep-time Compute, arXiv 2504.13171 의 ASTRA 이식).
 *
 * 아이디어: 로컬 LLM 의 최대 약점(느린 추론)을 응답 시점에서 *유휴 시간*으로 옮긴다.
 * 유휴 시간에 두뇌의 "raw context"(위키 원문 더미)를 "learned context"(예상 질의별
 * 압축 소화 노트)로 변환해 두면, 응답 시점의 RAG 가 이미 소화된 고밀도 노트를
 * 검색해 적은 토큰으로 더 정확히 답한다.
 *
 * 설계 (ASTRA 제약 반영):
 *  - 저장: <brain>/Digests/<slug>.md — 검색 가능한 지식 폴더. type: digest frontmatter.
 *    사람이 열람·수정·삭제 가능 (파일이 UI). 원문이 항상 우선 — 소화 노트는 요약 캐시.
 *  - 대상 선정: 최근 N일 내 수정 파일이 많은 폴더 순 (최근 추가된 지식이 가장 질의될
 *    확률이 높다는 가설 — sleep-time 효과는 질의 예측 가능성에 비례).
 *  - 노후화: 소스 파일 mtime > 소화 노트 generated_at 이면 재생성. 아니면 skip
 *    (steady-state 비용 0).
 *  - 승인 불요: 외부 지식 유입이 아니라 기존 두뇌의 재구성이므로 Permission Based
 *    Learning 의 승인 게이트 대상이 아니다. 단 전부 파일로 투명하게.
 *  - 스케줄: 매일 KST 03:00 (기본) — stocksWatcher 와 동일한 setTimeout 체인 패턴.
 */
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 { simpleChatCompletion } from '../../intelligence/llmCall';

export const DIGEST_DIR = 'Digests';
const MAX_PER_RUN = 5;
const RECENT_WINDOW_DAYS = 7;
const MAX_FILES_PER_TARGET = 8;
const MAX_INPUT_CHARS = 9000;

let _timer: NodeJS.Timeout | undefined;
let _disposed = false;
let _lastFiredYmd = '';

// ── 대상 선정 (순수 로직 — 테스트 대상) ─────────────────────────────────────

export interface DigestTarget {
    /** 폴더 키 (brain 상대 경로, '' = 루트 직속). */
    folder: string;
    /** 파일명 슬러그. */
    slug: string;
    /** 최근 수정 파일 절대 경로 (mtime 내림차순, 상한 적용). */
    files: string[];
    /** 가장 최신 소스 mtime — 노후화 판정용. */
    newestMtimeMs: number;
}

export function folderSlug(folder: string): string {
    const base = (folder || 'root').replace(/[\\/]+/g, '--').replace(/[^a-zA-Z0-9가-힣_.-]+/g, '-');
    return base.slice(0, 80) || 'root';
}

/**
 * 최근 windowDays 내 수정된 파일을 폴더(두뇌 1-2단계 경로)별로 묶어,
 * 수정 파일 수가 많은 순으로 소화 대상을 고른다. Digests/ 자신은 제외.
 */
export function selectDigestTargets(
    brainPath: string,
    allFiles: string[],
    nowMs: number,
    windowDays = RECENT_WINDOW_DAYS,
): DigestTarget[] {
    const cutoff = nowMs - windowDays * 86_400_000;
    const byFolder = new Map<string, Array<{ file: string; mtimeMs: number }>>();
    for (const f of allFiles) {
        let mtimeMs = 0;
        try { mtimeMs = fs.statSync(f).mtimeMs; } catch { continue; }
        if (mtimeMs < cutoff) continue;
        const rel = path.relative(brainPath, f);
        const parts = rel.split(/[\\/]/);
        if (parts[0] === DIGEST_DIR) continue; // 소화 노트 자신은 재소화하지 않음
        // 폴더 키: 최대 2단계 (예: "Topics_Rag", "Coding/Python"). 루트 직속 파일은 ''.
        const folder = parts.length > 2 ? parts.slice(0, 2).join('/') : parts.length === 2 ? parts[0] : '';
        const arr = byFolder.get(folder) || [];
        arr.push({ file: f, mtimeMs });
        byFolder.set(folder, arr);
    }
    return Array.from(byFolder.entries())
        .map(([folder, items]) => {
            items.sort((a, b) => b.mtimeMs - a.mtimeMs);
            return {
                folder,
                slug: folderSlug(folder),
                files: items.slice(0, MAX_FILES_PER_TARGET).map(i => i.file),
                newestMtimeMs: items[0].mtimeMs,
            };
        })
        .sort((a, b) => b.files.length - a.files.length || b.newestMtimeMs - a.newestMtimeMs);
}

/** 소화 노트가 최신인지 — generated_at ≥ 소스 최신 mtime 이면 skip. */
export function isDigestFresh(digestFile: string, newestSourceMtimeMs: number): boolean {
    try {
        if (!fs.existsSync(digestFile)) return false;
        const head = fs.readFileSync(digestFile, 'utf8').slice(0, 600);
        const m = head.match(/^generated_at:\s*["']?([^"'\n]+)["']?\s*$/m);
        if (!m) return false;
        const t = Date.parse(m[1].trim());
        return Number.isFinite(t) && t >= newestSourceMtimeMs;
    } catch {
        return false;
    }
}

// ── 소화 노트 생성 ───────────────────────────────────────────────────────────

const DIGEST_SYSTEM = [
    '너는 지식 소화기다. 주어진 위키 문서들을 읽고, 나중에 사용자가 물어볼 법한 질문에 즉시 답할 수 있는 "소화 노트"를 만든다.',
    '형식 (마크다운):',
    '## 예상 질문과 답',
    '- **Q: <예상 질문>** — A: <원문 근거 기반 압축 답변. 출처 문서 제목을 [제목] 으로 표기>',
    '  (질문 4~7개 — 문서들이 실제로 답할 수 있는 것만)',
    '## 핵심 사실',
    '- <문서들에서 가장 중요한 사실·수치·결론. 출처 [제목] 표기>',
    '## 문서 간 연결',
    '- <문서들 사이의 관계·공통 주제·모순(있다면)>',
    '규칙: 원문에 없는 내용을 지어내지 마라. 모순이 있으면 숨기지 말고 명시하라. 한국어로.',
].join('\n');

function buildDigestInput(brainPath: string, target: DigestTarget): { input: string; sourceTitles: string[] } {
    const pieces: string[] = [];
    const titles: string[] = [];
    let used = 0;
    for (const f of target.files) {
        if (used >= MAX_INPUT_CHARS) break;
        let content = '';
        try { content = fs.readFileSync(f, 'utf8'); } catch { continue; }
        const title = path.basename(f, '.md');
        titles.push(title);
        const budget = Math.min(MAX_INPUT_CHARS - used, Math.floor(MAX_INPUT_CHARS / Math.min(target.files.length, 4)));
        const body = content.slice(0, budget);
        pieces.push(`=== [${title}] ===\n${body}`);
        used += body.length;
    }
    return { input: pieces.join('\n\n'), sourceTitles: titles };
}

export function digestMarkdown(target: DigestTarget, body: string, sourceTitles: string[], nowIso: string): string {
    const label = target.folder || '(두뇌 루트)';
    return [
        '---',
        'type: digest',
        `title: "소화 노트: ${label}"`,
        `generated_at: ${nowIso}`,
        `sources: [${sourceTitles.map(t => `"${t.replace(/"/g, "'")}"`).join(', ')}]`,
        '---',
        '',
        `# 소화 노트: ${label}`,
        '',
        `> ⚙️ 자동 생성 (sleep-time 사전 소화) — **원문이 항상 우선**입니다. 소스가 바뀌면 자동 재생성되며, 이 파일은 삭제해도 안전합니다.`,
        '',
        body.trim(),
        '',
    ].join('\n');
}

// ── 실행 ─────────────────────────────────────────────────────────────────────

/** 사전 소화 1회 실행. 반환: 요약 문자열 (생성/스킵 건수). */
export async function runSleepDigestOnce(): 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 digestDir = path.join(brainPath, DIGEST_DIR);
    const allFiles = findBrainFiles(brainPath);
    const targets = selectDigestTargets(brainPath, allFiles, Date.now());
    if (targets.length === 0) return '최근 변경 지식 없음 — 소화할 것 없음';

    fs.mkdirSync(digestDir, { recursive: true });
    const readmePath = path.join(digestDir, 'README.md');
    if (!fs.existsSync(readmePath)) {
        fs.writeFileSync(readmePath, [
            '# Digests — sleep-time 사전 소화 노트',
            '',
            '유휴 시간에 ASTRA 가 최근 변경된 두뇌 지식을 미리 읽고 만든 "learned context" 입니다.',
            '검색(RAG)이 원문과 함께 이 노트를 찾아 응답 품질·속도를 높입니다.',
            '**원문이 항상 우선** — 소스 변경 시 자동 재생성되고, 삭제해도 다음 실행 때 다시 만들어집니다.',
            '',
        ].join('\n'), 'utf8');
    }

    let made = 0, skipped = 0, failed = 0;
    for (const target of targets) {
        if (made >= MAX_PER_RUN) break;
        const digestFile = path.join(digestDir, `${target.slug}.md`);
        if (isDigestFresh(digestFile, target.newestMtimeMs)) { skipped++; continue; }
        try {
            const { input, sourceTitles } = buildDigestInput(brainPath, target);
            if (!input.trim()) { skipped++; continue; }
            const body = await simpleChatCompletion(
                DIGEST_SYSTEM,
                `[대상 폴더] ${target.folder || '(루트)'}\n\n${input}`,
                { baseUrl: config.ollamaUrl, model: config.defaultModel, temperature: 0.2, maxTokens: 1400, timeoutMs: 180000 },
            );
            if (!body.trim()) { failed++; continue; }
            fs.writeFileSync(digestFile, digestMarkdown(target, body, sourceTitles, new Date().toISOString()), 'utf8');
            made++;
            logInfo('Sleep digest 생성.', { folder: target.folder, files: target.files.length });
        } catch (e: any) {
            failed++;
            logError('Sleep digest 실패 (다음 대상 계속).', { folder: target.folder, error: e?.message ?? String(e) });
        }
    }
    const summary = `소화 ${made}건 생성 · ${skipped}건 최신(스킵)${failed ? ` · ${failed}건 실패` : ''}`;
    logInfo('Sleep digest 사이클 완료.', { summary });
    return summary;
}

// ── 일일 스케줄러 (KST, stocksWatcher 패턴) ──────────────────────────────────

function nowInKst(): { hour: number; minute: number; ymd: string } {
    const parts = new Intl.DateTimeFormat('en-US', {
        timeZone: 'Asia/Seoul', year: 'numeric', month: '2-digit', day: '2-digit',
        hour: '2-digit', minute: '2-digit', hour12: false,
    }).formatToParts(new Date());
    const get = (t: string) => parts.find(p => p.type === t)?.value || '00';
    return { hour: Number(get('hour')), minute: Number(get('minute')), ymd: `${get('year')}-${get('month')}-${get('day')}` };
}

function digestTime(): { hour: number; minute: number } {
    const raw = (vscode.workspace.getConfiguration('g1nation').get<string>('sleepDigest.time', '03:00') || '03:00').trim();
    const m = raw.match(/^(\d{1,2}):(\d{2})$/);
    return m ? { hour: Math.min(23, Number(m[1])), minute: Math.min(59, Number(m[2])) } : { hour: 3, minute: 0 };
}

function msUntilNextFire(): number {
    const { hour, minute, ymd } = nowInKst();
    const t = digestTime();
    const nowMin = hour * 60 + minute;
    const targetMin = t.hour * 60 + t.minute;
    if (targetMin > nowMin && _lastFiredYmd !== ymd) return (targetMin - nowMin) * 60_000;
    return ((24 * 60 - nowMin) + targetMin) * 60_000;
}

function scheduleNext(): void {
    if (_disposed) return;
    const ms = msUntilNextFire();
    logInfo('Sleep digest 다음 실행 예약.', { inHours: (ms / 3_600_000).toFixed(1) });
    _timer = setTimeout(async () => {
        const enabled = vscode.workspace.getConfiguration('g1nation').get<boolean>('sleepDigest.enabled', true);
        if (enabled) {
            _lastFiredYmd = nowInKst().ymd;
            try { await runSleepDigestOnce(); } catch (e: any) {
                logError('Sleep digest 사이클 실패.', { error: e?.message ?? String(e) });
            }
            // 같은 유휴 슬롯에 신규 지식 충돌 스캔도 수행 (쓰기 시점 정합성 게이트).
            try {
                const { runConflictScanOnce } = await import('./conflictScan');
                await runConflictScanOnce();
            } catch (e: any) {
                logError('충돌 스캔 실패 (무시).', { error: e?.message ?? String(e) });
            }
        }
        scheduleNext();
    }, ms);
}

/** VS Code 시작 시 호출 — disposable 반환. */
export function startSleepDigestWatcher(): vscode.Disposable {
    _disposed = false;
    scheduleNext();
    return new vscode.Disposable(() => {
        _disposed = true;
        if (_timer) { clearTimeout(_timer); _timer = undefined; }
    });
}
