const { AverageCache } = require('../models');
const { EventScore, EventParticipant, Member, Event, Club, sequelize } = require('../models');
const { Op } = require('sequelize');
const memoryCache = new Map();

// 캐시 키 생성 함수
const generateAverageKey = (memberId, startDate, endDate) => {
  return `average:${memberId}:${startDate.getTime()}:${endDate.getTime()}`;
};

// 클럽 설정 기반 캐시 키 생성
const generateClubSettingAverageKey = (memberId, clubId, period) => {
  return `average:club:${clubId}:${memberId}:${period}`;
};

/**
 * 회원의 기간별 에버리지 계산 (캐싱 적용)
 * @param {number} memberId - 회원 ID
 * @param {Date} startDate - 시작 날짜
 * @param {Date} endDate - 종료 날짜
 * @returns {Object} - 에버리지 정보
 */
const calculateMemberAverage = async (memberId, clubId, startDate, endDate) => {
  try {
    // 캐시 키 생성
    const cacheKey = generateAverageKey(memberId, startDate, endDate);
    
    // 메모리 캐시에서 데이터 조회
    if (memoryCache.has(cacheKey)) {
      return memoryCache.get(cacheKey);
    }
    
    // DB 캐시에서 데이터 조회
    const cachedData = await AverageCache.findOne({
      where: {
        memberId,
        startDate,
        endDate,
        expiredAt: { [Op.gt]: new Date() }
      }
    });
    if (cachedData) {
      memoryCache.set(cacheKey, {
        average: cachedData.average,
        games: cachedData.games,
        totalScore: cachedData.totalScore
      });
      return memoryCache.get(cacheKey);
    }
    
    // 캐시에 없으면 DB에서 계산
    // 1. games(경기수)를 동적으로 count로 계산
    const games = await EventScore.count({
      include: [
        {
          model: EventParticipant,
          where: { memberId },
          include: [
            {
              model: Event,
              where: {
                startDate: {
                  [Op.between]: [startDate, endDate]
                }
              }
            }
          ]
        }
      ]
    });

    if (games === 0) {
      const result = { average: 0, games: 0, totalScore: 0 };
      // DB 캐시에 저장 (1시간 TTL)
      await AverageCache.upsert({
        memberId,
        clubId: clubId, // 필요시 clubId 인자 추가
        startDate,
        endDate,
        average: 0,
        games: 0,
        totalScore: 0,
        expiredAt: new Date(Date.now() + 60 * 60 * 1000)
      });
      memoryCache.set(cacheKey, result);
      return result;
    }

    // 2. 점수 총합 계산 (동적)
    const scores = await EventScore.findAll({
      include: [
        {
          model: EventParticipant,
          where: { memberId },
          include: [
            {
              model: Event,
              where: {
                startDate: {
                  [Op.between]: [startDate, endDate]
                }
              }
            }
          ]
        }
      ]
    });
    const totalScore = scores.reduce((sum, score) => sum + score.score, 0);
    const average = parseFloat((totalScore / games).toFixed(1));

    const result = { average, games, totalScore };
    if (!clubId) {
      console.warn('[AverageCache] clubId가 null 또는 undefined로 전달됨:', { memberId, startDate, endDate });
    }
    await AverageCache.upsert({
      memberId,
      clubId, // 반드시 값 전달
      startDate,
      endDate,
      average,
      games,
      totalScore,
      expiredAt: new Date(Date.now() + 60 * 60 * 1000)
    });
    memoryCache.set(cacheKey, result);
    return result;
  } catch (error) {
    console.error('에버리지 계산 중 오류 발생:', error);
    throw error;
  }
};

/**
 * 클럽 설정에 따른 회원 에버리지 계산 (캐싱 적용)
 * @param {number} memberId - 회원 ID
 * @param {number} clubId - 클럽 ID
 * @returns {Object} - 에버리지 정보
 */
