/**
 * 파일 업로드 컨트롤러
 * 이벤트 관련 파일 업로드 및 처리를 담당하는 컨트롤러
 */
const multer = require('multer');
const path = require('path');
const fs = require('fs');
const xlsx = require('xlsx');
const csv = require('csv-parser');
const Tesseract = require('tesseract.js');
const sharp = require('sharp');
const models = require('../models');
const { Event, EventParticipant, Member, EventScore, Club, EventTeam, ClubMember, EventTeamMember } = models;
const { Op, Sequelize } = require('sequelize');
const { sequelize } = require('../models');
const averageService = require('../services/averageService');

// 업로드 디렉토리 설정
const uploadDir = path.join(__dirname, '../uploads');
// 업로드 디렉토리가 없으면 생성
if (!fs.existsSync(uploadDir)) {
  fs.mkdirSync(uploadDir, { recursive: true });
}

// 파일 업로드를 위한 multer 설정
const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, uploadDir);
  },
  filename: function (req, file, cb) {
    const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
    cb(null, uniqueSuffix + path.extname(file.originalname));
  }
});

// 파일 필터 (허용된 파일 형식만 업로드)
const fileFilter = (req, file, cb) => {
  const allowedTypes = [
    'application/vnd.ms-excel',
    'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
    'text/csv',
    'image/jpeg',
    'image/png'
  ];
  
  if (allowedTypes.includes(file.mimetype)) {
    cb(null, true);
  } else {
    cb(new Error('지원되지 않는 파일 형식입니다. Excel, CSV, JPG, PNG 파일만 업로드 가능합니다.'), false);
  }
};

// 업로드 저장 롤백: 이벤트 및 관련 데이터 삭제 (옵션: 게스트 정리)
exports.rollbackEventImport = async (req, res) => {
  try {
    const { eventId, clubId, includeGuests } = req.body || {};
    const eid = Number(eventId);
    const cid = Number(clubId);
    if (!eid || !cid) {
      return res.status(400).json({ message: '유효한 eventId, clubId가 필요합니다.' });
    }

    await sequelize.transaction(async (t) => {
      // 이벤트 검증 (동일 클럽)
      const event = await Event.findOne({ where: { id: eid, clubId: cid }, transaction: t });
      if (!event) {
        throw new Error('대상 이벤트를 찾을 수 없습니다.');
      }

      // 관련 팀멤버 삭제
      const teams = await EventTeam.findAll({ where: { eventId: eid }, transaction: t });
      const teamIds = teams.map((tm) => tm.id);
      if (teamIds.length > 0) {
        await EventTeamMember.destroy({ where: { eventTeamId: teamIds }, transaction: t });
      }

      // 점수 삭제
      await EventScore.destroy({ where: { eventId: eid }, transaction: t });

      // 참가자 삭제 전, 게스트 후보 수집
      let guestMemberIds = [];
      if (includeGuests) {
        const participants = await EventParticipant.findAll({ where: { eventId: eid }, transaction: t });
        const memberIds = participants.map((p) => p.memberId).filter(Boolean);
        if (memberIds.length > 0) {
          const members = await Member.findAll({ where: { id: memberIds, clubId: cid, memberType: 'guest' }, transaction: t });
          guestMemberIds = members.map((m) => m.id);
        }
      }

      // 참가자 삭제
      await EventParticipant.destroy({ where: { eventId: eid }, transaction: t });

      // 팀 삭제
      await EventTeam.destroy({ where: { eventId: eid }, transaction: t });

      // 이벤트 삭제
      await Event.destroy({ where: { id: eid, clubId: cid }, transaction: t });

      // 게스트 정리: 다른 참가 기록이 없고, 동일 클럽의 조인만 있는 경우 제거
      if (includeGuests && guestMemberIds.length > 0) {
        // 다른 참가 기록이 없는 멤버만 필터
        const others = await EventParticipant.findAll({ where: { memberId: guestMemberIds }, transaction: t });
        const stillUsed = new Set(others.map((p) => p.memberId));
        const deletable = guestMemberIds.filter((id) => !stillUsed.has(id));
        if (deletable.length > 0) {
          // 클럽 조인 제거
          const club = await Club.findOne({ where: { id: cid }, transaction: t });
          if (club) {
            // removeMember 지원 여부에 따라 destroy 직접 수행
            await sequelize.models.ClubMembers?.destroy?.({ where: { clubId: cid, memberId: deletable }, transaction: t }).catch(() => {});
          }
          // 멤버 삭제
          await Member.destroy({ where: { id: deletable, clubId: cid, memberType: 'guest' }, transaction: t });
        }
      }
    });

    res.json({ message: '롤백이 완료되었습니다.' });
  } catch (error) {
    console.error('롤백 오류:', error);
    res.status(500).json({ message: `롤백 중 오류가 발생했습니다: ${error.message}` });
  }
};

