import fp from 'fastify-plugin';
import { fetchRecentVideoIds, fetchVideoInfo, fetchAllVideos } from './api.js';
import { CATEGORY_IDS } from '../../config/index.js';
import { withTransaction } from '../../utils/transaction.js';
import { syncScheduleById, deleteSchedule } from '../meilisearch/index.js';
import { logActivity } from '../../utils/log.js';

const YOUTUBE_CATEGORY_ID = CATEGORY_IDS.YOUTUBE;

async function youtubeBotPlugin(fastify) {
  /**
   * 다음 특정 요일 날짜 계산 (KST 기준)
   * @param {number} targetDay - 목표 요일 (0=일, 4=목)
   * @param {Date} fromDate - 기준 날짜 (기본: 오늘)
   * @returns {string} YYYY-MM-DD 형식
   */
  function getNextWeekday(targetDay, fromDate = new Date()) {
    const kst = new Date(fromDate.toLocaleString('en-US', { timeZone: 'Asia/Seoul' }));
    const currentDay = kst.getDay();
    // 다음 주 같은 요일까지 일수 계산
    let daysUntil = targetDay - currentDay + 7;
    if (daysUntil <= 0) daysUntil += 7;

    const nextDate = new Date(kst);
    nextDate.setDate(kst.getDate() + daysUntil);

    const year = nextDate.getFullYear();
    const month = String(nextDate.getMonth() + 1).padStart(2, '0');
    const day = String(nextDate.getDate()).padStart(2, '0');
    return `${year}-${month}-${day}`;
  }

  /**
   * 해당 날짜의 예정 일정 조회 (is_temp = 1인 것)
   */
  async function findScheduledEntry(bot, date) {
    const [rows] = await fastify.db.query(
      `SELECT sy.schedule_id, s.title, s.date, s.time
       FROM schedule_youtube sy
       JOIN schedules s ON s.id = sy.schedule_id
       WHERE s.is_temp = 1 AND sy.channel_id = ? AND s.date = ?`,
      [bot.channelId, date]
    );
    return rows[0] || null;
  }

  /**
   * 채널의 일반 영상 개수 조회 (쇼츠 제외)
   */
  async function getVideoCount(channelId) {
    const [rows] = await fastify.db.query(
      `SELECT COUNT(*) as cnt FROM schedule_youtube
       WHERE channel_id = ? AND video_type = 'video' AND video_id IS NOT NULL`,
      [channelId]
    );
    return rows[0].cnt;
  }

  /**
   * 예정 일정 제목 생성
   */
  async function generateScheduledTitle(bot) {
    const { autoScheduleNext } = bot;

    if (autoScheduleNext.titleTemplate) {
      const videoCount = await getVideoCount(bot.channelId);
      const nextEpisode = videoCount + 1;

      return autoScheduleNext.titleTemplate
        .replace('{channelName}', bot.channelName)
        .replace('{episode}', nextEpisode);
    }

    return autoScheduleNext.title || `${bot.channelName} (예정)`;
  }

  /**
   * 다음 주 예정 일정 생성
   */
  async function createScheduledEntry(bot) {
    const { autoScheduleNext } = bot;
    if (!autoScheduleNext) return null;

    const nextDate = getNextWeekday(autoScheduleNext.dayOfWeek);

    // 이미 존재하는지 확인 (같은 채널, 같은 날짜, is_temp = 1)
    const [existing] = await fastify.db.query(
      `SELECT sy.schedule_id FROM schedule_youtube sy
       JOIN schedules s ON s.id = sy.schedule_id
       WHERE s.is_temp = 1 AND sy.channel_id = ? AND s.date = ?`,
      [bot.channelId, nextDate]
    );
    if (existing.length > 0) {
      return null; // 이미 존재
    }

    // 제목 생성
    const title = await generateScheduledTitle(bot);

    // 트랜잭션으로 생성
    const scheduleId = await withTransaction(fastify.db, async (conn) => {
      const [result] = await conn.query(
        'INSERT INTO schedules (category_id, title, date, time, is_temp) VALUES (?, ?, ?, ?, 1)',
        [YOUTUBE_CATEGORY_ID, title, nextDate, autoScheduleNext.time]
      );
      const newScheduleId = result.insertId;

      await conn.query(
        'INSERT INTO schedule_youtube (schedule_id, video_id, video_type, channel_id, channel_name) VALUES (?, ?, ?, ?, ?)',
        [newScheduleId, null, 'video', bot.channelId, bot.channelName]
      );

      return newScheduleId;
    });

    // Meilisearch 동기화
    if (scheduleId) {
      await syncScheduleById(fastify.meilisearch, fastify.db, scheduleId);
      fastify.log.info(`[${bot.id}] 다음 주 예정 일정 생성: ${nextDate} - ${title}`);
    }

    return scheduleId;
  }

  /**
   * 예정 일정을 실제 영상으로 덮어씌움
   */
  async function updateScheduledEntry(scheduledEntry, video, bot) {
    await withTransaction(fastify.db, async (conn) => {
      // schedules 테이블 업데이트 (is_temp = 0으로 변경)
      await conn.query(
        'UPDATE schedules SET title = ?, date = ?, time = ?, is_temp = 0 WHERE id = ?',
        [video.title, video.date, video.time, scheduledEntry.schedule_id]
      );

      // schedule_youtube 테이블 업데이트
      await conn.query(
        'UPDATE schedule_youtube SET video_id = ?, video_type = ? WHERE schedule_id = ?',
        [video.videoId, video.videoType, scheduledEntry.schedule_id]
      );
    });

    // Meilisearch 동기화
    await syncScheduleById(fastify.meilisearch, fastify.db, scheduledEntry.schedule_id);
    fastify.log.info(`[${bot.id}] 예정 일정 업데이트: ${video.title}`);

    return scheduledEntry.schedule_id;
  }

  /**
   * 예정 일정 삭제 + 다음 주 예정 일정 생성
   */
  async function deleteScheduledAndCreateNext(bot, scheduleId) {
    // 삭제
    await withTransaction(fastify.db, async (conn) => {
      await conn.query('DELETE FROM schedule_members WHERE schedule_id = ?', [scheduleId]);
      await conn.query('DELETE FROM schedule_youtube WHERE schedule_id = ?', [scheduleId]);
      await conn.query('DELETE FROM schedules WHERE id = ?', [scheduleId]);
    });

    // Meilisearch에서도 삭제
    await deleteSchedule(fastify.meilisearch, scheduleId);
    fastify.log.info(`[${bot.id}] 예정 일정 삭제 (영상 미업로드)`);

    // 다음 주 예정 일정 생성
    await createScheduledEntry(bot);
  }

  /**
   * 예정 일정 deadline 체크 (금요일 00시)
   */
  async function checkScheduledDeadline(bot) {
    const { autoScheduleNext } = bot;
    if (!autoScheduleNext || !autoScheduleNext.deadlineDayOfWeek) return;

    const now = new Date();
    const kst = new Date(now.toLocaleString('en-US', { timeZone: 'Asia/Seoul' }));
    const currentDay = kst.getDay();

    // deadline 요일인지 확인 (금요일 = 5)
    if (currentDay !== autoScheduleNext.deadlineDayOfWeek) {
      return;
    }

    // 어제(목요일) 날짜 계산 - deadline 당일이면 전날이 목표 요일
    const targetDate = new Date(kst);
    targetDate.setDate(kst.getDate() - 1); // 어제

    const year = targetDate.getFullYear();
    const month = String(targetDate.getMonth() + 1).padStart(2, '0');
    const day = String(targetDate.getDate()).padStart(2, '0');
    const targetDateStr = `${year}-${month}-${day}`;

    // 예정 일정이 아직 존재하는지 확인 (is_temp = 1인 것)
    const [rows] = await fastify.db.query(
      `SELECT sy.schedule_id FROM schedule_youtube sy
       JOIN schedules s ON s.id = sy.schedule_id
       WHERE s.is_temp = 1 AND sy.channel_id = ? AND s.date = ?`,
      [bot.channelId, targetDateStr]
    );

    if (rows.length > 0) {
      // 아직 예정 상태 → 삭제 + 다음 주 생성
      await deleteScheduledAndCreateNext(bot, rows[0].schedule_id);
    }
  }

  /**
   * 멤버 이름 맵 조회
   */
  async function getMemberNameMap() {
    const [rows] = await fastify.db.query('SELECT id, name FROM members');
    const map = {};
    for (const r of rows) {
      map[r.name] = r.id;
    }
    return map;
  }

  /**
   * description에서 멤버 추출
   */
  function extractMemberIds(description, memberNameMap) {
    if (!description) return [];
    const ids = [];
    for (const [name, id] of Object.entries(memberNameMap)) {
      if (description.includes(name)) {
        ids.push(id);
      }
    }
    return ids;
  }

  /**
   * 영상을 DB에 저장
   */
  async function saveVideo(video, bot) {
    // 중복 체크 (video_id로) - 트랜잭션 전에 수행
    const [existing] = await fastify.db.query(
      'SELECT id FROM schedule_youtube WHERE video_id = ?',
      [video.videoId]
    );
    if (existing.length > 0) {
      return null;
    }

    // 커스텀 설정 적용
    // 제목 필터: 하나라도 포함되어야 통과
    if (bot.titleFilters && bot.titleFilters.length > 0) {
      const matchesFilter = bot.titleFilters.some((filter) => video.title.includes(filter));
      if (!matchesFilter) {
        return null;
      }
    }

    const { autoScheduleNext } = bot;
    const isVideoType = video.videoType === 'video'; // 쇼츠가 아닌 일반 영상

    // 예정 일정 처리 (쇼츠 제외 옵션이 있으면 쇼츠는 무시)
    if (autoScheduleNext && isVideoType) {
      // 해당 날짜의 예정 일정이 있는지 확인
      const scheduledEntry = await findScheduledEntry(bot, video.date);

      if (scheduledEntry) {
        // 예정 일정을 실제 영상으로 덮어씌움
        await updateScheduledEntry(scheduledEntry, video, bot);
        // 다음 주 예정 일정 생성
        await createScheduledEntry(bot);
        return scheduledEntry.schedule_id;
      }
    }

    // 멤버 이름 맵 미리 조회 (트랜잭션 전에)
    let nameMap = null;
    if (bot.extractMembersFromDesc || bot.extractMembersFromTitle) {
      nameMap = await getMemberNameMap();
    }

    // 트랜잭션으로 INSERT 작업 수행
    let scheduleId;
    try {
      scheduleId = await withTransaction(fastify.db, async (connection) => {
        // schedules 테이블에 저장
        const [result] = await connection.query(
          'INSERT INTO schedules (category_id, title, date, time) VALUES (?, ?, ?, ?)',
          [YOUTUBE_CATEGORY_ID, video.title, video.date, video.time]
        );
        const newScheduleId = result.insertId;

        // schedule_youtube 테이블에 저장
        await connection.query(
          'INSERT INTO schedule_youtube (schedule_id, video_id, video_type, channel_id, channel_name) VALUES (?, ?, ?, ?, ?)',
          [newScheduleId, video.videoId, video.videoType, video.channelId, bot.channelName]
        );

        // 멤버 연결 (커스텀 설정)
        const hasDefaultMembers = bot.defaultMemberIds && bot.defaultMemberIds.length > 0;
        if (hasDefaultMembers || bot.extractMembersFromDesc || bot.extractMembersFromTitle) {
          const memberIds = [];
          if (hasDefaultMembers) {
            memberIds.push(...bot.defaultMemberIds);
          }
          if (nameMap && bot.extractMembersFromDesc) {
            memberIds.push(...extractMemberIds(video.description, nameMap));
          }
          if (nameMap && bot.extractMembersFromTitle) {
            memberIds.push(...extractMemberIds(video.title, nameMap));
          }
          if (memberIds.length > 0) {
            const uniqueIds = [...new Set(memberIds)];
            const values = uniqueIds.map(id => [newScheduleId, id]);
            await connection.query(
              'INSERT INTO schedule_members (schedule_id, member_id) VALUES ?',
              [values]
            );
          }
        }

        return newScheduleId;
      });
    } catch (err) {
      // UNIQUE 제약 위반 (동시성 중복) → 무시
      if (err.code === 'ER_DUP_ENTRY') return null;
      throw err;
    }

    // 새 영상 추가 후 다음 주 예정 일정 생성 (쇼츠 제외)
    if (autoScheduleNext && isVideoType && scheduleId) {
      await createScheduledEntry(bot);
    }

    return scheduleId;
  }

  /**
   * 최근 영상 동기화 (정기 실행)
   */
  async function syncNewVideos(bot) {
    // 예정 일정 deadline 체크 (금요일 00시)
    if (bot.autoScheduleNext) {
      await checkScheduledDeadline(bot);
    }

    // 1. 최근 영상 ID 목록만 조회 (activities.list - 1 unit)
    const videoIds = await fetchRecentVideoIds(bot.channelId, 10);
    if (videoIds.length === 0) {
      return { addedCount: 0, total: 0 };
    }

    // 2. DB에서 이미 존재하는 영상 필터링
    const [existing] = await fastify.db.query(
      'SELECT video_id FROM schedule_youtube WHERE video_id IN (?)',
      [videoIds]
    );
    const existingIds = new Set(existing.map(r => r.video_id));
    const newVideoIds = videoIds.filter(id => !existingIds.has(id));

    if (newVideoIds.length === 0) {
      return { addedCount: 0, total: videoIds.length };
    }

    // 3. 새 영상만 상세 정보 조회 (videos.list - 새 영상당 1 unit)
    let addedCount = 0;
    for (const videoId of newVideoIds) {
      const video = await fetchVideoInfo(videoId);
      if (!video) continue;

      const scheduleId = await saveVideo(video, bot);
      if (scheduleId) {
        await syncScheduleById(fastify.meilisearch, fastify.db, scheduleId);
        logActivity(fastify.db, {
          actor: bot.id,
          action: 'create',
          category: 'schedule',
          targetType: 'youtube_schedule',
          targetId: scheduleId,
          summary: `YouTube 영상 추가: ${video.title}`,
        });
        addedCount++;
      }
    }

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

  /**
   * 전체 영상 동기화 (초기화)
   */
  async function syncAllVideos(bot) {
    const videos = await fetchAllVideos(bot.channelId);
    let addedCount = 0;

    for (const video of videos) {
      const scheduleId = await saveVideo(video, bot);
      if (scheduleId) {
        // Meilisearch 동기화
        await syncScheduleById(fastify.meilisearch, fastify.db, scheduleId);
        addedCount++;
      }
    }

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

  /**
   * 관리 중인 채널 ID 목록 (DB에서 조회)
   */
  async function getManagedChannelIds() {
    const [rows] = await fastify.db.query(
      'SELECT channel_id FROM bot_youtube WHERE enabled = 1'
    );
    return rows.map(r => r.channel_id);
  }

  fastify.decorate('youtubeBot', {
    syncNewVideos,
    syncAllVideos,
    getManagedChannelIds,
    saveVideo,
  });
}

export default fp(youtubeBotPlugin, {
  name: 'youtubeBot',
  dependencies: ['db'],
});