const calculateMemberAverageByClubSetting = async (memberId, clubId, deps = module.exports) => {
  try {
    const member = await Member.findByPk(memberId);
    if (!member) {
      throw new Error('회원을 찾을 수 없습니다.');
    }

    const club = await Club.findByPk(clubId);
    if (!club) {
      throw new Error('클럽을 찾을 수 없습니다.');
    }

    // 캐시 키 생성

    // 기간 계산
    const now = new Date();
    const currentYear = now.getFullYear();
    const currentMonth = now.getMonth();
    let startDate;
    let endDate = new Date();

    switch (club.averageCalculationPeriod) {
      case '3month':
        startDate = new Date(now);
        startDate.setMonth(now.getMonth() - 3);
        break;
      case '6month':
        startDate = new Date(now);
        startDate.setMonth(now.getMonth() - 6);
        break;
      case '1year':
        startDate = new Date(now);
        startDate.setFullYear(now.getFullYear() - 1);
        break;
      case 'firsthalf':
        startDate = new Date(currentYear, 0, 1); // 1월 1일
        endDate = new Date(currentYear, 5, 30); // 6월 30일
        break;
      case 'secondhalf':
        startDate = new Date(currentYear, 6, 1); // 7월 1일
        endDate = new Date(currentYear, 11, 31); // 12월 31일
        break;
      case 'quarterly':
        // 현재 분기 계산
        const currentQuarter = Math.floor(currentMonth / 3) + 1;
        const quarterStartMonth = (currentQuarter - 1) * 3;
        startDate = new Date(currentYear, quarterStartMonth, 1); // 분기 시작일
        endDate = new Date(currentYear, quarterStartMonth + 3, 0); // 분기 마지막일
        break;
      case 'total':
      default:
        startDate = new Date(0); // 1970년 1월 1일
        endDate = now;
        break;
    }

    // 캐시 키 생성
    const cacheKey = generateClubSettingAverageKey(memberId, clubId, club.averageCalculationPeriod);

    // 메모리 캐시에서 데이터 조회
    if (memoryCache.has(cacheKey)) {
      return memoryCache.get(cacheKey);
    }

    // DB 캐시에서 데이터 조회
    const cachedData = await AverageCache.findOne({
      where: {
        memberId,
        clubId,
        startDate,
        endDate,
        expiredAt: { [Op.gt]: new Date() }
      }
    });
    if (cachedData) {
      memoryCache.set(cacheKey, {
        average: cachedData.average,
        games: cachedData.games,
        totalScore: cachedData.totalScore
      });
      return memoryCache.get(cacheKey);
    }

    // DB에서 계산
    const result = await deps.calculateMemberAverage(memberId, clubId, startDate, endDate);

    
    // 결과를 캐시에 저장 (TTL: 1시간)
    await AverageCache.upsert({
      memberId,
      clubId,
      startDate,
      endDate,
      average: result.average,
      games: result.games,
      totalScore: result.totalScore,
      expiredAt: new Date(Date.now() + 60 * 60 * 1000)
    });
    memoryCache.set(cacheKey, result);
    
    return result;
  } catch (error) {
    console.error('클럽 설정에 따른 에버리지 계산 중 오류 발생:', error);
    throw error;
  }
};

/**
 * 새로운 점수가 추가되었을 때 캐시 무효화
 * @param {number} memberId - 회원 ID
 */
// 특정 회원의 모든 club/기간별 average 캐시도 무효화
const invalidateClubAveragesForMember = async (memberId, deps = module.exports) => {
  // 메모리 캐시에서 삭제
  for (const key of memoryCache.keys()) {
    if (key.startsWith('average:club:') && key.split(':')[3] == String(memberId)) {
      memoryCache.delete(key);
    }
  }
  // DB 캐시에서 삭제
  await AverageCache.destroy({
    where: {
      memberId
    }
  });
};

const invalidateMemberAverageCache = async (memberId, deps = module.exports) => {
  // 메모리 캐시에서 삭제
  for (const key of memoryCache.keys()) {
    if (key.startsWith('average:') && key.split(':')[1] == String(memberId)) {
      memoryCache.delete(key);
    }
  }
  // DB 캐시에서 삭제
  await AverageCache.destroy({
    where: {
      memberId
    }
  });
  await invalidateClubAveragesForMember(memberId, deps);
};

/**
 * 만료된 캐시 정리 (주기적으로 실행)
 */
const cleanExpiredCache = async () => {
  try {
    const now = new Date();
    
    // 메모리 캐시 정리
    for (const [key, value] of memoryCache.entries()) {
      if (value.expiry && new Date(value.expiry) < now) {
        memoryCache.delete(key);
      }
    }
    
    // DB 캐시 정리
    await AverageCache.destroy({
      where: {
        expiredAt: { [Op.lt]: now }
      }
    });
  } catch (error) {
    console.error('만료된 캐시 정리 중 오류 발생:', error);
  }
};

// 서버 시작 시 캐시 초기화


// 주기적으로 만료된 캐시 정리 (1시간마다)
setInterval(cleanExpiredCache, 60 * 60 * 1000);

module.exports = {
  calculateMemberAverage,
  calculateMemberAverageByClubSetting,
  invalidateMemberAverageCache,
  cleanExpiredCache,
  // 테스트를 위해 내부 함수 노출
  getFromCache: (key) => memoryCache.get(key),
  setToCache: (key, value) => memoryCache.set(key, value),
  invalidateCache: (key) => memoryCache.delete(key)
};