const upload = multer({ 
  storage: storage,
  fileFilter: fileFilter,
  limits: { fileSize: 10 * 1024 * 1024 } // 10MB 제한
});

// 파일 업로드 처리
exports.uploadEventFile = (req, res) => {
  upload.single('file')(req, res, async function (err) {
    if (err instanceof multer.MulterError) {
      // Multer 에러 처리
      return res.status(400).json({ message: `파일 업로드 오류: ${err.message}` });
    } else if (err) {
      // 기타 에러 처리
      return res.status(400).json({ message: err.message });
    }
    
    // 파일이 없는 경우
    if (!req.file) {
      return res.status(400).json({ message: '업로드할 파일이 없습니다.' });
    }
    
    // 클럽 ID 확인
    const clubId = req.body.clubId;
    if (!clubId) {
      // 파일 삭제
      if (fs.existsSync(req.file.path)) {
        fs.unlinkSync(req.file.path);
      }
      return res.status(400).json({ message: '클럽 ID가 필요합니다.' });
    }
    
    try {
      // 클럽 존재 여부 확인
      const club = await Club.findOne({
        where: { id: clubId, status: 'active' }
      });
      
      if (!club) {
        // 파일 삭제
        if (fs.existsSync(req.file.path)) {
          fs.unlinkSync(req.file.path);
        }
        return res.status(404).json({ message: '클럽을 찾을 수 없습니다.' });
      }
      
            // 이미지 파일 전처리 (OCR용)
      let ocrImagePath = req.file.path;
      if (req.file.mimetype.startsWith('image/')) {
        const preprocessedPath = req.file.path.replace(/(\.[^.]+)$/, '_preprocessed$1');
        await sharp(req.file.path)
          .grayscale()
          .sharpen()
          .toFile(preprocessedPath);
        ocrImagePath = preprocessedPath;
      }

      // 파일 정보 반환
      res.json({
        message: '파일이 업로드되었습니다.',
        file: {
          filename: req.file.filename,
          originalname: req.file.originalname,
          mimetype: req.file.mimetype,
          size: req.file.size,
          path: req.file.path,
          clubId: clubId
        }
      });
    } catch (error) {
      // 오류 발생 시 파일 삭제
      if (fs.existsSync(req.file.path)) {
        fs.unlinkSync(req.file.path);
      }
      console.error('파일 업로드 오류:', error);
      res.status(500).json({ message: `파일 업로드 중 오류가 발생했습니다: ${error.message}` });
    }
  });
};

