/**
 * CSRF Protection Middleware
 *
 * Cross-Site Request Forgery 방지를 위한 토큰 기반 보호
 *
 * 구현 방식:
 * 1. 서버에서 CSRF 토큰 생성 및 응답 헤더로 전송
 * 2. 클라이언트는 state-changing 요청 시 토큰을 헤더에 포함
 * 3. 서버에서 토큰 검증
 *
 * @author TK-FB-Project
 * @since 2026-02-04
 */

const crypto = require('crypto');
const logger = require('../utils/logger');

// 토큰 저장소 (프로덕션에서는 Redis 사용 권장)
const tokenStore = new Map();

// 토큰 유효 시간 (1시간)
const TOKEN_EXPIRY = 60 * 60 * 1000;

// 토큰 정리 주기 (5분)
const CLEANUP_INTERVAL = 5 * 60 * 1000;

/**
 * 만료된 토큰 정리
 */
const cleanupExpiredTokens = () => {
  const now = Date.now();
  for (const [token, data] of tokenStore.entries()) {
    if (now > data.expiresAt) {
      tokenStore.delete(token);
    }
  }
};

// 주기적 정리
setInterval(cleanupExpiredTokens, CLEANUP_INTERVAL);

/**
 * CSRF 토큰 생성
 *
 * @param {string} sessionId - 세션 ID 또는 사용자 식별자
 * @returns {string} 생성된 CSRF 토큰
 */
const generateToken = (sessionId) => {
  const token = crypto.randomBytes(32).toString('hex');
  const expiresAt = Date.now() + TOKEN_EXPIRY;

  tokenStore.set(token, {
    sessionId,
    expiresAt,
    createdAt: Date.now()
  });

  return token;
};

/**
 * CSRF 토큰 검증
 *
 * @param {string} token - 검증할 토큰
 * @param {string} sessionId - 세션 ID
 * @returns {boolean} 유효 여부
 */
const validateToken = (token, sessionId) => {
  if (!token || !tokenStore.has(token)) {
    return false;
  }

  const data = tokenStore.get(token);

  // 만료 체크
  if (Date.now() > data.expiresAt) {
    tokenStore.delete(token);
    return false;
  }

  // 세션 일치 체크
  if (data.sessionId !== sessionId) {
    return false;
  }

  return true;
};

/**
 * CSRF 토큰을 응답 헤더에 설정하는 미들웨어
 *
 * @param {Object} req - Express request
 * @param {Object} res - Express response
 * @param {Function} next - Next middleware
 */
const setCsrfToken = (req, res, next) => {
  // 세션 ID 또는 사용자 ID 사용
  const sessionId = req.user?.user_id?.toString() || req.sessionID || req.ip;

  // 새 토큰 생성
  const token = generateToken(sessionId);

  // 응답 헤더에 토큰 설정
  res.setHeader('X-CSRF-Token', token);

  // 요청 객체에 저장 (다른 미들웨어에서 사용 가능)
  req.csrfToken = token;

  next();
};

/**
 * CSRF 토큰 검증 미들웨어
 * POST, PUT, DELETE, PATCH 요청에 적용
 *
 * @param {Object} options - 옵션
 * @param {string[]} options.ignoreMethods - 검증 제외 메서드 (기본: GET, HEAD, OPTIONS)
 * @param {string[]} options.ignorePaths - 검증 제외 경로 (정규식 패턴 가능)
 * @returns {Function} Express 미들웨어
 */
const verifyCsrfToken = (options = {}) => {
  const {
    ignoreMethods = ['GET', 'HEAD', 'OPTIONS'],
    ignorePaths = ['/api/auth/login', '/api/auth/register', '/api/health']
  } = options;

  return (req, res, next) => {
    // 제외 메서드 체크
    if (ignoreMethods.includes(req.method)) {
      return next();
    }

    // 제외 경로 체크
    for (const pattern of ignorePaths) {
      if (typeof pattern === 'string' && req.path === pattern) {
        return next();
      }
      if (pattern instanceof RegExp && pattern.test(req.path)) {
        return next();
      }
    }

    // 토큰 추출 (헤더 또는 body에서)
    const token = req.headers['x-csrf-token'] ||
                  req.headers['csrf-token'] ||
                  req.body?._csrf ||
                  req.query?._csrf;

    // 세션 ID
    const sessionId = req.user?.user_id?.toString() || req.sessionID || req.ip;

    // 토큰 검증
    if (!validateToken(token, sessionId)) {
      logger.warn('CSRF 토큰 검증 실패', {
        path: req.path,
        method: req.method,
        ip: req.ip,
        hasToken: !!token
      });

      return res.status(403).json({
        success: false,
        error: 'CSRF 토큰이 유효하지 않습니다. 페이지를 새로고침 후 다시 시도해주세요.',
        code: 'CSRF_TOKEN_INVALID'
      });
    }

    // 사용된 토큰 제거 (일회성 사용)
    tokenStore.delete(token);

    // 새 토큰 발급
    const newToken = generateToken(sessionId);
    res.setHeader('X-CSRF-Token', newToken);
    req.csrfToken = newToken;

    next();
  };
};

/**
 * CSRF 토큰 발급 엔드포인트 핸들러
 * GET /api/csrf-token
 */
const getCsrfToken = (req, res) => {
  const sessionId = req.user?.user_id?.toString() || req.sessionID || req.ip;
  const token = generateToken(sessionId);

  res.json({
    success: true,
    csrfToken: token,
    expiresIn: TOKEN_EXPIRY / 1000 // 초 단위
  });
};

module.exports = {
  generateToken,
  validateToken,
  setCsrfToken,
  verifyCsrfToken,
  getCsrfToken
};
