/**
 * 축제 크롤러 - Gemini API 클라이언트
 * url_context 도구로 게시글을 직접 분석하여 프로미스나인 출연 축제 정보 추출
 */

const GEMINI_MODEL = 'gemini-2.5-flash';
const GEMINI_ENDPOINT = `https://generativelanguage.googleapis.com/v1beta/models/${GEMINI_MODEL}:generateContent`;
const REQUEST_TIMEOUT = 240000; // 4분

/**
 * 프롬프트 생성
 */
function buildPrompt(postUrls) {
  const urlList = postUrls.map((u, i) => `${i + 1}. ${u}`).join('\n');
  return `다음 블로그 게시글들의 내용을 모두 가져와서 분석해주세요:

${urlList}

각 게시글에서 프로미스나인(fromis_9)이 출연하는 2026년 **대학 축제** 일정을 모두 추출하세요.

규칙:
1. 게시글 내용에 "프로미스나인" 또는 "fromis_9"이 출연진으로 명시된 경우만 인정
2. 추측, 예상, 가능성 같은 미확정 정보는 제외
3. **대학 축제(대동제 등)만** 포함. 지역 축제, 가요제, 콘서트 등 대학 행사가 아닌 것은 제외
4. 캠퍼스 구분이 필요한 학교(단국대·성균관대·경희대 등)는 반드시 캠퍼스명 포함
5. 게시글에 출연 시간(공연 시작 시각)이 명시되어 있으면 time 필드에 "HH:MM" 형식으로, 없으면 빈 문자열
6. 게시글에 적힌 정보만 사용 (추론 금지)

출력: JSON 배열만 출력하세요. 설명, 코드블록 표시(\`\`\`) 없이.
[
  {
    "date": "YYYY-MM-DD",
    "time": "HH:MM 또는 빈 문자열",
    "university": "정확한 학교명 (캠퍼스 포함)",
    "festival_name": "축제 공식 명칭 (없으면 빈 문자열)",
    "source_url": "정보가 있던 게시글 URL"
  }
]

해당하는 일정이 없으면 빈 배열 []을 반환하세요.`;
}

/**
 * Gemini 응답 텍스트에서 JSON 배열 파싱
 */
function parseJsonArray(text) {
  let clean = text.trim();
  // 코드블록 제거
  if (clean.startsWith('```')) {
    clean = clean.replace(/^```(?:json)?\s*/, '').replace(/\s*```$/, '').trim();
  }
  const parsed = JSON.parse(clean);
  if (!Array.isArray(parsed)) {
    throw new Error('Gemini 응답이 배열이 아닙니다');
  }
  return parsed;
}

// 일시적 오류로 재시도할 HTTP 상태 코드
const RETRYABLE_STATUS = [500, 503, 429];
const MAX_RETRIES = 3;
const RETRY_DELAY = 20000; // 20초

/**
 * Gemini API 단일 호출 (재시도 없음)
 */
async function callGemini(postUrls, apiKey) {
  const payload = {
    contents: [{ parts: [{ text: buildPrompt(postUrls) }] }],
    tools: [{ url_context: {} }],
    generationConfig: { temperature: 0.05 },
  };

  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT);

  try {
    const res = await fetch(`${GEMINI_ENDPOINT}?key=${apiKey}`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload),
      signal: controller.signal,
    });
    clearTimeout(timeoutId);

    if (!res.ok) {
      const errText = await res.text();
      const err = new Error(`Gemini API 오류 ${res.status}: ${errText.slice(0, 200)}`);
      err.status = res.status;
      throw err;
    }
    return await res.json();
  } catch (err) {
    clearTimeout(timeoutId);
    if (err.name === 'AbortError') {
      throw new Error('Gemini API 요청 타임아웃');
    }
    throw err;
  }
}

/**
 * URL 목록을 Gemini url_context로 분석하여 축제 일정 추출
 * @param {string[]} postUrls - 분석할 게시글 URL (최대 20개)
 * @param {string} apiKey - Gemini API 키
 * @returns {Promise<Array>} [{ date, time, university, festival_name, source_url }]
 */
export async function extractFestivalsFromUrls(postUrls, apiKey) {
  if (!apiKey) {
    throw new Error('GEMINI_API_KEY가 설정되지 않았습니다');
  }
  if (postUrls.length === 0) {
    return [];
  }

  // 일시적 오류(503/500/429)는 재시도
  let result;
  let lastErr;
  for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
    try {
      result = await callGemini(postUrls, apiKey);
      break;
    } catch (err) {
      lastErr = err;
      if (attempt < MAX_RETRIES && RETRYABLE_STATUS.includes(err.status)) {
        await new Promise(r => setTimeout(r, RETRY_DELAY));
        continue;
      }
      throw err;
    }
  }
  if (!result) throw lastErr;

  const candidate = result?.candidates?.[0];
  if (!candidate) {
    throw new Error('Gemini 응답에 candidates가 없습니다');
  }

  const text = candidate.content?.parts?.map(p => p.text || '').join('') || '';
  if (!text.trim()) {
    throw new Error('Gemini 응답이 비어 있습니다');
  }

  return parseJsonArray(text);
}