// 업로드된 파일 파싱 및 미리보기 데이터 제공
exports.parseEventFile = async (req, res) => {
  try {
    const { filename, clubId, sheetName } = req.body;
    
    if (!filename) {
      return res.status(400).json({ message: '파일명이 필요합니다.' });
    }
    
    if (!clubId) {
      return res.status(400).json({ message: '클럽 ID가 필요합니다.' });
    }
    
    const filePath = path.join(uploadDir, filename);
    
    // 파일 존재 여부 확인
    if (!fs.existsSync(filePath)) {
      return res.status(404).json({ message: '파일을 찾을 수 없습니다.' });
    }
    
    // 파일 확장자 확인
    const fileExt = path.extname(filename).toLowerCase();
    let parsedData = [];
    
    // 클럽 회원 목록 조회 (참가자 매핑용)
    const club = await Club.findOne({
      where: { id: clubId, status: 'active' }
    });
    
    if (!club) {
      return res.status(404).json({ message: '클럽을 찾을 수 없습니다.' });
    }
    
    const clubMembers = await club.getMembers({
      attributes: ['id', 'name', 'gender', 'handicap'],
      through: { where: { status: 'active' } }
    });
    
    // 각 회원의 동적 에버리지 계산
    const memberMap = new Map();
    for (const member of clubMembers) {
      if (member && member.name) {
        let dynamicAverage = 0;
        try {
          const averageData = await averageService.calculateMemberAverageByClubSetting(
            member.id, 
            clubId
          );
          dynamicAverage = averageData.average;
        } catch (error) {
          console.error(`회원 ${member.id}의 에버리지 계산 오류:`, error);
        }
        
        memberMap.set(member.name.trim(), {
          id: member.id,
          name: member.name,
          gender: member.gender,
          average: dynamicAverage,
          handicap: member.handicap
        });
      }
    };
    
    // 파일 형식에 따른 처리
    if (fileExt === '.xlsx' || fileExt === '.xls') {
      // Excel 파일 처리
      parsedData = await parseExcelFile(filePath, memberMap, sheetName);
    } else if (fileExt === '.csv') {
      // CSV 파일 처리
      parsedData = await parseCsvFile(filePath, memberMap);
    } else if (fileExt === '.jpg' || fileExt === '.jpeg' || fileExt === '.png') {
      // 이미지 파일 OCR 처리
      parsedData = await parseImageFile(filePath, memberMap);
    } else {
      return res.status(400).json({ message: '지원되지 않는 파일 형식입니다.' });
    }
    
    // 참가자별 동적 에버리지 계산 호출 (memberId가 있을 때만)
    if (parsedData && parsedData.participants) {
      for (const p of parsedData.participants) {
        if (p.memberId) {
          await averageService.calculateMemberAverageByClubSetting(p.memberId, clubId);
        }
      }
    }

    res.json({
      message: '파일 파싱 완료',
      data: parsedData
    });
  } catch (error) {
    console.error('파일 파싱 오류:', error);
    res.status(500).json({ message: `파일 파싱 중 오류가 발생했습니다: ${error.message}` });
  }
};

// Excel 파일 파싱 함수
async function parseExcelFile(filePath, memberMap, requestedSheetName = null) {
  const workbook = xlsx.readFile(filePath, { type: 'binary', cellDates: true, codepage: 949 });
  
  // 요청된 시트명이 있고 해당 시트가 존재하면 그것을 사용, 아니면 첫 번째 시트 사용
  const sheetName = (requestedSheetName && workbook.SheetNames.includes(requestedSheetName)) 
    ? requestedSheetName 
    : workbook.SheetNames[0];
  
  const worksheet = workbook.Sheets[sheetName];
  const jsonData = xlsx.utils.sheet_to_json(worksheet, { header: 1, defval: '' });
  
  return processRawData(jsonData, memberMap);
}

// CSV 파일 파싱 함수
async function parseCsvFile(filePath, memberMap) {
  return new Promise((resolve, reject) => {
    const results = [];
    fs.createReadStream(filePath, { encoding: 'utf8' })
      .pipe(csv({ headers: false, skipEmptyLines: true }))
      .on('data', (data) => {
        // 각 행을 배열로 변환
        results.push(Object.values(data).map(cell => (cell || '').trim()));
      })
      .on('end', () => {
        try {
          const processedData = processRawData(results, memberMap);
          resolve(processedData);
        } catch (error) {
          reject(error);
        }
      })
      .on('error', (error) => reject(error));
  });
}

// 이미지 파일 OCR 처리 함수
async function parseImageFile(filePath, memberMap) {
  try {
    // 한글 인식을 위한 Tesseract 설정
    const { data } = await Tesseract.recognize(filePath, 'kor+eng', {
      logger: m => console.log(m),
      tessedit_char_whitelist: '0123456789가-힣이름팀핸디총핀에버Gg.,:()[]- ',
      preserve_interword_spaces: 1,
      psm: 6 // 표/블록: 6, 한줄: 7, 단어: 8. 상황에 따라 실험 가능
    });
    
    // OCR 원본 결과 로그
    console.log('[OCR 원본 결과]', data.text);

    // OCR 결과 텍스트 처리
    const lines = data.text.split('\n')
      .filter(line => line.trim() !== '')
      .map(line => line.split(/\s+/));
    
    return processRawData(lines, memberMap);
  } catch (error) {
    console.error('OCR 처리 오류:', error);
    throw new Error('이미지 OCR 처리 중 오류가 발생했습니다.');
  }
}

