/**
 * InsuWiki Phase 3: Claude Code 요약 파이프라인 CLI
 *
 * 서브커맨드:
 *   list              - pending/failed summary_jobs 조회
 *   read <jobId>      - 청크 읽기 및 섹션별 텍스트 출력 [--output <path>]
 *   save <jobId>      - 요약 JSON 파일로 insurance_summaries 저장 --input <file>
 *   complete <jobId>  - 수동으로 summary_jobs status를 'complete'로 변경
 *
 * 실행:
 *   NODE_PATH=./nextapp/node_modules npx ts-node scripts/summary-pipeline.ts <subcommand> [args]
 *
 * 또는 프로젝트 루트에 package.json scripts 추가 후:
 *   npm run summary-pipeline -- <subcommand> [args]
 *
 * NOTE: firebase-admin 패키지는 nextapp/node_modules에 있습니다.
 *       NODE_PATH=./nextapp/node_modules 환경변수로 실행하거나,
 *       dotenv.config 전에 require('module').globalPaths 를 수정하세요.
 */

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

// 루트 .env.local / .env 로드 (존재하는 경우)
dotenv.config({ path: path.join(__dirname, '../.env.local') });
dotenv.config({ path: path.join(__dirname, '../.env') });
// nextapp/.env.local 로드 (firebase-admin 등 패키지가 nextapp에 설치되어 있으므로 함께 참조)
dotenv.config({ path: path.join(__dirname, '../nextapp/.env.local') });
dotenv.config({ path: path.join(__dirname, '../nextapp/.env') });

// ── Firebase Admin 초기화 ────────────────────────────────────────────────────
// 인증 우선순위:
//   1. GOOGLE_SERVICE_ACCOUNT_KEY (JSON 문자열)
//   2. GOOGLE_APPLICATION_CREDENTIALS (서비스 계정 파일 경로) → ADC
//   3. FIREBASE_PROJECT_ID 만 있을 경우 → ADC (gcloud auth application-default)
if (!admin.apps.length) {
    const serviceAccountKey = process.env.GOOGLE_SERVICE_ACCOUNT_KEY;
    const projectId =
        process.env.FIREBASE_PROJECT_ID ||
        process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID;

    try {
        if (serviceAccountKey) {
            // 방법 1: JSON 문자열로 직접 초기화
            admin.initializeApp({
                credential: admin.credential.cert(JSON.parse(serviceAccountKey)),
                projectId,
            });
        } else if (process.env.GOOGLE_APPLICATION_CREDENTIALS) {
            // 방법 2: 서비스 계정 파일 경로로 초기화
            admin.initializeApp({
                credential: admin.credential.applicationDefault(),
                projectId,
            });
        } else {
            // 방법 3: ADC (gcloud auth application-default login 필요)
            console.warn(
                '⚠️ GOOGLE_SERVICE_ACCOUNT_KEY 없음 → Application Default Credentials(ADC) 시도',
            );
            admin.initializeApp({
                credential: admin.credential.applicationDefault(),
                projectId,
            });
        }
    } catch (err) {
        console.error('❌ Firebase Admin 초기화 실패:', err);
        console.error(
            '   GOOGLE_SERVICE_ACCOUNT_KEY 또는 GOOGLE_APPLICATION_CREDENTIALS 환경 변수를 설정하세요.',
        );
        process.exit(1);
    }
}

const db = admin.firestore();

// ── 타입 정의 ────────────────────────────────────────────────────────────────
type SummaryJobStatus = 'pending' | 'processing' | 'complete' | 'failed';
type SummaryLevel = 1 | 2 | 3;

interface SummaryJob {
    id: string;
    productId: string;
    companyId: string;
    companyName: string;
    productName: string;
    driveFileId: string;
    chunksCount: number;
    status: SummaryJobStatus;
    error?: string;
    createdAt: admin.firestore.Timestamp;
    updatedAt: admin.firestore.Timestamp;
}

