const express = require('express');
const rateLimit = require('express-rate-limit');
const path = require('path');
const bodyParser = require('body-parser');
const API = require('./src/js/index.js');
const { logger } = require('./src/js/logger.js');
const favicon = require('serve-favicon');
const app = express();
const port = process.env.PORT || 3512;
require('./src/js/utils.js');

app.set('trust proxy', true);

// Middleware
// app.use(bodyParser.json({ limit: '10mb' }));
app.use(bodyParser.urlencoded({ extended: true, limit: '10mb' }));
app.use(express.static(__dirname));
app.use(express.static(path.join(__dirname, 'public')));
app.use('/images', express.static(path.join(__dirname, 'src/images')));
app.use(favicon(path.join(__dirname, 'src', 'images', 'favicon.ico')));
app.use(
  bodyParser.json({
    limit: '10mb',
    verify: (req, res, buf) => {
      req.rawBody = buf.toString();
    },
  })
);
// app.use(express.raw({ type: 'application/json', limit: '10mb' }));

const fs = require('fs');

let DEFAULT_SSO_TOKEN = '';

// Try to read the token from a file
try {
  const tokenPath = path.join(__dirname, 'token.txt');
  if (fs.existsSync(tokenPath)) {
    DEFAULT_SSO_TOKEN = fs.readFileSync(tokenPath, 'utf8').trim();
    logger.info('Default SSO token loaded successfully from token.txt');
  } else {
    logger.warn('token.txt not found, demo mode may not function correctly');
  }
} catch (error) {
  logger.error('Error loading token file:', { error: error.message });
}

setInterval(() => demoModeRequestTracker.cleanupExpiredEntries(), 3600000); // Clean up every hour

// Configure rate limiting for demo mode
const demoModeRequestTracker = {
  requests: new Map(),
  maxRequestsPerHour: 20, // Adjust as needed
  resetInterval: 60 * 60 * 1000, // 1 hour in milliseconds

  isLimited: function (ip) {
    const now = Date.now();
    const userRequests = this.requests.get(ip) || {
      count: 0,
      resetTime: now + this.resetInterval,
    };

    // Reset counter if the reset time has passed
    if (now > userRequests.resetTime) {
      userRequests.count = 0;
      userRequests.resetTime = now + this.resetInterval;
    }

    return userRequests.count >= this.maxRequestsPerHour;
  },

  incrementCounter: function (ip) {
    const now = Date.now();
    const userRequests = this.requests.get(ip) || {
      count: 0,
      resetTime: now + this.resetInterval,
    };

    // Reset counter if the reset time has passed
    if (now > userRequests.resetTime) {
      userRequests.count = 1; // Start with 1 for this request
      userRequests.resetTime = now + this.resetInterval;
    } else {
      userRequests.count++;
    }

    this.requests.set(ip, userRequests);
    return userRequests.count;
  },

  cleanupExpiredEntries: function () {
    const now = Date.now();
    for (const [ip, data] of this.requests.entries()) {
      if (now > data.resetTime) {
        // Either remove entries completely or reset them to 0
        this.requests.delete(ip);
      }
    }
  },
};