// 원시 데이터 처리 및 정규화 함수
function processRawData(rawData, memberMap) {
  // 이벤트 정보 추론
  const inferredEventInfo = inferEventInfo(rawData);

  // 다양한 표 헤더 키워드 정의
  const headerKeywords = {
    name: ['이름', '성명', '참가자'],
    team: ['팀', '팀명'],
    handicap: ['핸디', '핸디캡'],
    total: ['총핀', '총점', '합계'],
    avg: ['에버', '평균'],
    // 점수 컬럼: 1게임, 2게임, 1G, 2G, 16, 26 등 다양한 형태
    score: [/^(\d+)[Gg]$/, /^(\d{2,})$/, /^\d+게임$/]
  };

  // 헤더 행 찾기 및 컬럼 인덱스 매핑
  let headerRow = -1;
  let colMap = {};
  for (let i = 0; i < Math.min(10, rawData.length); i++) {
    const row = rawData[i];
    row.forEach((cell, idx) => {
      if (typeof cell !== 'string') return;
      for (const key in headerKeywords) {
        const patterns = headerKeywords[key];
        for (const pattern of patterns) {
          if ((typeof pattern === 'string' && cell.replace(/\s/g, '').includes(pattern)) ||
              (pattern instanceof RegExp && pattern.test(cell.replace(/\s/g, '')))) {
            if (key === 'score') {
              if (!colMap.scores) colMap.scores = [];
              colMap.scores.push(idx);
            } else {
              colMap[key] = idx;
            }
            headerRow = i;
            break;
          }
        }
      }
    });
    // 헤더가 최소 이름, 점수, 팀 중 하나라도 있으면 헤더로 간주
    if (Object.keys(colMap).length > 1) break;
  }
  if (headerRow === -1) return { participants: [], gameCount: 0, inferredEventInfo };

  // 데이터 행 파싱
  const participants = [];
  for (let i = headerRow + 1; i < rawData.length; i++) {
    const row = rawData[i];
    if (row.length < 2) continue;
    // 이름: 한글 2자 이상
    const name = (colMap.name !== undefined ? row[colMap.name] : row.find(cell => /^[가-힣a-z]{2,}$/i.test(cell)))?.toString().trim();
    if (!name) continue;
    const teamNumber = colMap.team !== undefined ? row[colMap.team] : '';
    const scores = (colMap.scores || []).map(idx => parseInt(row[idx], 10)).filter(n => !isNaN(n));
    const handicap = colMap.handicap !== undefined ? parseInt(row[colMap.handicap], 10) : null;
    const total = colMap.total !== undefined ? parseInt(row[colMap.total], 10) : null;
    const avg = colMap.avg !== undefined ? parseFloat(row[colMap.avg]) : null;
    // 회원 매핑 (이름 기준)
    const memberMatch = memberMap && memberMap.get && name ? memberMap.get(name.trim()) : null;
    participants.push({
      name,
      teamNumber,
      scores,
      handicap,
      total,
      avg,
      memberId: memberMatch ? memberMatch.id : null,
      memberInfo: memberMatch || null
    });
  }
  return {
    participants,
    gameCount: Math.max(...participants.map(p => p.scores.length), 0),
    inferredEventInfo
  };
}