interface InsuranceChunk {
    id: string;
    productId: string;
    companyId: string;
    pageNumber: number;
    chunkText: string;
    [key: string]: unknown;
}

interface SectionInfo {
    title: string;
    startPage: number;
    endPage: number;
    text: string;
}

// save 커맨드에서 사용하는 입력 JSON 포맷
interface SummaryInputJson {
    level1: string;
    level2: Array<{
        sectionTitle: string;
        content: string;
        pageRange: { start: number; end: number };
    }>;
}

// ── 상수 ─────────────────────────────────────────────────────────────────────
const COLLECTIONS = {
    SUMMARY_JOBS: 'summary_jobs',
    INSURANCE_CHUNKS: 'insurance_chunks',
    INSURANCE_SUMMARIES: 'insurance_summaries',
} as const;

// 7일(밀리초)
const STALE_THRESHOLD_MS = 7 * 24 * 60 * 60 * 1000;

// 섹션 감지 패턴: 제n관, 제n장, 제n절, 부칙
const SECTION_PATTERNS = [
    /^제\s*\d+\s*관\s*\S*/,  // 제1관 총칙 (제목이 다음 줄일 수도 있음)
    /^제\s*\d+\s*장\s*\S*/,  // 제1장 총칙
    /^제\s*\d+\s*절\s*\S*/,  // 제1절 총칙
    /^부\s*칙/,               // 부칙
];

// ── 유틸: 청크 텍스트에서 섹션 제목 감지 ─────────────────────────────────────
function detectSectionTitle(text: string): string | null {
    const lines = text.split('\n');
    for (const line of lines) {
        const trimmed = line.trim();
        if (!trimmed) continue;
        for (const pattern of SECTION_PATTERNS) {
            if (pattern.test(trimmed)) {
                return trimmed;
            }
        }
    }
    return null;
}

// ── 유틸: Timestamp → Date ───────────────────────────────────────────────────
function toDate(ts: admin.firestore.Timestamp): Date {
    return ts.toDate();
}

// ── 서브커맨드: list ──────────────────────────────────────────────────────────
async function cmdList(): Promise<void> {
    console.log('🔍 summary_jobs 조회 중 (status: pending | failed)...\n');

    let snapshot: admin.firestore.QuerySnapshot;
    try {
        snapshot = await db
            .collection(COLLECTIONS.SUMMARY_JOBS)
            .where('status', 'in', ['pending', 'failed'])
            .orderBy('createdAt', 'asc')
            .get();
    } catch (err) {
        console.error('❌ Firestore 조회 실패:', err);
        process.exit(1);
    }

    if (snapshot.empty) {
        console.log('✅ 처리 대기 중인 summary_jobs 없음.');
        return;
    }

    interface JobListItem {
        id: string;
        productId: string;
        companyName: string;
        productName: string;
        chunksCount: number;
        status: SummaryJobStatus;
        createdAt: string;
        stale: boolean;
    }

    const now = Date.now();
    const results: JobListItem[] = snapshot.docs.map(
        (doc: admin.firestore.QueryDocumentSnapshot) => {
            const data = doc.data() as Omit<SummaryJob, 'id'>;
            const createdAt = data.createdAt ? toDate(data.createdAt) : new Date(0);
            const ageMs = now - createdAt.getTime();
            const isStale = data.status === 'pending' && ageMs >= STALE_THRESHOLD_MS;

            return {
                id: doc.id,
                productId: data.productId,
                companyName: data.companyName,
                productName: data.productName,
                chunksCount: data.chunksCount,
                status: data.status,
                createdAt: createdAt.toISOString(),
                stale: isStale,
            };
        },
    );

    // 콘솔 요약 출력
    results.forEach((job: JobListItem) => {
        const staleTag = job.stale ? ' ⚠️ STALE' : '';
        console.log(`[${job.status.toUpperCase()}]${staleTag} ${job.id}`);
        console.log(
            `  회사: ${job.companyName}  상품: ${job.productName}  청크: ${job.chunksCount}개  생성: ${job.createdAt}`,
        );
    });

    // JSON 배열 출력 (stale 필드 제외)
    console.log('\n--- JSON 출력 ---');
    const jsonResults = results.map((item: JobListItem) => {
        const { stale: _stale, ...rest } = item;
        void _stale;
        return rest;
    });
    console.log(JSON.stringify(jsonResults, null, 2));
}

