/**
 * 기존 문서 정적 매칭 수동 실행 스크립트
 *
 * documents/{docId} onWrite 트리거가 발생하지 않은 기존 문서들에 대해
 * staticMatching Cloud Function의 매칭 로직을 오프라인으로 실행하여
 * ai_suggestions 서브컬렉션을 생성합니다.
 *
 * 사용법:
 *   cd /home/jay/projects/insuwiki/.worktrees/task-1724.1-dev5/nextapp
 *   npx ts-node ../scripts/triggerStaticMatching.ts --dry-run
 *   npx ts-node ../scripts/triggerStaticMatching.ts --limit 10
 *   npx ts-node ../scripts/triggerStaticMatching.ts
 *
 * Task 1724.1: 기존 문서 ai_suggestions 백필
 * 생성일: 2026-04-12
 */

import * as admin from 'firebase-admin';
import * as fs from 'fs';

// ============================================================
// Firebase Admin 초기화
// ============================================================
if (!admin.apps.length) {
    const keyPath = '/home/jay/.config/gcloud/service-accounts/insuwiki-j2h-fa603f4f75f5.json';
    if (fs.existsSync(keyPath)) {
        const serviceAccount = JSON.parse(fs.readFileSync(keyPath, 'utf8'));
        admin.initializeApp({
            credential: admin.credential.cert(serviceAccount),
            projectId: 'insuwiki-j2h',
        });
    } else {
        admin.initializeApp({ projectId: 'insuwiki-j2h' });
        console.warn('서비스 계정 키 파일 없음 — 기본 인증 사용 (gcloud auth)');
    }
}

const db = admin.firestore();

// ============================================================
// 타입 정의 (functions/src/staticMatching.ts에서 복사)
// ============================================================

type LinkMethod = 'manual' | 'static' | 'embedding' | 'semantic';
type LinkCreatedBy = 'user' | 'system' | 'ai';

interface InsuranceTerm {
    id: string;
    term: string;
    definition: string;
    commonAliases: string[];
    icdCodes?: string[];
    companyId: string;
    productId: string;
    pageNumber: number;
    verified: boolean;
    createdAt: FirebaseFirestore.Timestamp;
}

interface MatchResult {
    termId: string;
    term: string;
    method: LinkMethod;
    confidence: number;
    explanation: string;
}

interface NormalizeMap {
    [key: string]: string;
}

interface AiLinkingConfig {
    maxSuggestions: number;
    minConfidence: number;
    enabled: boolean;
}

// ============================================================
// findMatchingTerms 순수 함수 (functions/src/staticMatching.ts에서 복사)
// ============================================================