// 파일에서 이벤트 정보 추론
function inferEventInfo(rawData) {
  const result = {
    title: null,
    titleCandidates: [],
    startDate: null,
    location: null
  };
  
  // 파일의 처음 10줄을 검색하여 이벤트 정보 추론
  const maxRows = Math.min(10, rawData.length);
  
  // 제목 추론 패턴 (키워드 포함 전체 구문 추출)
  const titlePatterns = [
    /(.+?정기전)/i,
    /(.+?대회)/i,
    /(.+?이벤트)/i,
    /(.+?토너먼트)/i,
    /(.+?경기)/i,
    /(.+?오픈)/i,
    /제\s*\d+\s*회\s*.+/i,
    /제\s*\d+\s*차\s*.+/i,
    /\d+\s*회\s*.+/i,
    /\d+\s*차\s*.+/i,
    /정기전\s*[:\-]?\s*.+/i,
    /대회\s*[:\-]?\s*.+/i,
    /이벤트\s*[:\-]?\s*.+/i
  ];
  
  // 날짜 추론 패턴
  const datePatterns = [
    // YYYY-MM-DD 형식
    /(\d{4})[-\/\.]\s*(\d{1,2})[-\/\.]\s*(\d{1,2})/,
    // YYYY년 MM월 DD일 형식
    /(\d{4})\s*년\s*(\d{1,2})\s*월\s*(\d{1,2})\s*일/,
    // MM월 DD일 형식 (현재 연도 가정)
    /(\d{1,2})\s*월\s*(\d{1,2})\s*일/,
    // 날짜: YYYY-MM-DD 형식
    /날짜\s*[:\-]?\s*(\d{4})[-\/\.]\s*(\d{1,2})[-\/\.]\s*(\d{1,2})/,
    // 일시: YYYY-MM-DD 형식
    /일시\s*[:\-]?\s*(\d{4})[-\/\.]\s*(\d{1,2})[-\/\.]\s*(\d{1,2})/,
    // 시작일: YYYY-MM-DD 형식
    /시작일\s*[:\-]?\s*(\d{4})[-\/\.]\s*(\d{1,2})[-\/\.]\s*(\d{1,2})/,
    // YYYYMMDD 형식
    /\s*(\d{4})\s*(\d{2})\s*(\d{2})\s*/,
    // YYMMDD, YY.MM.DD, YY-MM-DD 형식 (25.02.04, 25-02-04, 250204)
    /(\d{2})[.\-\/\s]?(\d{2})[.\-\/\s]?(\d{2})/
  ];
  
  // 파일명에서 정보 추론 (originalname이 있는 경우)
  if (rawData.originalname) {
    const filename = rawData.originalname;
    
    // 제목 추론 (모든 후보 수집)
    for (const pattern of titlePatterns) {
      const match = filename.match(pattern);
      if (match && match[1]) {
        const candidate = match[1].trim();
        if (!result.titleCandidates.includes(candidate)) {
          result.titleCandidates.push(candidate);
        }
      }
    }
    if (result.titleCandidates.length > 0) {
      result.title = result.titleCandidates[0];
    }
    
    // 날짜 추론
    for (const pattern of datePatterns) {
      const match = filename.match(pattern);
      if (match) {
        // 매칭 그룹의 수에 따라 다르게 처리
        if (match.length >= 4) { // YYYY-MM-DD 형식
          const year = parseInt(match[1]);
          const month = parseInt(match[2]) - 1; // JavaScript의 월은 0부터 시작
          const day = parseInt(match[3]);
          result.startDate = new Date(year, month, day);
        } else if (match.length >= 3) { // MM월 DD일 형식
          const currentYear = new Date().getFullYear();
          const month = parseInt(match[1]) - 1;
          const day = parseInt(match[2]);
          result.startDate = new Date(currentYear, month, day);
        }
        break;
      }
    }
  }
  
  // 파일 내용에서 정보 추론
  for (let i = 0; i < maxRows; i++) {
    const row = rawData[i];
    if (!row || !Array.isArray(row)) continue;
    
    // 행을 문자열로 변환하여 패턴 매칭
    const rowText = row.join(' ');
    
    // 제목 후보 수집
    for (const pattern of titlePatterns) {
      const match = rowText.match(pattern);
      if (match && match[1]) {
        const candidate = match[1].trim();
        if (!result.titleCandidates.includes(candidate)) {
          result.titleCandidates.push(candidate);
        }
      }
    }
    if (!result.title && result.titleCandidates.length > 0) {
      result.title = result.titleCandidates[0];
    }
    
    // 날짜가 아직 추론되지 않은 경우
    if (!result.startDate) {
      for (const pattern of datePatterns) {
        const match = rowText.match(pattern);
        if (match) {
          // 매칭 그룹의 수에 따라 다르게 처리
          if (match.length >= 4) {
            // YYYY-MM-DD, YYYYMMDD, YYMMDD, YY.MM.DD, YY-MM-DD 모두 처리
            let year = parseInt(match[1], 10);
            const month = parseInt(match[2], 10) - 1;
            const day = parseInt(match[3], 10);
            // 2자리 연도라면 보정
            if (match[1].length === 2) {
              year += 2000;
            }
            result.startDate = new Date(year, month, day);
          } else if (match.length === 3) {
            // MM월 DD일 형식 (현재 연도 가정)
            const currentYear = new Date().getFullYear();
            const month = parseInt(match[1]) - 1;
            const day = parseInt(match[2]);
            result.startDate = new Date(currentYear, month, day);
          }
          break;
        }
      }
    }
    
    // 모든 정보가 추론되었다면 중단
    if (result.title && result.startDate) {
      break;
    }
  }
  
  return result;
}

