/**
 * generateYoutubeChannelInsight: 채널별 L2 요약 집약 → L1 채널 통합 인사이트 생성
 *
 * 흐름:
 * 1. youtube_channels에서 활성 채널 목록 조회
 * 2. 각 채널별 youtube_summaries (level: 'L2') 문서 조회 (최신 20개)
 * 3. L2 요약이 3개 미만이면 해당 채널 스킵
 * 4. 기존 L1이 있으면: 가장 최근 L1의 createdAt 이후에 새 L2가 추가된 경우에만 재생성
 * 5. L2 요약들을 결합하여 Gemini에 L1 프롬프트 전달
 * 6. 결과를 youtube_summaries에 저장 (level: 'L1', channelId, channelName, summary, embedding, videoCount, createdAt)
 */

import * as admin from 'firebase-admin';
import { onRequest } from 'firebase-functions/v2/https';
import { GoogleGenerativeAI } from '@google/generative-ai';

// ── Gemini API 지수 백오프 래퍼 ──────────────────────────────────────────
async function withBackoff<T>(
    fn: () => Promise<T>,
    maxRetries = 4,
    baseDelayMs = 5000
): Promise<T> {
    let lastErr: unknown;
    for (let attempt = 0; attempt <= maxRetries; attempt++) {
        try {
            return await fn();
        } catch (err: any) {
            lastErr = err;
            if (attempt === maxRetries) break;
            const delay = baseDelayMs * Math.pow(2, attempt) + Math.random() * 1000;
            console.warn(`  ⚠️ Gemini 오류 (시도 ${attempt + 1}/${maxRetries}), ${Math.round(delay / 1000)}초 후 재시도...`, err?.message);
            await new Promise(r => setTimeout(r, delay));
        }
    }
    throw lastErr;
}

// ── L1 통합 인사이트 프롬프트 생성 ───────────────────────────────────────
function buildL1Prompt(channelName: string, l2Summaries: string[]): string {
    const combinedL2 = l2Summaries
        .map((s, i) => `[영상 요약 ${i + 1}]\n${s}`)
        .join('\n\n---\n\n');

    return `당신은 보험 유튜브 채널 분석 전문가입니다.
아래는 "${channelName}" 채널의 최근 영상들의 구조화 요약(L2)입니다.
이 채널의 전체적인 콘텐츠 특성과 인사이트를 통합 분석하세요.

[분석 항목]
## 채널 핵심 주제
이 채널이 주로 다루는 보험 분야와 핵심 주제를 정리합니다.

## 반복 등장 키워드/상품
여러 영상에서 반복적으로 언급되는 보험 상품, 보험사, 약관 조항을 정리합니다.

## 채널 관점/논조
이 채널의 전반적인 관점(소비자 옹호, 업계 분석, 실무 팁 등)을 분석합니다.

## 설계사 활용 가이드
이 채널의 콘텐츠를 설계사가 업무에 활용하는 방법을 제안합니다.

## 주의 필요 콘텐츠
약관과 상충하거나 주의가 필요한 콘텐츠 경향을 정리합니다.
없으면 "특별한 주의사항 없음"으로 표기합니다.

⚠️ 규칙:
- 원본 L2 요약에 없는 내용 추가 금지
- 불확실 표현 금지

[L2 요약 목록]
${combinedL2}`;
}