function findMatchingTerms(
    content: string,
    terms: InsuranceTerm[],
    existingLinkIds: string[],
    normalizeMap: NormalizeMap = {}
): MatchResult[] {
    const results: MatchResult[] = [];
    const contentLower = content.toLowerCase();
    const contentNoSpace = contentLower.replace(/\s/g, '');
    const seenTermIds = new Set<string>();
    const excludeIds = new Set(existingLinkIds);

    for (const term of terms) {
        if (excludeIds.has(term.id)) continue;
        if (seenTermIds.has(term.id)) continue;

        const termLower = term.term.toLowerCase();

        // 1. exact match → confidence: 100
        if (contentLower.includes(termLower)) {
            results.push({
                termId: term.id,
                term: term.term,
                method: 'static',
                confidence: 100,
                explanation: `exact match: "${term.term}"`,
            });
            seenTermIds.add(term.id);
            continue;
        }

        // 2. alias match (commonAliases) → confidence: 90
        let aliasMatched = false;
        for (const alias of term.commonAliases) {
            const aliasLower = alias.toLowerCase();
            if (contentLower.includes(aliasLower)) {
                results.push({
                    termId: term.id,
                    term: term.term,
                    method: 'static',
                    confidence: 90,
                    explanation: `alias match: "${alias}" → "${term.term}"`,
                });
                seenTermIds.add(term.id);
                aliasMatched = true;
                break;
            }
        }
        if (aliasMatched) continue;

        let normalizedTermLower = termLower;
        if (normalizeMap[termLower]) {
            normalizedTermLower = normalizeMap[termLower].toLowerCase();
        }
        const normalizedNoSpace = normalizedTermLower.replace(/\s/g, '');

        // 3. 공백 제거 후 match → confidence: 85
        if (normalizedNoSpace.length > 0 && contentNoSpace.includes(normalizedNoSpace)) {
            results.push({
                termId: term.id,
                term: term.term,
                method: 'static',
                confidence: 85,
                explanation: `공백 제거 match: "${term.term}"`,
            });
            seenTermIds.add(term.id);
            continue;
        }

        // 4. substring 포함 체크 → confidence: 70 (3자 이상 용어만)
        if (termLower.length >= 3) {
            const contentWords = contentLower.split(/[\s.,!?;:()[\]{}'"]/);
            const substringMatched = contentWords.some(word => {
                if (word.length < 2) return false;
                return termLower.startsWith(word) && word.length >= 2;
            });
            if (substringMatched) {
                results.push({
                    termId: term.id,
                    term: term.term,
                    method: 'static',
                    confidence: 70,
                    explanation: `substring match: "${term.term}"`,
                });
                seenTermIds.add(term.id);
            }
        }
    }

    return results;
}

// ============================================================
// 커맨드라인 인수 파싱
// ============================================================

const args = process.argv.slice(2);
const isDryRun = args.includes('--dry-run');
const limitIdx = args.indexOf('--limit');
const limitN = limitIdx !== -1 && args[limitIdx + 1]
    ? parseInt(args[limitIdx + 1], 10)
    : null;

// ============================================================
// 메인 실행 함수
// ============================================================

async function run(): Promise<void> {
    console.log('\n=== InsuWiki 정적 매칭 수동 실행 스크립트 ===');
    if (isDryRun) console.log('[DRY-RUN 모드] Firestore에 저장하지 않습니다.');
    if (limitN !== null) console.log(`[LIMIT] 최대 ${limitN}개 문서 처리`);
    console.log('');

    // ── 1. insurance_terms 전체 로드 ────────────────────────
    console.log('1. insurance_terms 컬렉션 로드 중...');
    const terms: InsuranceTerm[] = [];
    const termsSnap = await db.collection('insurance_terms').get();
    termsSnap.forEach(doc => {
        terms.push({ id: doc.id, ...doc.data() } as InsuranceTerm);
    });
    console.log(`   → ${terms.length}개 용어 로드 완료`);

    if (terms.length === 0) {
        console.error('insurance_terms가 없습니다. 종료합니다.');
        process.exit(1);
    }

    // ── 2. config/aiLinking 로드 ────────────────────────────
    console.log('2. config/aiLinking 로드 중...');
    const defaultConfig: AiLinkingConfig = {
        maxSuggestions: 5,
        minConfidence: 70,
        enabled: true,
    };
    let config = defaultConfig;
    try {
        const configDoc = await db.collection('config').doc('aiLinking').get();
        if (configDoc.exists) {
            config = { ...defaultConfig, ...configDoc.data() } as AiLinkingConfig;
        }
    } catch (err) {
        console.warn('   config/aiLinking 로드 실패, 기본값 사용:', err);
    }
    console.log(`   → maxSuggestions=${config.maxSuggestions}, minConfidence=${config.minConfidence}, enabled=${config.enabled}`);

    if (!config.enabled) {
        console.log('AI linking이 비활성화되어 있습니다 (enabled=false). 종료합니다.');
        process.exit(0);
    }

    // ── 3. config/normalizeMap 로드 ─────────────────────────
    console.log('3. config/normalizeMap 로드 중...');
    let normalizeMap: NormalizeMap = {};
    try {
        const nmDoc = await db.collection('config').doc('normalizeMap').get();
        normalizeMap = nmDoc.exists ? (nmDoc.data() as NormalizeMap) : {};
    } catch (err) {
        console.warn('   normalizeMap 로드 실패, 빈 맵 사용:', err);
    }
    console.log(`   → ${Object.keys(normalizeMap).length}개 정규화 규칙 로드 완료`);

    // ── 4. documents 컬렉션에서 content가 있는 문서 로드 ────
    console.log('4. documents 컬렉션 로드 중...');
    const allDocs: Array<{ id: string; title: string; content: string; outgoingLinkIds: string[] }> = [];
    const docsSnap = await db.collection('documents').get();
    for (const doc of docsSnap.docs) {
        const data = doc.data();
        const content: string = data.content || '';
        if (!content.trim()) continue;
        allDocs.push({
            id: doc.id,
            title: data.title || '',
            content,
            outgoingLinkIds: data.outgoingLinkIds || [],
        });
    }

    const documents = limitN !== null ? allDocs.slice(0, limitN) : allDocs;
    console.log(`   → content 있는 문서 ${allDocs.length}개 중 ${documents.length}개 처리 대상`);
    console.log('');

    // ── 5. 문서별 매칭 실행 및 저장 ─────────────────────────
    console.log('5. 문서별 매칭 실행 중...');

    let totalSaved = 0;
    let totalSkipped = 0;
    let processedCount = 0;

    for (const doc of documents) {
        processedCount++;
        const docLabel = `[${processedCount}/${documents.length}] "${doc.title}" (${doc.id})`;

        // a. 기존 ai_suggestions 로드 (dismissed 체크)
        const existingSuggestionsSnap = await db
            .collection('documents')
            .doc(doc.id)
            .collection('ai_suggestions')
            .get();

        const dismissedTermIds = new Set<string>();
        existingSuggestionsSnap.forEach(suggDoc => {
            if (suggDoc.data().dismissed === true) {
                dismissedTermIds.add(suggDoc.id); // doc.id = termId
            }
        });

        // b. findMatchingTerms 실행
        const matchResults = findMatchingTerms(
            doc.content,
            terms,
            doc.outgoingLinkIds,
            normalizeMap
        );

        // c. dismissed 및 minConfidence 필터링 + maxSuggestions 제한
        const filtered = matchResults
            .filter(r => !dismissedTermIds.has(r.termId))
            .filter(r => r.confidence >= config.minConfidence)
            .sort((a, b) => b.confidence - a.confidence)
            .slice(0, config.maxSuggestions);

        if (filtered.length === 0) {
            console.log(`  ${docLabel} → 유효한 매칭 없음, 스킵`);
            totalSkipped++;
            continue;
        }

        // d. 매칭된 term의 title로 대상 documents 찾기
        const suggestionPromises = filtered.map(async (matchResult) => {
            const targetSnap = await db
                .collection('documents')
                .where('title', '==', matchResult.term)
                .limit(1)
                .get();

            if (targetSnap.empty) return null;

            const targetDoc = targetSnap.docs[0];
            const targetDocId = targetDoc.id;

            // 자기 자신 링크 제외
            if (targetDocId === doc.id) return null;

            // 기존 수동 링크 제외
            if (doc.outgoingLinkIds.includes(targetDocId)) return null;

            return { matchResult, targetDocId, targetTitle: matchResult.term };
        });

        const resolved = (await Promise.all(suggestionPromises))
            .filter((s): s is NonNullable<typeof s> => s !== null);

        if (resolved.length === 0) {
            console.log(`  ${docLabel} → 대상 문서 없음, 스킵`);
            totalSkipped++;
            continue;
        }

        // e. ai_suggestions 서브컬렉션에 저장
        if (!isDryRun) {
            const batch = db.batch();
            const suggestionsRef = db
                .collection('documents')
                .doc(doc.id)
                .collection('ai_suggestions');

            let batchCount = 0;
            for (const suggestion of resolved) {
                if (dismissedTermIds.has(suggestion.matchResult.termId)) continue;

                const suggDocRef = suggestionsRef.doc(suggestion.matchResult.termId);
                batch.set(suggDocRef, {
                    targetDocId: suggestion.targetDocId,
                    targetTitle: suggestion.targetTitle,
                    method: 'static' as LinkMethod,
                    confidence: suggestion.matchResult.confidence,
                    createdBy: 'system' as LinkCreatedBy,
                    explanation: suggestion.matchResult.explanation,
                    dismissed: false,
                    createdAt: admin.firestore.Timestamp.now(),
                }, { merge: true });
                batchCount++;
            }

            if (batchCount > 0) {
                await batch.commit();
                totalSaved += batchCount;
                console.log(`  ${docLabel} → ${batchCount}개 ai_suggestions 저장`);
            }
        } else {
            // dry-run: 결과만 출력
            totalSaved += resolved.length;
            console.log(`  ${docLabel} → [DRY-RUN] ${resolved.length}개 ai_suggestions 예정:`);
            for (const s of resolved) {
                console.log(`    - "${s.matchResult.term}" (targetDocId: ${s.targetDocId}, confidence: ${s.matchResult.confidence})`);
            }
        }
    }

    // ── 6. 통계 출력 ─────────────────────────────────────────
    console.log('\n=== 실행 완료 ===');
    console.log(`처리 문서 수  : ${processedCount}`);
    console.log(`생성된 ai_suggestions: ${totalSaved}${isDryRun ? ' (dry-run, 미저장)' : ''}`);
    console.log(`스킵 문서 수  : ${totalSkipped}`);
    console.log('');
}

// ============================================================
// 실행
// ============================================================
run()
    .then(() => process.exit(0))
    .catch(err => {
        console.error('\n실행 실패:', err);
        process.exit(1);
    });