// ── 서브커맨드: read ──────────────────────────────────────────────────────────
async function cmdRead(jobId: string, outputPath: string | null): Promise<void> {
    // 1. summary_jobs 문서 조회
    let jobDoc: admin.firestore.DocumentSnapshot;
    try {
        jobDoc = await db.collection(COLLECTIONS.SUMMARY_JOBS).doc(jobId).get();
    } catch (err) {
        console.error('❌ Firestore 조회 실패:', err);
        process.exit(1);
    }

    if (!jobDoc.exists) {
        console.error(`❌ Job not found: ${jobId}`);
        process.exit(1);
    }

    const job = { id: jobDoc.id, ...jobDoc.data() } as SummaryJob;
    const { productId, status } = job;

    console.log(`📄 Job: ${jobId}  상품: ${job.companyName} / ${job.productName}  상태: ${status}`);

    // 2. insurance_chunks 조회 (pageNumber 오름차순)
    let chunksSnapshot: admin.firestore.QuerySnapshot;
    try {
        chunksSnapshot = await db
            .collection(COLLECTIONS.INSURANCE_CHUNKS)
            .where('productId', '==', productId)
            .orderBy('pageNumber', 'asc')
            .get();
    } catch (err) {
        console.error('❌ insurance_chunks 조회 실패:', err);
        process.exit(1);
    }

    if (chunksSnapshot.empty) {
        const errMsg = `No chunks found for product: ${productId}`;
        console.error(`❌ ${errMsg}`);
        // 청크 없음 → job status를 failed로 업데이트
        await db.collection(COLLECTIONS.SUMMARY_JOBS).doc(jobId).update({
            status: 'failed',
            error: errMsg,
            updatedAt: admin.firestore.FieldValue.serverTimestamp(),
        });
        process.exit(1);
    }

    const chunks = chunksSnapshot.docs.map(
        (doc: admin.firestore.QueryDocumentSnapshot) => ({
            id: doc.id,
            ...(doc.data() as Omit<InsuranceChunk, 'id'>),
        }),
    ) as InsuranceChunk[];

    console.log(`✅ 총 ${chunks.length}개 청크 로드됨 (pageNumber 순)`);

    // 3. status를 'processing'으로 업데이트 (pending인 경우만)
    if (status === 'pending') {
        try {
            await db.collection(COLLECTIONS.SUMMARY_JOBS).doc(jobId).update({
                status: 'processing',
                updatedAt: admin.firestore.FieldValue.serverTimestamp(),
            });
            console.log(`🔄 Job status: pending → processing`);
        } catch (err) {
            console.error('⚠️ status 업데이트 실패 (계속 진행):', err);
        }
    }

    // 4. 섹션 감지 및 텍스트 구성
    const sections: SectionInfo[] = [];
    let currentSection: SectionInfo | null = null;

    for (const chunk of chunks) {
        const sectionTitle = detectSectionTitle(chunk.chunkText);

        if (sectionTitle) {
            // 이전 섹션 종료
            if (currentSection) {
                currentSection.endPage = chunk.pageNumber - 1;
                sections.push(currentSection);
            }
            // 새 섹션 시작
            currentSection = {
                title: sectionTitle,
                startPage: chunk.pageNumber,
                endPage: chunk.pageNumber,
                text: chunk.chunkText,
            };
        } else {
            if (currentSection) {
                currentSection.endPage = chunk.pageNumber;
                currentSection.text += '\n\n' + chunk.chunkText;
            } else {
                // 첫 번째 섹션 헤더 이전 청크들 → "서문" 섹션
                currentSection = {
                    title: '서문',
                    startPage: chunk.pageNumber,
                    endPage: chunk.pageNumber,
                    text: chunk.chunkText,
                };
            }
        }
    }

    // 마지막 섹션 저장
    if (currentSection) {
        sections.push(currentSection);
    }

    // 섹션이 전혀 감지되지 않은 경우: 전체를 하나의 섹션으로
    if (sections.length === 0) {
        const allText = chunks.map((c) => c.chunkText).join('\n\n');
        const firstPage = chunks[0]?.pageNumber ?? 1;
        const lastPage = chunks[chunks.length - 1]?.pageNumber ?? 1;
        sections.push({
            title: '전체',
            startPage: firstPage,
            endPage: lastPage,
            text: allText,
        });
    }

    // 5. 출력 문자열 생성
    const outputLines: string[] = [];
    sections.forEach((section, idx) => {
        const header = `=== 섹션 ${idx + 1}: ${section.title} (Page ${section.startPage}-${section.endPage}) ===`;
        outputLines.push(header);
        outputLines.push(section.text);
        outputLines.push('');
    });
    const outputText = outputLines.join('\n');

    // 6. stdout 출력
    process.stdout.write('\n' + outputText + '\n');

    // 7. 파일 저장 (--output 옵션 지정 시)
    if (outputPath) {
        try {
            fs.writeFileSync(outputPath, outputText, 'utf-8');
            console.log(`\n💾 섹션 텍스트 저장 완료: ${outputPath}`);
        } catch (err) {
            console.error(`❌ 파일 저장 실패 (${outputPath}):`, err);
            process.exit(1);
        }
    }

    console.log(`✅ read 완료. 감지된 섹션 수: ${sections.length}`);
}