// 이벤트에 파일 데이터 저장
exports.saveEventFileData = async (req, res) => {
  const { eventData, participants, clubId } = req.body;
  
  if (!eventData || !participants || !clubId) {
    return res.status(400).json({ message: '이벤트 정보, 참가자 정보, 클럽 ID가 필요합니다.' });
  }
  // clubId 타입 보정 (문자열로 들어온 경우 Number로 변환)
  const cid = Number(clubId);
  if (!cid || Number.isNaN(cid)) {
    return res.status(400).json({ message: '유효한 클럽 ID가 필요합니다.' });
  }
  
  try {
    // 트랜잭션 시작
    const result = await sequelize.transaction(async (t) => {
      // 프런트에서 전달할 수 있는 핸디 포함 여부 플래그 (기본: true)
      const scoresIncludeHandicap = (
        (typeof req.body.scoresIncludeHandicap === 'boolean') ? req.body.scoresIncludeHandicap
        : (eventData && typeof eventData.scoresIncludeHandicap === 'boolean') ? eventData.scoresIncludeHandicap
        : true
      );
      // 이벤트 타입/상태 정규화 (모델 enum 호환)
      const normalizeEventType = (v) => {
        if (!v) return 'regular';
        const s = String(v).toLowerCase();
        if (['regular','일반'].includes(s)) return 'regular';
        if (['lightning','번개'].includes(s)) return 'lightning';
        if (['practice','연습'].includes(s)) return 'practice';
        if (['exchange','교류전'].includes(s)) return 'exchange';
        if (['event','행사'].includes(s)) return 'event';
        return 'other';
      };
      const normalizeStatus = (v) => {
        if (!v) return 'completed';
        const s = String(v).toLowerCase();
        if (['active','활성'].includes(s)) return 'active';
        if (['ready','대기','준비','draft'].includes(s)) return 'ready';
        if (['completed','완료','done','finished'].includes(s)) return 'completed';
        if (['canceled','cancelled','취소'].includes(s)) return 'canceled';
        if (['deleted','삭제'].includes(s)) return 'deleted';
        return 'completed';
      };

      // 1. 이벤트 생성 또는 업데이트
      let event;
      if (eventData.id) {
        // 기존 이벤트 업데이트
        event = await Event.findOne({
          where: { id: eventData.id, clubId: cid },
          transaction: t
        });
        
        if (!event) {
          throw new Error('이벤트를 찾을 수 없습니다.');
        }
        
        await event.update({
          title: eventData.title,
          description: eventData.description || '',
          // 비즈니스 규칙: 이벤트 유형은 정기전, 상태는 완료로 고정
          eventType: 'regular',
          startDate: new Date(eventData.startDate),
          endDate: eventData.endDate ? new Date(eventData.endDate) : null,
          location: eventData.location || '',
          gameCount: eventData.gameCount || 3,
          maxParticipants: eventData.maxParticipants || 0,
          status: 'completed'
        }, { transaction: t });
      } else {
        // 새 이벤트 생성
        event = await Event.create({
          clubId: cid,
          title: eventData.title,
          description: eventData.description || '',
          // 비즈니스 규칙: 이벤트 유형은 정기전으로 저장
          eventType: 'regular',
          startDate: new Date(eventData.startDate),
          endDate: eventData.endDate ? new Date(eventData.endDate) : null,
          location: eventData.location || '',
          gameCount: eventData.gameCount || 3,
          maxParticipants: eventData.maxParticipants || 0,
          // 비즈니스 규칙: 상태는 완료로 저장
          status: 'completed',
          publicHash: generateRandomHash()
        }, { transaction: t });
      }
      // 이벤트에서 최종 클럽ID 확정(추가 안정성)
      const effectiveClubId = Number(event.clubId);
      // 클럽 인스턴스 로드 (조인 테이블 조작용)
      const clubInstance = await Club.findOne({ where: { id: effectiveClubId, status: 'active' }, transaction: t });
      
      // 1.5 팀 정보 처리 - 참가자 처리 전에 팀 생성
      // 팀 번호별로 그룹화
      const teamGroups = {};
      participants.forEach(p => {
        if (p.teamNumber) {
          if (!teamGroups[p.teamNumber]) {
            teamGroups[p.teamNumber] = [];
          }
          teamGroups[p.teamNumber].push(p);
        }
      });
      
      // 팀 생성
      const createdTeams = {};
      for (const teamNumber in teamGroups) {
        // 기존 팀 확인
        let eventTeam = await EventTeam.findOne({
          where: {
            eventId: event.id,
            teamNumber: parseInt(teamNumber)
          },
          transaction: t
        });
        
        // 팀이 없으면 생성
        if (!eventTeam) {
          eventTeam = await EventTeam.create({
            eventId: event.id,
            teamNumber: parseInt(teamNumber),
            name: `팀 ${teamNumber}`,
            handicap: 0 // 기본 핸디캡 값
          }, { transaction: t });
        }
        
        createdTeams[teamNumber] = eventTeam;
      }
      
      // 2. 참가자 및 점수 처리
      const affectedMemberIds = new Set();
      for (const participant of participants) {
        let eventParticipant;
        
        // 참가자 생성 또는 업데이트
        if (participant.memberId) {
          // 기존 회원인 경우
          const existingParticipant = await EventParticipant.findOne({
            where: {
              eventId: event.id,
              memberId: participant.memberId
            },
            transaction: t
          });
          
          // 팀 ID 가져오기
          let teamId = null;
          if (participant.teamNumber && createdTeams[participant.teamNumber]) {
            teamId = createdTeams[participant.teamNumber].id;
          }
          
          if (existingParticipant) {
            // 기존 참가자 업데이트 (팀 정보 포함)
            await existingParticipant.update({
              teamId: teamId
            }, { transaction: t });
            eventParticipant = existingParticipant;
          } else {
            eventParticipant = await EventParticipant.create({
              eventId: event.id,
              memberId: participant.memberId,
              status: 'confirmed',
              paymentStatus: 'paid',
              teamId: teamId,
              comment: ''
            }, { transaction: t });
          }
          affectedMemberIds.add(Number(participant.memberId));
        } else {
          // 새 회원 생성 및 참가자 등록
          // 클럽 여성 기본 핸디캡 기준으로 성별/핸디캡 보정
          const femaleBase = Number(clubInstance?.femaleHandicap ?? 0) || 0;
          let parsedHandicap = null;
          if (participant.handicap !== undefined && participant.handicap !== null) {
            const ph = Number(participant.handicap);
            if (Number.isFinite(ph)) parsedHandicap = ph;
          }
          // 점수 객체에 handicap이 개별 제공되는 경우 최댓값을 사용
          if (parsedHandicap == null && Array.isArray(participant.scores)) {
            for (const s of participant.scores) {
              if (s && typeof s === 'object' && s.handicap !== undefined) {
                const sh = Number(s.handicap);
                if (Number.isFinite(sh)) {
                  parsedHandicap = parsedHandicap == null ? sh : Math.max(parsedHandicap, sh);
                }
              }
            }
          }
          const isFemaleByRule = (parsedHandicap != null && parsedHandicap >= femaleBase) ||
            (Array.isArray(participant.scores) && participant.scores.some((s) => Number(s?.handicap) >= femaleBase));
          const genderNormalized = (participant.gender === 'female' || participant.gender === '여성') ? 'female' : 'male';
          const finalGender = isFemaleByRule ? 'female' : genderNormalized;

          const newMember = await Member.create({
            clubId: effectiveClubId, // required by Member model
            name: participant.name,
            gender: finalGender,
            memberType: 'guest',
            status: 'active',
            handicap: parsedHandicap ?? 0
          }, { transaction: t });
          
          // 클럽 회원으로 등록 (조인 테이블을 association으로 처리)
          if (!clubInstance) {
            throw new Error('클럽을 찾을 수 없습니다.');
          }
          await clubInstance.addMember(newMember, {
            through: { joinDate: new Date(), status: 'active' },
            transaction: t,
          });
          
          // 팀 ID 가져오기
          let teamId = null;
          if (participant.teamNumber && createdTeams[participant.teamNumber]) {
            teamId = createdTeams[participant.teamNumber].id;
          }
          
          eventParticipant = await EventParticipant.create({
            eventId: event.id,
            memberId: newMember.id,
            status: 'confirmed',
            paymentStatus: 'paid',
            teamId: teamId,
            comment: ''
          }, { transaction: t });
          affectedMemberIds.add(Number(newMember.id));
        }
        
        // 팀에 속한 참가자인 경우 팀 멤버로 등록
        if (participant.teamNumber && createdTeams[participant.teamNumber]) {
          // 기존 팀 멤버 확인
          const existingTeamMember = await EventTeamMember.findOne({
            where: {
              eventTeamId: createdTeams[participant.teamNumber].id,
              eventParticipantId: eventParticipant.id
            },
            transaction: t
          });
          
          // 팀 멤버가 없으면 생성
          if (!existingTeamMember) {
            await EventTeamMember.create({
              eventTeamId: createdTeams[participant.teamNumber].id,
              eventParticipantId: eventParticipant.id,
              order: 1
            }, { transaction: t });
          }
        }
        
        // 점수 저장
        if (participant.scores && participant.scores.length > 0) {
          // 기존 점수 삭제
          await EventScore.destroy({
            where: {
              eventId: event.id,
              participantId: eventParticipant.id
            },
            transaction: t
          });
          
          // 새 점수 등록 (숫자 배열/객체 배열 모두 지원)
          const scoreItems = Array.isArray(participant.scores)
            ? participant.scores.map((s, idx) => (
                typeof s === 'number'
                  ? { gameNumber: idx + 1, score: s }
                  : s
              ))
            : [];

          for (const s of scoreItems) {
            const gameNumber = Number(s.gameNumber ?? 0) || 0;
            const handicap = s.handicap !== undefined ? Number(s.handicap) : Number(participant.handicap || 0);
            // scoresIncludeHandicap 플래그에 따라 저장 방식 결정
            const provided = s.totalScore !== undefined ? Number(s.totalScore) : Number(s.score ?? 0) || 0;
            const totalScore = scoresIncludeHandicap
              ? provided
              : Math.max(0, provided + (Number.isFinite(handicap) ? handicap : 0));
            const baseScore = scoresIncludeHandicap
              ? Math.max(0, provided - (Number.isFinite(handicap) ? handicap : 0))
              : provided;
            if (gameNumber <= 0) continue;

            await EventScore.create({
              eventId: event.id,
              participantId: eventParticipant.id,
              gameNumber,
              score: baseScore,
              handicap,
              totalScore,
              teamNumber: participant.teamNumber || null
            }, { transaction: t });
          }
        }
      }

      // 트랜잭션 내부에서는 목록만 반환 (커밋 후 워밍업)
      return { event, affectedMemberIds: Array.from(affectedMemberIds), effectiveClubId };
    });
    // 커밋 이후 평균 캐시 무효화 및 워밍업 (신규 점수가 커밋된 뒤 계산되도록)
    if (result && Array.isArray(result.affectedMemberIds)) {
      for (const mid of result.affectedMemberIds) {
        try {
          await averageService.invalidateMemberAverageCache(mid);
          await averageService.calculateMemberAverageByClubSetting(mid, result.effectiveClubId);
        } catch (e) {
          console.error('평균 캐시 갱신 실패:', e);
        }
      }
    }

    res.json({
      message: '이벤트 데이터가 성공적으로 저장되었습니다.',
      event: {
        id: result.event.id,
        title: result.event.title,
        eventType: result.event.eventType,
        startDate: result.event.startDate
      },
      rollbackToken: {
        eventId: result.event.id,
        clubId: Number(result.event.clubId)
      }
    });
  } catch (error) {
    console.error('이벤트 데이터 저장 오류:', error);
    res.status(500).json({ message: `이벤트 데이터 저장 중 오류가 발생했습니다: ${error.message}` });
  }
};

// 랜덤 해시 생성 함수
function generateRandomHash() {
  const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
  return Array.from({ length: 16 }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
}

// 임시 파일 삭제
exports.deleteUploadedFile = async (req, res) => {
  try {
    const { filename } = req.body;
    
    if (!filename) {
      return res.status(400).json({ message: '파일명이 필요합니다.' });
    }
    
    const filePath = path.join(uploadDir, filename);
    
    // 파일 존재 여부 확인
    if (fs.existsSync(filePath)) {
      fs.unlinkSync(filePath);
    }
    
    res.json({ message: '파일이 삭제되었습니다.' });
  } catch (error) {
    console.error('파일 삭제 오류:', error);
    res.status(500).json({ message: `파일 삭제 중 오류가 발생했습니다: ${error.message}` });
  }
};