// Demo mode middleware
const demoModeMiddleware = (req, res, next) => {
  // Skip non-API routes
  if (!req.path.startsWith('/api/')) {
    return next();
  }

  // Get client IP for tracking
  // const clientIP = req.headers['x-forwarded-for'] || req.ip || req.connection.remoteAddress;
  const clientIP =
    req.headers['cf-connecting-ip'] ||
    req.headers['x-forwarded-for']?.split(',')[0] ||
    req.ip ||
    req.connection.remoteAddress;

  // Check if demo mode is active
  if (DEFAULT_SSO_TOKEN) {
    // For API endpoints, check rate limit in demo mode
    if (demoModeRequestTracker.isLimited(clientIP)) {
      const userRequests = demoModeRequestTracker.requests.get(clientIP);
      const resetTimeMs = userRequests.resetTime - Date.now();

      // Format time as HH:MM:SS
      const hours = Math.floor(resetTimeMs / (60 * 60 * 1000));
      const minutes = Math.floor(
        (resetTimeMs % (60 * 60 * 1000)) / (60 * 1000)
      );
      const seconds = Math.floor((resetTimeMs % (60 * 1000)) / 1000);
      const timeFormat = `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;

      logger.warn(`Demo mode rate limit exceeded for IP: ${clientIP}`);
      return res.status(429).json({
        status: 'error',
        message: `Please try again in ${timeFormat} or use your own SSO token.`,
        timestamp: global.Utils.toIsoString(new Date()),
      });
    }

    // Increment the counter
    //   const count = demoModeRequestTracker.incrementCounter(clientIP);
    //   logger.debug(`Demo mode request count for ${clientIP}: ${count}`);
  }

  next();
};

function incrementDemoCounter(req, ssoToken) {
  if (ssoToken === DEFAULT_SSO_TOKEN) {
    const clientIP =
      req.headers['cf-connecting-ip'] ||
      req.headers['x-forwarded-for']?.split(',')[0] ||
      req.ip ||
      req.connection.remoteAddress;

    const count = demoModeRequestTracker.incrementCounter(clientIP);
    logger.debug(`Demo mode request count for ${clientIP}: ${count}`);
  }
}

app.use(demoModeMiddleware);
app.use((req, res, next) => {
  if (req.query.demo === 'true' || req.query.demo === '1') {
    // Enable demo mode for this session if it's not already enabled
    if (!req.session) {
      req.session = {};
    }
    req.session.forceDemoMode = true;
    logger.info('Demo mode enabled via URL parameter');
  } else if (req.query.demo === 'false' || req.query.demo === '0') {
    // Disable demo mode for this session
    if (req.session) {
      req.session.forceDemoMode = false;
    }
    logger.info('Demo mode disabled via URL parameter');
  }
  next();
});

// Initialize key replacements
let keyReplacements = {};

try {
  const replacementsPath = path.join(
    __dirname,
    'src',
    'data',
    'replacements.json'
  );
  if (fs.existsSync(replacementsPath)) {
    const replacementsContent = fs.readFileSync(replacementsPath, 'utf8');
    keyReplacements = JSON.parse(replacementsContent);
    // logger.debug("Replacements loaded successfully");
  } else {
    logger.warn('replacements.json not found, key replacement disabled');
  }
} catch (error) {
  logger.error('Error loading replacements file:', { error: error.message });
}

const replaceJsonKeys = (obj) => {
  if (!obj || typeof obj !== 'object') return obj;

  if (Array.isArray(obj)) {
    return obj.map((item) => replaceJsonKeys(item));
  }

  const newObj = {};
  Object.keys(obj).forEach((key) => {
    // Replace key if it exists in replacements
    const newKey = keyReplacements[key] || key;

    // DEBUG: Log replacements when they happen
    // if (newKey !== key) {
    //   logger.debug(`Replacing key "${key}" with "${newKey}"`);
    // }

    // Also check if the value should be replaced (if it's a string)
    let value = obj[key];
    if (typeof value === 'string' && keyReplacements[value]) {
      value = keyReplacements[value];
      // logger.debug(`Replacing value "${obj[key]}" with "${value}"`);
    }

    // Process value recursively if it's an object or array
    newObj[newKey] = replaceJsonKeys(value);
  });

  return newObj;
};

// Utility regex function
const sanitizeJsonOutput = (data) => {
  if (!data) return data;

  // Convert to string to perform regex operations
  const jsonString = JSON.stringify(data);

  // Define regex pattern that matches HTML entities
  const regexPattern =
    /<span class=".*?">|<\/span>|">/g;

  // Replace unwanted patterns
  const sanitizedString = jsonString.replace(regexPattern, '');

  // Parse back to object
  try {
    return JSON.parse(sanitizedString);
  } catch (e) {
    logger.error('Error parsing sanitized JSON:', e);
    return data; // Return original data if parsing fails
  }
};

// Replace the processJsonOutput function with this more efficient version
const processJsonOutput = (
  data,
  options = { sanitize: true, replaceKeys: true }
) => {
  // Use a more efficient deep clone approach instead of JSON.parse(JSON.stringify())
  function deepClone(obj) {
    if (obj === null || typeof obj !== 'object') {
      return obj;
    }

    if (Array.isArray(obj)) {
      return obj.map((item) => deepClone(item));
    }

    const clone = {};
    Object.keys(obj).forEach((key) => {
      clone[key] = deepClone(obj[key]);
    });

    return clone;
  }

  // Create a deep copy of the data to avoid reference issues
  let processedData = deepClone(data);

  // Apply sanitization if needed
  if (options.sanitize) {
    processedData = sanitizeJsonOutput(processedData);
  }

  // Apply key replacement if needed
  if (options.replaceKeys) {
    processedData = replaceJsonKeys(processedData);
  }

  return processedData;
};

// Store active sessions to avoid repeated logins
const activeSessions = new Map();

// Utility function to create a timeout promise
const timeoutPromise = (ms) => {
  return new Promise((_, reject) => {
    setTimeout(() => reject(new Error(`Request timed out after ${ms}ms`)), ms);
  });
};

// Helper function to ensure login
const ensureLogin = async (ssoToken) => {
  if (!activeSessions.has(ssoToken)) {
    logger.info(
      `Attempting to login with SSO token: ${ssoToken.substring(0, 5)}...`
    );
    // logger.info(`Attempting to login with SSO token: ${ssoToken}`);
    const loginResult = await Promise.race([
      API.login(ssoToken),
      timeoutPromise(10000), // 10 second timeout
    ]);

    logger.debug(`Login successful: ${JSON.stringify(loginResult)}`);
    logger.debug(`Session created at: ${global.Utils.toIsoString(new Date())}`);
    activeSessions.set(ssoToken, new Date());
  } else {
    logger.debug('Using existing session');
  }
};

// Helper function to handle API errors
const handleApiError = (error, res) => {
  logger.error('API Error:', error);
  logger.error(`Error Stack: ${error.stack}`);
  logger.error(`Error Time: ${global.Utils.toIsoString(new Date())}`);

  // Try to extract more useful information from the error
  let errorMessage = error.message || 'Unknown API error';
  let errorName = error.name || 'ApiError';

  // Handle the specific JSON parsing error
  if (errorName === 'SyntaxError' && errorMessage.includes('JSON')) {
    logger.debug('JSON parsing error detected');
    return res.status(200).json({
      status: 'error',
      message:
        'Failed to parse API response. This usually means the SSO token is invalid or expired.',
      error_type: 'InvalidResponseError',
      timestamp: global.Utils.toIsoString(new Date()),
    });
  }

  // Send a more graceful response
  return res.status(200).json({
    status: 'error',
    message: errorMessage,
    error_type: errorName,
    timestamp: global.Utils.toIsoString(new Date()),
  });
};

// API endpoint to fetch stats
app.post('/api/stats', async (req, res) => {
  logger.debug('Received request for /api/stats');
  logger.debug(
    `Request IP: ${
      req.headers['x-forwarded-for'] || req.ip || req.connection.remoteAddress
    }`
  );
  logger.debug(
    `Request JSON: ${JSON.stringify({
      username: req.body.username,
      platform: req.body.platform,
      game: req.body.game,
      apiCall: req.body.apiCall,
      sanitize: req.body.sanitize,
      replaceKeys: req.body.replaceKeys,
    })}`
  );

  try {
    let { username, ssoToken, platform, game, apiCall, sanitize, replaceKeys } =
      req.body;

    if (!ssoToken && DEFAULT_SSO_TOKEN) {
      ssoToken = DEFAULT_SSO_TOKEN;
      logger.info('Using default SSO token for demo mode');
    } else if (!ssoToken && !DEFAULT_SSO_TOKEN) {
      return res.status(200).json({
        status: 'error',
        message: 'SSO Token is required as demo mode is not active',
        timestamp: global.Utils.toIsoString(new Date()),
      });
    }

    /*
    logger.debug(
      `Request details - Username: ${username}, Platform: ${platform}, Game: ${game}, API Call: ${apiCall}`
    );

    logger.debug("=== STATS REQUEST ===");
    logger.debug(`Username: ${username || 'Not provided'}`);
    logger.debug(`Platform: ${platform}`);
    logger.debug(`Game: ${game}`);
    logger.debug(`API Call: ${apiCall}`);
    logger.debug(`Processing Options - Sanitize: ${sanitize}, Replace Keys: ${replaceKeys}`);
    logger.debug("====================="); */

    // For mapList, username is not required
    if (apiCall !== 'mapList' && !username) {
      return res.status(400).json({ error: 'Username is required' });
    }

    // Clear previous session if it exists
    if (activeSessions.has(ssoToken)) {
      logger.debug('Clearing previous session');
      activeSessions.delete(ssoToken);
    }

    // Login with the provided SSO token
    try {
      await ensureLogin(ssoToken);
    } catch (loginError) {
      logger.error('Login error:', loginError);
      return res.status(200).json({
        status: 'error',
        error_type: 'LoginError',
        message: 'SSO token login failed',
        details: loginError.message || 'Unknown login error',
        timestamp: global.Utils.toIsoString(new Date()),
      });
    }

    // Create a wrapped function for each API call to handle timeout
    const fetchWithTimeout = async (apiFn) => {
      return Promise.race([
        apiFn(),
        timeoutPromise(30000), // 30 second timeout
      ]);
    };

    // Check if the platform is valid for the game
    const requiresUno = ['mw2', 'wz2', 'mw3', 'wzm'].includes(game);
    if (requiresUno && platform !== 'uno' && apiCall !== 'mapList') {
      logger.warn(`${game} requires Uno ID`);
      return res.status(200).json({
        status: 'error',
        message: `${game} requires Uno ID (numerical ID)`,
        timestamp: global.Utils.toIsoString(new Date()),
      });
    }

    try {
      logger.debug(
        `Attempting to fetch ${game} data for ${username} on ${platform}`
      );
      let data;

      if (apiCall === 'fullData') {
        // Fetch lifetime stats based on game
        switch (game) {
          case 'mw':
            data = await fetchWithTimeout(() =>
              API.ModernWarfare.fullData(username, platform)
            );
            break;
          case 'wz':
            data = await fetchWithTimeout(() =>
              API.Warzone.fullData(username, platform)
            );
            break;
          case 'mw2':
            data = await fetchWithTimeout(() =>
              API.ModernWarfare2.fullData(username)
            );
            break;
          case 'wz2':
            data = await fetchWithTimeout(() =>
              API.Warzone2.fullData(username)
            );
            break;
          case 'mw3':
            data = await fetchWithTimeout(() =>
              API.ModernWarfare3.fullData(username)
            );
            break;
          case 'cw':
            data = await fetchWithTimeout(() =>
              API.ColdWar.fullData(username, platform)
            );
            break;
          case 'vg':
            data = await fetchWithTimeout(() =>
              API.Vanguard.fullData(username, platform)
            );
            break;
          case 'wzm':
            data = await fetchWithTimeout(() =>
              API.WarzoneMobile.fullData(username)
            );
            break;
          default:
            return res.status(200).json({
              status: 'error',
              message: 'Invalid game selected',
              timestamp: global.Utils.toIsoString(new Date()),
            });
        }
      } else if (apiCall === 'combatHistory') {
        // Fetch recent match history based on game
        switch (game) {
          case 'mw':
            data = await fetchWithTimeout(() =>
              API.ModernWarfare.combatHistory(username, platform)
            );
            break;
          case 'wz':
            data = await fetchWithTimeout(() =>
              API.Warzone.combatHistory(username, platform)
            );
            break;
          case 'mw2':
            data = await fetchWithTimeout(() =>
              API.ModernWarfare2.combatHistory(username)
            );
            break;
          case 'wz2':
            data = await fetchWithTimeout(() =>
              API.Warzone2.combatHistory(username)
            );
            break;
          case 'mw3':
            data = await fetchWithTimeout(() =>
              API.ModernWarfare3.combatHistory(username)
            );
            break;
          case 'cw':
            data = await fetchWithTimeout(() =>
              API.ColdWar.combatHistory(username, platform)
            );
            break;
          case 'vg':
            data = await fetchWithTimeout(() =>
              API.Vanguard.combatHistory(username, platform)
            );
            break;
          case 'wzm':
            data = await fetchWithTimeout(() =>
              API.WarzoneMobile.combatHistory(username)
            );
            break;
          default:
            return res.status(200).json({
              status: 'error',
              message: 'Invalid game selected',
              timestamp: global.Utils.toIsoString(new Date()),
            });
        }
      } else if (apiCall === 'mapList') {
        // Fetch map list (only valid for MW)
        if (game === 'mw') {
          data = await fetchWithTimeout(() =>
            API.ModernWarfare.mapList(platform)
          );
        } else {
          return res.status(200).json({
            status: 'error',
            message: 'Map list is only available for Modern Warfare',
            timestamp: global.Utils.toIsoString(new Date()),
          });
        }
      }

      logger.debug('Data fetched successfully');
      logger.debug(`Response Size: ~${JSON.stringify(data).length / 1024} KB`);
      logger.debug(`Response Time: ${global.Utils.toIsoString(new Date())}`);

      // Safely handle the response data
      if (!data) {
        logger.warn('No data returned from API');
        return res.json({
          status: 'partial_success',
          message: 'No data returned from API, but no error thrown',
          data: null,
          timestamp: global.Utils.toIsoString(new Date()),
        });
      }

      // logger.debug("Returning data to client");

      const { sanitize, replaceKeys } = req.body;

      incrementDemoCounter(req, ssoToken);

      return res.json({
        // status: "success",
        data: processJsonOutput(data, { sanitize, replaceKeys }),
        timestamp: global.Utils.toIsoString(new Date()),
      });
    } catch (apiError) {
      return handleApiError(apiError, res);
    }
  } catch (serverError) {
    logger.error('Server Error:', serverError);

    // Return a structured error response even for unexpected errors
    return res.status(200).json({
      status: 'server_error',
      message: 'The server encountered an unexpected error',
      error_details: serverError.message || 'Unknown server error',
      timestamp: global.Utils.toIsoString(new Date()),
    });
  }
});

// API endpoint to fetch recent matches
app.post('/api/matches', async (req, res) => {
  logger.debug('Received request for /api/matches');
  logger.debug(
    `Request IP: ${
      req.headers['x-forwarded-for'] || req.ip || req.connection.remoteAddress
    }`
  );
  logger.debug(
    `Request JSON: ${JSON.stringify({
      username: req.body.username,
      platform: req.body.platform,
      game: req.body.game,
      sanitize: req.body.sanitize,
      replaceKeys: req.body.replaceKeys,
    })}`
  );

  try {
    let { username, ssoToken, platform, game, sanitize, replaceKeys } =
      req.body;

    /*
    logger.debug(
       `Request details - Username: ${username}, Platform: ${platform}, Game: ${game}`
    );

    logger.debug("=== MATCHES REQUEST ===");
    logger.debug(`Username: ${username}`);
    logger.debug(`Platform: ${platform}`);
    logger.debug(`Game: ${game}`);
    logger.debug(`Processing Options - Sanitize: ${sanitize}, Replace Keys: ${replaceKeys}`);
    logger.debug("========================"); */

    if (!username) {
      return res.status(400).json({ error: 'Username is required' });
    }

    if (!ssoToken && DEFAULT_SSO_TOKEN) {
      ssoToken = DEFAULT_SSO_TOKEN;
      logger.info('Using default SSO token for demo mode');
    } else if (!ssoToken && !DEFAULT_SSO_TOKEN) {
      return res.status(200).json({
        status: 'error',
        message: 'SSO Token is required as demo mode is not active',
        timestamp: global.Utils.toIsoString(new Date()),
      });
    }

    try {
      await ensureLogin(ssoToken);
    } catch (loginError) {
      return res.status(200).json({
        status: 'error',
        error_type: 'LoginError',
        message: 'SSO token login failed',
        details: loginError.message || 'Unknown login error',
        timestamp: global.Utils.toIsoString(new Date()),
      });
    }

    // Create a wrapped function for each API call to handle timeout
    const fetchWithTimeout = async (apiFn) => {
      return Promise.race([
        apiFn(),
        timeoutPromise(30000), // 30 second timeout
      ]);
    };

    try {
      logger.debug(
        `Attempting to fetch combat history for ${username} on ${platform}`
      );
      let data;

      // Check if the platform is valid for the game
      const requiresUno = ['mw2', 'wz2', 'mw3', 'wzm'].includes(game);
      if (requiresUno && platform !== 'uno') {
        return res.status(200).json({
          status: 'error',
          message: `${game} requires Uno ID (numerical ID)`,
          timestamp: global.Utils.toIsoString(new Date()),
        });
      }

      // Fetch combat history based on game
      switch (game) {
        case 'mw':
          data = await fetchWithTimeout(() =>
            API.ModernWarfare.combatHistory(username, platform)
          );
          break;
        case 'wz':
          data = await fetchWithTimeout(() =>
            API.Warzone.combatHistory(username, platform)
          );
          break;
        case 'mw2':
          data = await fetchWithTimeout(() =>
            API.ModernWarfare2.combatHistory(username)
          );
          break;
        case 'wz2':
          data = await fetchWithTimeout(() =>
            API.Warzone2.combatHistory(username)
          );
          break;
        case 'mw3':
          data = await fetchWithTimeout(() =>
            API.ModernWarfare3.combatHistory(username)
          );
          break;
        case 'cw':
          data = await fetchWithTimeout(() =>
            API.ColdWar.combatHistory(username, platform)
          );
          break;
        case 'vg':
          data = await fetchWithTimeout(() =>
            API.Vanguard.combatHistory(username, platform)
          );
          break;
        case 'wzm':
          data = await fetchWithTimeout(() =>
            API.WarzoneMobile.combatHistory(username)
          );
          break;
        default:
          return res.status(200).json({
            status: 'error',
            message: 'Invalid game selected',
            timestamp: global.Utils.toIsoString(new Date()),
          });
      }

      const { sanitize, replaceKeys } = req.body;

      incrementDemoCounter(req, ssoToken);

      return res.json({
        // status: "success",
        data: processJsonOutput(data, { sanitize, replaceKeys }),
        timestamp: global.Utils.toIsoString(new Date()),
      });
    } catch (apiError) {
      return handleApiError(apiError, res);
    }
  } catch (serverError) {
    return res.status(200).json({
      status: 'server_error',
      message: 'The server encountered an unexpected error',
      error_details: serverError.message || 'Unknown server error',
      timestamp: global.Utils.toIsoString(new Date()),
    });
  }
});

// API endpoint to fetch match info
app.post('/api/matchInfo', async (req, res) => {
  logger.debug('Received request for /api/matchInfo');
  logger.debug(
    `Request IP: ${
      req.headers['x-forwarded-for'] || req.ip || req.connection.remoteAddress
    }`
  );
  logger.debug(
    `Request JSON: ${JSON.stringify({
      matchId: req.body.matchId,
      platform: req.body.platform,
      game: req.body.game,
      sanitize: req.body.sanitize,
      replaceKeys: req.body.replaceKeys,
    })}`
  );

  try {
    let { matchId, ssoToken, platform, game, sanitize, replaceKeys } = req.body;

    /*
    logger.debug(
      `Request details - Match ID: ${matchId}, Platform: ${platform}, Game: ${game}`
    );

    logger.debug("=== MATCH INFO REQUEST ===");
    logger.debug(`Match ID: ${matchId}`);
    logger.debug(`Platform: ${platform}`);
    logger.debug(`Game: ${game}`);
    logger.debug(`Processing Options - Sanitize: ${sanitize}, Replace Keys: ${replaceKeys}`);
    logger.debug("=========================="); */

    if (!matchId) {
      return res.status(400).json({ error: 'Match ID is required' });
    }

    if (!ssoToken && DEFAULT_SSO_TOKEN) {
      ssoToken = DEFAULT_SSO_TOKEN;
      logger.info('Using default SSO token for demo mode');
    } else if (!ssoToken && !DEFAULT_SSO_TOKEN) {
      return res.status(200).json({
        status: 'error',
        message: 'SSO Token is required as demo mode is not active',
        timestamp: global.Utils.toIsoString(new Date()),
      });
    }

    try {
      await ensureLogin(ssoToken);
    } catch (loginError) {
      return res.status(200).json({
        status: 'error',
        error_type: 'LoginError',
        message: 'SSO token login failed',
        details: loginError.message || 'Unknown login error',
        timestamp: global.Utils.toIsoString(new Date()),
      });
    }

    // Create a wrapped function for each API call to handle timeout
    const fetchWithTimeout = async (apiFn) => {
      return Promise.race([
        apiFn(),
        timeoutPromise(30000), // 30 second timeout
      ]);
    };

    try {
      logger.debug(`Attempting to fetch match info for match ID: ${matchId}`);
      let data;

      // Fetch match info based on game
      switch (game) {
        case 'mw':
          data = await fetchWithTimeout(() =>
            API.ModernWarfare.matchInfo(matchId, platform)
          );
          break;
        case 'wz':
          data = await fetchWithTimeout(() =>
            API.Warzone.matchInfo(matchId, platform)
          );
          break;
        case 'mw2':
          data = await fetchWithTimeout(() =>
            API.ModernWarfare2.matchInfo(matchId)
          );
          break;
        case 'wz2':
          data = await fetchWithTimeout(() => API.Warzone2.matchInfo(matchId));
          break;
        case 'mw3':
          data = await fetchWithTimeout(() =>
            API.ModernWarfare3.matchInfo(matchId)
          );
          break;
        case 'cw':
          data = await fetchWithTimeout(() =>
            API.ColdWar.matchInfo(matchId, platform)
          );
          break;
        case 'vg':
          data = await fetchWithTimeout(() =>
            API.Vanguard.matchInfo(matchId, platform)
          );
          break;
        case 'wzm':
          data = await fetchWithTimeout(() =>
            API.WarzoneMobile.matchInfo(matchId)
          );
          break;
        default:
          return res.status(200).json({
            status: 'error',
            message: 'Invalid game selected',
            timestamp: global.Utils.toIsoString(new Date()),
          });
      }

      const { sanitize, replaceKeys } = req.body;

      incrementDemoCounter(req, ssoToken);

      return res.json({
        // status: "success",
        data: processJsonOutput(data, { sanitize, replaceKeys }),
        timestamp: global.Utils.toIsoString(new Date()),
      });
    } catch (apiError) {
      return handleApiError(apiError, res);
    }
  } catch (serverError) {
    return res.status(200).json({
      status: 'server_error',
      message: 'The server encountered an unexpected error',
      error_details: serverError.message || 'Unknown server error',
      timestamp: global.Utils.toIsoString(new Date()),
    });
  }
});

// API endpoint for user-related API calls
app.post('/api/user', async (req, res) => {
  logger.debug('Received request for /api/user');
  logger.debug(
    `Request IP: ${
      req.headers['x-forwarded-for'] || req.ip || req.connection.remoteAddress
    }`
  );
  logger.debug(
    `Request JSON: ${JSON.stringify({
      username: req.body.username,
      platform: req.body.platform,
      userCall: req.body.userCall,
      sanitize: req.body.sanitize,
      replaceKeys: req.body.replaceKeys,
    })}`
  );

  try {
    let { username, ssoToken, platform, userCall, sanitize, replaceKeys } =
      req.body;

    /*
    logger.debug(
      `Request details - Username: ${username}, Platform: ${platform}, User Call: ${userCall}`
    );

    logger.debug("=== USER DATA REQUEST ===");
    logger.debug(`Username: ${username || 'Not provided'}`);
    logger.debug(`Platform: ${platform}`);
    logger.debug(`User Call: ${userCall}`);
    logger.debug(`Processing Options - Sanitize: ${sanitize}, Replace Keys: ${replaceKeys}`);
    logger.debug("========================="); */

    // For eventFeed and identities, username is not required
    if (
      !username &&
      userCall !== 'eventFeed' &&
      userCall !== 'friendFeed' &&
      userCall !== 'identities'
    ) {
      return res
        .status(400)
        .json({ error: 'Username is required for this API call' });
    }

    if (!ssoToken && DEFAULT_SSO_TOKEN) {
      ssoToken = DEFAULT_SSO_TOKEN;
      logger.info('Using default SSO token for demo mode');
    } else if (!ssoToken && !DEFAULT_SSO_TOKEN) {
      return res.status(200).json({
        status: 'error',
        message: 'SSO Token is required as demo mode is not active',
        timestamp: global.Utils.toIsoString(new Date()),
      });
    }

    try {
      await ensureLogin(ssoToken);
    } catch (loginError) {
      return res.status(200).json({
        status: 'error',
        error_type: 'LoginError',
        message: 'SSO token login failed',
        details: loginError.message || 'Unknown login error',
        timestamp: global.Utils.toIsoString(new Date()),
      });
    }

    // Create a wrapped function for each API call to handle timeout
    const fetchWithTimeout = async (apiFn) => {
      return Promise.race([
        apiFn(),
        timeoutPromise(30000), // 30 second timeout
      ]);
    };

    try {
      logger.debug(`Attempting to fetch user data for ${userCall}`);
      let data;

      // Fetch user data based on userCall
      switch (userCall) {
        case 'codPoints':
          data = await fetchWithTimeout(() =>
            API.Me.codPoints(username, platform)
          );
          break;
        case 'connectedAccounts':
          data = await fetchWithTimeout(() =>
            API.Me.connectedAccounts(username, platform)
          );
          break;
        case 'eventFeed':
          data = await fetchWithTimeout(() => API.Me.eventFeed());
          break;
        case 'friendFeed':
          data = await fetchWithTimeout(() =>
            API.Me.friendFeed(username, platform)
          );
          break;
        case 'identities':
          data = await fetchWithTimeout(() => API.Me.loggedInIdentities());
          break;
        case 'friendsList':
          data = await fetchWithTimeout(() => API.Me.friendsList());
          break;
        // case "settings":
        //   data = await fetchWithTimeout(() =>
        //     API.Me.settings(username, platform)
        //   );
        //   break;
        default:
          return res.status(200).json({
            status: 'error',
            message: 'Invalid user API call selected',
            timestamp: global.Utils.toIsoString(new Date()),
          });
      }

      const { sanitize, replaceKeys } = req.body;

      incrementDemoCounter(req, ssoToken);

      return res.json({
        // status: "success",
        data: processJsonOutput(data, { sanitize, replaceKeys }),
        timestamp: global.Utils.toIsoString(new Date()),
      });
    } catch (apiError) {
      return handleApiError(apiError, res);
    }
  } catch (serverError) {
    return res.status(200).json({
      status: 'server_error',
      message: 'The server encountered an unexpected error',
      error_details: serverError.message || 'Unknown server error',
      timestamp: global.Utils.toIsoString(new Date()),
    });
  }
});

// API endpoint for fuzzy search
app.post('/api/search', async (req, res) => {
  logger.debug('Received request for /api/search');
  logger.debug(
    `Request IP: ${
      req.headers['x-forwarded-for'] || req.ip || req.connection.remoteAddress
    }`
  );
  logger.debug(
    `Request JSON: ${JSON.stringify({
      username: req.body.username,
      platform: req.body.platform,
      sanitize: req.body.sanitize,
      replaceKeys: req.body.replaceKeys,
    })}`
  );

  try {
    let { username, ssoToken, platform, sanitize, replaceKeys } = req.body;

    /*
    logger.debug(
      `Request details - Match ID: ${matchId}, Platform: ${platform}, Game: ${game}`
    );

    logger.debug("=== MATCH INFO REQUEST ===");
    logger.debug(`Match ID: ${matchId}`);
    logger.debug(`Platform: ${platform}`);
    logger.debug(`Game: ${game}`);
    logger.debug(`Processing Options - Sanitize: ${sanitize}, Replace Keys: ${replaceKeys}`);
    logger.debug("=========================="); */

    if (!username) {
      return res.status(400).json({ error: 'Username is required' });
    }

    if (!ssoToken && DEFAULT_SSO_TOKEN) {
      ssoToken = DEFAULT_SSO_TOKEN;
      logger.info('Using default SSO token for demo mode');
    } else if (!ssoToken && !DEFAULT_SSO_TOKEN) {
      return res.status(200).json({
        status: 'error',
        message: 'SSO Token is required as demo mode is not active',
        timestamp: global.Utils.toIsoString(new Date()),
      });
    }

    try {
      await ensureLogin(ssoToken);
    } catch (loginError) {
      return res.status(200).json({
        status: 'error',
        error_type: 'LoginError',
        message: 'SSO token login failed',
        details: loginError.message || 'Unknown login error',
        timestamp: global.Utils.toIsoString(new Date()),
      });
    }

    // Create a wrapped function for each API call to handle timeout
    const fetchWithTimeout = async (apiFn) => {
      return Promise.race([
        apiFn(),
        timeoutPromise(30000), // 30 second timeout
      ]);
    };

    try {
      logger.debug(
        `Attempting fuzzy search for ${username} on platform ${platform}`
      );
      const data = await fetchWithTimeout(() =>
        API.Misc.search(username, platform)
      );

      const { sanitize, replaceKeys } = req.body;

      incrementDemoCounter(req, ssoToken);

      return res.json({
        // status: "success",
        data: processJsonOutput(data, { sanitize, replaceKeys }),
        timestamp: global.Utils.toIsoString(new Date()),
        // link: "Stats pulled using codtracker.rimmyscorner.com",
      });
    } catch (apiError) {
      return handleApiError(apiError, res);
    }
  } catch (serverError) {
    return res.status(200).json({
      status: 'server_error',
      message: 'The server encountered an unexpected error',
      error_details: serverError.message || 'Unknown server error',
      timestamp: global.Utils.toIsoString(new Date()),
    });
  }
});

// Improved logging endpoint
app.post('/api/log', (req, res) => {
  const clientIP =
    req.headers['cf-connecting-ip'] ||
    req.headers['x-forwarded-for']?.split(',')[0] ||
    req.ip ||
    req.connection.remoteAddress;
  const userAgent = req.headers['user-agent'];
  const referer = req.headers['referer'];
  const origin = req.headers['origin'];
  let logData;

  try {
    // Handle data whether it comes as already parsed JSON or as a string
    if (typeof req.body === 'string') {
      logData = JSON.parse(req.body);
    } else if (req.body && typeof req.body === 'object') {
      logData = req.body;
    } else {
      // If no parsable data found, create a basic log entry
      logData = {
        eventType: 'unknown',
        timestamp: global.Utils.toIsoString(new Date()),
      };
    }

    // Enrich log with server-side data
    const enrichedLog = {
      ...logData,
      meta: {
        clientIP,
        userAgent,
        referer,
        origin,
        requestHeaders: sanitizeHeaders(req.headers),
        serverTimestamp: global.Utils.toIsoString(new Date()),
        requestId: req.id || Math.random().toString(36).substring(2, 15),
      },
    };

    // Use the dedicated user activity logger
    logger.userActivity(enrichedLog.eventType || 'unknown', enrichedLog);
  } catch (error) {
    logger.error('Error processing log data', {
      error: error.message,
      rawBody: typeof req.body === 'object' ? '[Object]' : req.body,
    });
  }

  // Always return 200 to avoid client-side errors
  res.status(200).send();
});

// Helper function to remove sensitive data from headers
function sanitizeHeaders(headers) {
  const safeHeaders = { ...headers };

  // Remove potential sensitive information
  const sensitiveHeaders = ['authorization', 'cookie', 'set-cookie'];
  sensitiveHeaders.forEach((header) => {
    if (safeHeaders[header]) {
      safeHeaders[header] = '[REDACTED]';
    }
  });

  return safeHeaders;
}

// Database storage function
/*
function storeLogInDatabase(logData) {
  // Example with MongoDB
  db.collection('user_logs').insertOne(logData)
    .catch(err => logger.error('Failed to store log in database:', err));
}
*/

app.get('/health', (req, res) => {
  const clientIP =
    req.headers['cf-connecting-ip'] ||
    req.headers['x-forwarded-for']?.split(',')[0] ||
    req.ip ||
    req.connection.remoteAddress;
  // Check if demo mode is forced via URL parameter
  const forceDemoMode = req.session && req.session.forceDemoMode === true;

  const isDemoMode = !!DEFAULT_SSO_TOKEN || forceDemoMode;
  res.json({
    status: 'ok',
    timestamp: global.Utils.toIsoString(new Date()),
    demoMode: isDemoMode,
    requestsRemaining:
      isDemoMode ?
        demoModeRequestTracker.maxRequestsPerHour -
        (demoModeRequestTracker.requests.get(clientIP)?.count || 0)
      : null,
  });
});

app.get('/demo', (req, res) => {
  // Set a cookie or session variable to enable demo mode
  if (!req.session) {
    req.session = {};
  }
  req.session.forceDemoMode = true;

  // Redirect to the main app
  res.redirect('/');
});

// Serve the main HTML file
app.get('/', (req, res) => {
  res.sendFile(path.join(__dirname, 'src', 'index.html'));
});

// Start the server
app.listen(port, () => {
  logger.info(`Server running on http://localhost:${port}`);
});