// ── 서브커맨드: save ──────────────────────────────────────────────────────────
async function cmdSave(jobId: string, inputFile: string): Promise<void> {
    // 1. 입력 파일 존재 확인
    if (!fs.existsSync(inputFile)) {
        console.error(`❌ 입력 파일을 찾을 수 없습니다: ${inputFile}`);
        process.exit(1);
    }

    // 2. JSON 파싱
    let summaryInput: SummaryInputJson;
    try {
        const raw = fs.readFileSync(inputFile, 'utf-8');
        summaryInput = JSON.parse(raw) as SummaryInputJson;
    } catch (err) {
        console.error(`❌ JSON 파싱 실패 (${inputFile}):`, err);
        process.exit(1);
    }

    if (!summaryInput.level1 || typeof summaryInput.level1 !== 'string') {
        console.error('❌ JSON 형식 오류: level1 필드(string)가 필요합니다.');
        process.exit(1);
    }
    if (!Array.isArray(summaryInput.level2)) {
        console.error('❌ JSON 형식 오류: level2 필드(array)가 필요합니다.');
        process.exit(1);
    }

    // 3. summary_jobs 문서 조회
    let jobDoc: admin.firestore.DocumentSnapshot;
    try {
        jobDoc = await db.collection(COLLECTIONS.SUMMARY_JOBS).doc(jobId).get();
    } catch (err) {
        console.error('❌ Firestore 조회 실패:', err);
        process.exit(1);
    }

    if (!jobDoc.exists) {
        console.error(`❌ Job not found: ${jobId}`);
        process.exit(1);
    }

    const job = { id: jobDoc.id, ...jobDoc.data() } as SummaryJob;
    const { productId, companyId } = job;

    console.log(`💾 저장 시작: ${job.companyName} / ${job.productName} (${productId})`);

    try {
        // 4. 기존 insurance_summaries 문서 삭제 (idempotent)
        console.log('🗑️  기존 insurance_summaries 삭제 중...');
        const existingSnapshot = await db
            .collection(COLLECTIONS.INSURANCE_SUMMARIES)
            .where('productId', '==', productId)
            .get();

        if (existingSnapshot.docs.length > 0) {
            const deletePromises = existingSnapshot.docs.map(
                (doc: admin.firestore.QueryDocumentSnapshot) => doc.ref.delete(),
            );
            await Promise.all(deletePromises);
            console.log(`   기존 ${existingSnapshot.docs.length}개 문서 삭제 완료`);
        } else {
            console.log('   삭제할 기존 문서 없음');
        }

        const now = admin.firestore.FieldValue.serverTimestamp();

        // 5. Level 1 요약 저장 (전체 상품 요약)
        const level1DocRef = db.collection(COLLECTIONS.INSURANCE_SUMMARIES).doc();
        await level1DocRef.set({
            productId,
            companyId,
            level: 1 as SummaryLevel,
            content: summaryInput.level1,
            createdAt: now,
            updatedAt: now,
        });
        console.log(`✅ Level 1 요약 저장 완료 (id: ${level1DocRef.id})`);

        // 6. Level 2 요약 저장 (섹션별)
        for (let idx = 0; idx < summaryInput.level2.length; idx++) {
            const section = summaryInput.level2[idx];
            const docRef = db.collection(COLLECTIONS.INSURANCE_SUMMARIES).doc();
            await docRef.set({
                productId,
                companyId,
                level: 2 as SummaryLevel,
                sectionTitle: section.sectionTitle,
                content: section.content,
                pageRange: section.pageRange,
                createdAt: now,
                updatedAt: now,
            });
            console.log(
                `✅ Level 2 섹션 ${idx + 1}/${summaryInput.level2.length} 저장: "${section.sectionTitle}" (id: ${docRef.id})`,
            );
        }

        // 7. summary_jobs status를 'complete'로 업데이트
        await db.collection(COLLECTIONS.SUMMARY_JOBS).doc(jobId).update({
            status: 'complete',
            updatedAt: now,
        });

        console.log(`\n✅ save 완료! Job ${jobId} → status: complete`);
        console.log(`   Level 1: 1개, Level 2: ${summaryInput.level2.length}개 저장됨`);

    } catch (err) {
        console.error('❌ 저장 중 오류 발생:', err);

        // 에러 발생 시 status를 'failed'로 업데이트
        try {
            await db.collection(COLLECTIONS.SUMMARY_JOBS).doc(jobId).update({
                status: 'failed',
                error: err instanceof Error ? err.message : String(err),
                updatedAt: admin.firestore.FieldValue.serverTimestamp(),
            });
            console.error(`   Job ${jobId} status → failed`);
        } catch (updateErr) {
            console.error('   status failed 업데이트도 실패:', updateErr);
        }

        process.exit(1);
    }
}

