/**
 * 대학 축제 크롤러 봇
 * memogipost 등 검색 페이지에서 프로미스나인 출연 대학 축제를 수집하여 행사 일정 자동 생성
 */
import fp from 'fastify-plugin';
import { fetchSearchPostUrls } from './scraper.js';
import { extractFestivalsFromUrls } from './gemini.js';
import { createEventSchedule, searchKakaoPlace, kakaoToVenue } from '../event.js';
import { CATEGORY_IDS } from '../../config/index.js';
import { logActivity } from '../../utils/log.js';

const MAX_URLS_PER_REQUEST = 20; // Gemini url_context 한 번에 처리할 URL 수

async function festivalBotPlugin(fastify, opts) {
  /**
   * 활동 중인 멤버 ID 목록 조회 (축제는 보통 완전체)
   */
  async function getActiveMemberIds() {
    const [rows] = await fastify.db.query(
      'SELECT id FROM members WHERE is_former = 0 ORDER BY id'
    );
    return rows.map(r => r.id);
  }

  /**
   * 같은 학교 + 날짜의 행사 일정이 이미 있는지 확인
   * 학교명은 부분일치로 비교 (캠퍼스명 유무 차이 흡수)
   * 예: "인천대학교" vs "인천대학교 송도캠퍼스" → 같은 학교로 간주
   */
  async function isDuplicate(date, university) {
    const [rows] = await fastify.db.query(
      `SELECT se.school_name FROM schedules s
       JOIN schedule_event se ON s.id = se.schedule_id
       WHERE s.category_id = ? AND s.date = ?`,
      [CATEGORY_IDS.EVENT, date]
    );
    for (const row of rows) {
      const existing = (row.school_name || '').trim();
      const target = university.trim();
      if (!existing) continue;
      // 동일하거나, 한쪽이 다른 쪽의 접두사이면 같은 학교
      if (existing === target
        || existing.startsWith(target)
        || target.startsWith(existing)) {
        return true;
      }
    }
    return false;
  }

  /**
   * 게시글 URL이 이미 처리됐는지 필터링 → 새 URL만 반환
   */
  async function filterNewUrls(postUrls) {
    if (postUrls.length === 0) return [];
    const [rows] = await fastify.db.query(
      'SELECT post_url FROM festival_crawl_log WHERE post_url IN (?)',
      [postUrls]
    );
    const processed = new Set(rows.map(r => r.post_url));
    return postUrls.filter(u => !processed.has(u));
  }

  /**
   * 크롤 로그 기록 (중복 방지용 - 이벤트 없는 글도 기록)
   */
  async function logCrawled(postUrl, status, resultCount = 0) {
    await fastify.db.query(
      `INSERT IGNORE INTO festival_crawl_log (post_url, status, result_count)
       VALUES (?, ?, ?)`,
      [postUrl, status, resultCount]
    );
  }

  /**
   * 단일 축제 정보로 행사 일정 생성
   * @returns {boolean} 생성 성공 여부
   */
  async function createFestivalSchedule(festival, memberIds) {
    const { date, time, university, festival_name } = festival;

    if (!date || !university) {
      fastify.log.warn(`[festival] 필수 정보 누락: ${JSON.stringify(festival)}`);
      return false;
    }

    // 중복 체크
    if (await isDuplicate(date, university)) {
      fastify.log.info(`[festival] 중복 건너뜀: ${date} ${university}`);
      return false;
    }

    // 카카오맵으로 장소 검색
    let venue = { name: university };
    try {
      const docs = await searchKakaoPlace(university);
      if (docs.length > 0) {
        venue = kakaoToVenue(docs[0]);
      }
    } catch (err) {
      fastify.log.warn(`[festival] 장소 검색 실패 (${university}): ${err.message}`);
    }

    // 제목: 축제명이 있으면 "학교명 축제명", 없으면 "학교명 대학 축제"
    const title = festival_name
      ? `${university} ${festival_name}`
      : `${university} 대학 축제`;

    const scheduleId = await createEventSchedule(fastify.db, fastify.meilisearch, {
      title,
      date,
      time: time || null,
      subtype: 'university',
      schoolName: university,
      memberIds,
      venue,
      postUrls: [], // 관련 링크는 관리자가 수동으로 입력 (블로그 링크 자동 추가 안 함)
    });

    logActivity(fastify.db, {
      actor: 'festival-crawler',
      action: 'create',
      category: 'schedule',
      targetType: 'event_schedule',
      targetId: scheduleId,
      summary: `대학 축제 자동 생성: ${title}`,
    });

    fastify.log.info(`[festival] 행사 생성: ${title} (${date})`);
    return true;
  }

  /**
   * 새 축제 일정 동기화 (스케줄러가 호출)
   * @param {object} bot - { id, searchUrl }
   */
  async function syncNewFestivals(bot) {
    const apiKey = fastify.config.gemini?.apiKey;
    if (!apiKey) {
      fastify.log.warn('[festival] GEMINI_API_KEY 미설정 - 동기화 건너뜀');
      return { addedCount: 0, total: 0 };
    }

    // 1. 검색 페이지에서 게시글 URL 수집
    const postUrls = await fetchSearchPostUrls(bot.searchUrl, fastify.log);
    if (postUrls.length === 0) {
      return { addedCount: 0, total: 0 };
    }

    // 2. 이미 처리한 URL 제외
    const newUrls = await filterNewUrls(postUrls);
    if (newUrls.length === 0) {
      fastify.log.info('[festival] 새 게시글 없음 - Gemini 호출 건너뜀');
      return { addedCount: 0, total: postUrls.length };
    }

    fastify.log.info(`[festival] 새 게시글 ${newUrls.length}개 발견`);

    const memberIds = await getActiveMemberIds();
    let addedCount = 0;

    // 3. 20개씩 배치로 Gemini 분석
    for (let i = 0; i < newUrls.length; i += MAX_URLS_PER_REQUEST) {
      const batch = newUrls.slice(i, i + MAX_URLS_PER_REQUEST);
      let festivals;
      try {
        festivals = await extractFestivalsFromUrls(batch, apiKey);
      } catch (err) {
        // 배치 실패 - 해당 URL들은 로그하지 않음 (다음 실행에 재시도)
        fastify.log.error(`[festival] Gemini 분석 실패: ${err.message}`);
        throw err; // scheduler의 consecutiveErrors 처리로 전달
      }

      // 결과를 source_url별로 그룹화
      const bySource = new Map();
      for (const f of festivals) {
        const src = f.source_url || '';
        if (!bySource.has(src)) bySource.set(src, []);
        bySource.get(src).push(f);
      }

      // 각 축제 일정 생성
      for (const festival of festivals) {
        try {
          const ok = await createFestivalSchedule(festival, memberIds);
          if (ok) addedCount++;
        } catch (err) {
          fastify.log.error(`[festival] 일정 생성 실패: ${err.message}`);
        }
      }

      // 4. 배치의 모든 URL을 크롤 로그에 기록
      for (const url of batch) {
        const matched = bySource.get(url) || [];
        const status = matched.length > 0 ? 'processed' : 'no_event';
        await logCrawled(url, status, matched.length);
      }
    }

    return { addedCount, total: postUrls.length };
  }

  fastify.decorate('festivalBot', {
    syncNewFestivals,
  });
}

export default fp(festivalBotPlugin, {
  name: 'festivalBot',
  dependencies: ['db', 'meilisearch'],
});