// ── 메인 Cloud Function ──────────────────────────────────────────────────
export const generateYoutubeChannelInsight = onRequest({
    region: 'asia-northeast3',
    memory: '512MiB',
    timeoutSeconds: 540,
    secrets: ['GEMINI_API_KEY'],
}, async (req, res) => {
    const db = admin.firestore();

    const apiKey = process.env.GEMINI_API_KEY;
    if (!apiKey) {
        res.status(500).json({ error: '필수 환경변수 없음: GEMINI_API_KEY' });
        return;
    }

    const genAI = new GoogleGenerativeAI(apiKey);
    const model = genAI.getGenerativeModel({ model: 'gemini-2.5-flash' });
    const embedModel = genAI.getGenerativeModel({ model: 'gemini-embedding-001' });

    // ── 1. 활성 채널 목록 조회 ────────────────────────────────────────────
    const channelsSnap = await db.collection('youtube_channels')
        .where('isActive', '==', true)
        .get();

    if (channelsSnap.empty) {
        res.status(200).json({ message: '활성 채널 없음', results: [] });
        return;
    }

    const results: Array<{
        channelId: string;
        channelName: string;
        status: 'generated' | 'skipped';
        reason?: string;
        l1DocId?: string;
    }> = [];

    for (const channelDoc of channelsSnap.docs) {
        const channel = channelDoc.data();
        const { channelId, channelName } = channel;

        console.log(`\n채널 처리 시작: ${channelName} (${channelId})`);

        try {
            // ── 2. 해당 채널의 L2 요약 조회 (최신 20개) ─────────────────
            const l2Snap = await db.collection('youtube_summaries')
                .where('channelId', '==', channelId)
                .where('level', '==', 'L2')
                .orderBy('createdAt', 'desc')
                .limit(20)
                .get();

            // ── 3. L2가 3개 미만이면 스킵 ────────────────────────────────
            if (l2Snap.size < 3) {
                console.log(`  스킵: L2 요약 ${l2Snap.size}개 (최소 3개 필요)`);
                results.push({
                    channelId,
                    channelName,
                    status: 'skipped',
                    reason: `L2 요약 부족 (${l2Snap.size}개)`,
                });
                continue;
            }

            // ── 4. 기존 L1 확인 → 최신 L2 추가 여부 판단 ────────────────
            const existingL1Snap = await db.collection('youtube_summaries')
                .where('channelId', '==', channelId)
                .where('level', '==', 'L1')
                .orderBy('createdAt', 'desc')
                .limit(1)
                .get();

            if (!existingL1Snap.empty) {
                const latestL1 = existingL1Snap.docs[0].data();
                const l1CreatedAt: admin.firestore.Timestamp = latestL1.createdAt;

                // 최근 L1 생성 이후에 추가된 L2가 있는지 확인
                const newL2AfterL1 = l2Snap.docs.filter(doc => {
                    const l2CreatedAt: admin.firestore.Timestamp = doc.data().createdAt;
                    return l2CreatedAt && l1CreatedAt && l2CreatedAt.toMillis() > l1CreatedAt.toMillis();
                });

                if (newL2AfterL1.length === 0) {
                    console.log(`  스킵: 최근 L1(${l1CreatedAt.toDate().toISOString()}) 이후 새 L2 없음`);
                    results.push({
                        channelId,
                        channelName,
                        status: 'skipped',
                        reason: '최근 L1 이후 새 L2 없음',
                    });
                    continue;
                }

                console.log(`  L1 재생성: 최근 L1 이후 새 L2 ${newL2AfterL1.length}개 발견`);
            } else {
                console.log(`  L1 최초 생성 (기존 L1 없음)`);
            }

            // ── 5. L2 요약 텍스트 결합 ───────────────────────────────────
            const l2Summaries = l2Snap.docs.map(doc => doc.data().summary as string).filter(Boolean);

            if (l2Summaries.length < 3) {
                console.log(`  스킵: 유효한 L2 summary 텍스트 ${l2Summaries.length}개 (최소 3개 필요)`);
                results.push({
                    channelId,
                    channelName,
                    status: 'skipped',
                    reason: `유효한 L2 summary 부족 (${l2Summaries.length}개)`,
                });
                continue;
            }

            const prompt = buildL1Prompt(channelName, l2Summaries);

            // ── 6. Gemini로 L1 인사이트 생성 (지수 백오프 적용) ──────────
            console.log(`  Gemini L1 생성 중 (L2 ${l2Summaries.length}개 기반)...`);
            const insightResult = await withBackoff(() =>
                model.generateContent(prompt)
            );
            const insightText = insightResult.response.text();

            // ── 7. Embedding 생성 ─────────────────────────────────────────
            const embeddingResult = await withBackoff(() =>
                embedModel.embedContent(insightText.slice(0, 8000))
            );
            const embedding = embeddingResult.embedding.values;

            // ── 8. youtube_summaries에 L1 저장 ───────────────────────────
            const l1DocRef = await db.collection('youtube_summaries').add({
                level: 'L1',
                channelId,
                channelName,
                summary: insightText,
                embedding,
                videoCount: l2Snap.size,
                createdAt: admin.firestore.FieldValue.serverTimestamp(),
            });

            console.log(`  L1 저장 완료: ${l1DocRef.id}`);
            results.push({
                channelId,
                channelName,
                status: 'generated',
                l1DocId: l1DocRef.id,
            });

            // Rate limit 방지 (채널 간 간격)
            await new Promise(r => setTimeout(r, 3000));

        } catch (err: any) {
            console.error(`  채널 처리 오류 (${channelName}):`, err?.message || err);
            results.push({
                channelId,
                channelName,
                status: 'skipped',
                reason: `오류: ${err?.message || String(err)}`,
            });
        }
    }

    const generated = results.filter(r => r.status === 'generated').length;
    const skipped = results.filter(r => r.status === 'skipped').length;

    console.log(`\n완료: 생성 ${generated}개, 스킵 ${skipped}개`);

    res.status(200).json({
        message: `L1 인사이트 생성 완료`,
        generated,
        skipped,
        results,
    });
});