// ── 서브커맨드: complete ──────────────────────────────────────────────────────
async function cmdComplete(jobId: string): Promise<void> {
    let jobDoc: admin.firestore.DocumentSnapshot;
    try {
        jobDoc = await db.collection(COLLECTIONS.SUMMARY_JOBS).doc(jobId).get();
    } catch (err) {
        console.error('❌ Firestore 조회 실패:', err);
        process.exit(1);
    }

    if (!jobDoc.exists) {
        console.error(`❌ Job not found: ${jobId}`);
        process.exit(1);
    }

    const prevStatus = (jobDoc.data() as SummaryJob).status;

    try {
        await db.collection(COLLECTIONS.SUMMARY_JOBS).doc(jobId).update({
            status: 'complete',
            updatedAt: admin.firestore.FieldValue.serverTimestamp(),
        });
        console.log(`✅ Job ${jobId}: ${prevStatus} → complete (수동 완료 처리)`);
    } catch (err) {
        console.error('❌ status 업데이트 실패:', err);
        process.exit(1);
    }
}

// ── 도움말 출력 ───────────────────────────────────────────────────────────────
function printUsage(): void {
    process.stdout.write(`
InsuWiki Phase 3: Claude Code 요약 파이프라인 CLI

사용법:
  NODE_PATH=./nextapp/node_modules npx ts-node scripts/summary-pipeline.ts <subcommand> [options]

서브커맨드:
  list
      pending/failed 상태의 summary_jobs 조회
      7일 이상 경과한 pending 항목은 "⚠️ STALE" 표시

  read <jobId> [--output <path>]
      지정 job의 insurance_chunks를 섹션별로 정리하여 stdout 출력
      --output <path>  섹션 텍스트를 파일로도 저장
      * pending 상태일 때만 status를 'processing'으로 업데이트

  save <jobId> --input <file>
      요약 JSON 파일을 읽어 insurance_summaries에 저장
      JSON 형식:
        {
          "level1": "전체 상품 요약 텍스트...",
          "level2": [
            { "sectionTitle": "제1관 총칙", "content": "...", "pageRange": { "start": 1, "end": 15 } },
            ...
          ]
        }
      * 기존 해당 productId의 summaries 삭제 후 재저장 (idempotent)
      * 성공 시 status → 'complete', 실패 시 status → 'failed'

  complete <jobId>
      summary_jobs status를 'complete'로 수동 변경

예시:
  NODE_PATH=./nextapp/node_modules npx ts-node scripts/summary-pipeline.ts list
  NODE_PATH=./nextapp/node_modules npx ts-node scripts/summary-pipeline.ts read job_abc123
  NODE_PATH=./nextapp/node_modules npx ts-node scripts/summary-pipeline.ts read job_abc123 --output /tmp/sections.txt
  NODE_PATH=./nextapp/node_modules npx ts-node scripts/summary-pipeline.ts save job_abc123 --input /tmp/summaries.json
  NODE_PATH=./nextapp/node_modules npx ts-node scripts/summary-pipeline.ts complete job_abc123
`);
}

// ── 메인 진입점 ──────────────────────────────────────────────────────────────
async function main(): Promise<void> {
    const args = process.argv.slice(2);
    const subcommand = args[0];

    if (!subcommand || subcommand === '--help' || subcommand === '-h') {
        printUsage();
        process.exit(0);
    }

    switch (subcommand) {
        case 'list': {
            await cmdList();
            break;
        }

        case 'read': {
            const jobId = args[1];
            if (!jobId || jobId.startsWith('--')) {
                console.error('❌ 사용법: summary-pipeline.ts read <jobId> [--output <path>]');
                process.exit(1);
            }

            // --output <path> 파싱
            let outputPath: string | null = null;
            const outputFlagIdx = args.indexOf('--output');
            if (outputFlagIdx !== -1) {
                outputPath = args[outputFlagIdx + 1] ?? null;
                if (!outputPath) {
                    console.error('❌ --output 옵션에 파일 경로를 지정하세요.');
                    process.exit(1);
                }
            }

            await cmdRead(jobId, outputPath);
            break;
        }

        case 'save': {
            const jobId = args[1];
            if (!jobId || jobId.startsWith('--')) {
                console.error('❌ 사용법: summary-pipeline.ts save <jobId> --input <file>');
                process.exit(1);
            }

            const inputFlagIdx = args.indexOf('--input');
            if (inputFlagIdx === -1) {
                console.error('❌ --input <file> 옵션이 필요합니다.');
                process.exit(1);
            }

            const inputFile = args[inputFlagIdx + 1];
            if (!inputFile) {
                console.error('❌ --input 옵션에 파일 경로를 지정하세요.');
                process.exit(1);
            }

            await cmdSave(jobId, inputFile);
            break;
        }

        case 'complete': {
            const jobId = args[1];
            if (!jobId || jobId.startsWith('--')) {
                console.error('❌ 사용법: summary-pipeline.ts complete <jobId>');
                process.exit(1);
            }

            await cmdComplete(jobId);
            break;
        }

        default: {
            console.error(`❌ 알 수 없는 서브커맨드: ${subcommand}`);
            printUsage();
            process.exit(1);
        }
    }

    process.exit(0);
}

main().catch((err) => {
    console.error('❌ 예기치 않은 오류:', err);
    process.exit(1);
});
