// backend/src/server.js
const express = require('express');
const http = require('http');
const { Server } = require('socket.io');
const cors = require('cors');
const dotenv = require('dotenv');
const path = require('path');
const jwt = require('jsonwebtoken');

dotenv.config();

// Fallback for JWT_SECRET REMOVED for security. Server will fail if not set.
if (!process.env.JWT_SECRET) {
  console.error('❌ FATAL: JWT_SECRET is not defined in environment variables. Exiting.');
  process.exit(1);
}

const authRoutes = require('./routes/authRoutes');
const alertRoutes = require('./routes/alertRoutes');
const profileRoutes = require('./routes/profileRoutes');
const paymentRoutes = require('./routes/paymentRoutes');
const adminRoutes = require('./routes/adminRoutes');
const newsRoutes = require('./routes/newsRoutes');
const logRoutes = require('./routes/logRoutes');
const referralRoutes = require('./routes/referralRoutes');
const cron = require('node-cron');
const blogAutomationService = require('./services/blogAutomationService');
const { syncStripeSubscriptions } = require('./utils/stripeSyncLogic');
const { generatePlaybook } = require('./utils/playbookGenerator');

console.log('--- SERVER STARTUP ---');
console.log('Current Work Directory:', process.cwd());
try { require('fs').writeFileSync('debug_server_start.log', `Restarted at ${new Date().toISOString()} in ${process.cwd()}\n`); } catch(e) {}

// Rate Limiting
const rateLimit = require('express-rate-limit');
const helmet = require('helmet');

// Global Rate Limiter for API
const globalLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 3000, // Limit each IP to 3000 requests per windowMs (Relaxed for Dev)
  message: { message: 'Too many requests from this IP, please try again after 15 minutes' },
  standardHeaders: true,
  legacyHeaders: false,
});

// General AI Limiter (Generations)
const aiGenLimiter = rateLimit({
  windowMs: 60 * 60 * 1000, // 1 hour
  max: 20, // Limit each IP to 20 generation requests per hour
  message: { message: 'Too many generation requests from this IP, please try again after an hour' },
  standardHeaders: true,
  legacyHeaders: false,
});

// Strict AI Tutor Limiter (Chat)
const aiTutorLimiter = rateLimit({
  windowMs: 1 * 60 * 1000, // 1 minute
  max: 10, // Limit each IP to 10 chat messages per minute
  message: { message: 'Slow down! You are sending messages too fast.' },
  standardHeaders: true,
  legacyHeaders: false,
});

process.on('uncaughtException', (err) => {
  console.error('❌ UNCAUGHT EXCEPTION! Server would have crashed:', err);
  // In production, we might want to exit, but for debugging cPanel 503s, let's try to keep it alive or log heavily
  console.error(err.stack);
});

process.on('unhandledRejection', (reason, promise) => {
  console.error('❌ UNHANDLED REJECTION!:', reason);
  // promise.catch((err) => console.error(err));
});

const { evaluateInvestigation } = require('./utils/aiService');
const db = require('./db');
const migrateDb = require('./migrateDb');

// Run automatic DB migration
const { runAutoGrader } = require('./services/autoGrader');

// Run automatic DB migration
const seedTemplates = require('./seedEmailTemplates');
const retrofitSimulations = require('./utils/retrofitSimulations');

// SAFE STARTUP WRAPPER
(async () => {
    try {
        console.log('🔄 Starting Database Migration...');
        await migrateDb();
        console.log('✅ Database Migration Completed');
    } catch (err) {
        console.error('❌ MIGRATION FAILED (Continuing anyway to keep server alive):', err);
    }

    try {
        console.log('🔧 Checking for missing Simulation Data...');
        await retrofitSimulations();
    } catch (err) {
        console.error('⚠️ Simulation Retrofit Warning:', err);
    }

    try {
        console.log('🌱 Seeding Email Templates...');
        await seedTemplates();
    } catch (err) {
        console.error('⚠️ Seeding Templates Failed:', err);
    }
})();

// Schedule Daily Stripe Sync (e.g., at 00:00 every day)
cron.schedule('0 0 * * *', async () => {
  console.log('Running daily Stripe sync job...');
  await syncStripeSubscriptions();
});

// Schedule AI Evaluation Check (Every Minute)
cron.schedule('* * * * *', async () => {
  await runAutoGrader();
});

// Schedule Pending Investigation Safety Net (Every 2 Minutes)
// Catches investigations that got stuck in 'submitted' due to server restarts or errors
const { processPendingSubmissions } = require('./utils/gradingService');
cron.schedule('*/2 * * * *', async () => {
    await processPendingSubmissions();
});

// Initialize Monthly Leaderboard Cron Jobs
const initCronJobs = require('./jobs/cronJobs');
initCronJobs();

// Schedule Automated Daily Threat Briefing (AI-Powered)
// Runs every morning at 09:00
const alertAutomationService = require('./services/alertAutomationService');
cron.schedule('0 9 * * *', async () => {
    console.log('[Cron] Initiating Daily Threat Briefing...');
    try {
        await alertAutomationService.runDailyBriefing();
    } catch (error) {
        console.error('[Cron] Daily Threat Briefing Failed:', error);
    }
}, { recoverMissedExecutions: false });

// Schedule Weekly Report (Mondays at 09:00)
cron.schedule('0 9 * * 1', async () => {
  console.log('Generating Weekly Analyst Reports...');
  try {
    // Prevent duplicate runs within the same day
    const lastRunResult = await db.query(
      "SELECT value FROM system_settings WHERE `key` = 'weekly_report_last_run'"
    );
    const today = new Date().toISOString().substring(0, 10); // e.g., "2026-01-05"
    if (lastRunResult.rows.length > 0 && lastRunResult.rows[0].value === today) {
      console.log(`Weekly report already sent today (${today}). Skipping.`);
      return;
    }

    // Mark as running immediately to prevent race conditions
    const todayStr = new Date().toISOString().substring(0, 10);
    await db.query(
      "INSERT INTO system_settings (`key`, value, category) VALUES (?, ?, 'system') ON DUPLICATE KEY UPDATE value = ?",
      ['weekly_report_last_run', todayStr, todayStr]
    );

    const { sendWeeklyReport } = require('./utils/emailService');

    // Get all users (including their email preferences)
    const usersResult = await db.query("SELECT id, name, email, email_preferences FROM users WHERE role != 'admin'");
    const users = usersResult.rows;

    for (const user of users) {
      // Check email preferences - skip if user opted out
      let prefs = user.email_preferences || {};
      if (typeof prefs === 'string') {
        try { prefs = JSON.parse(prefs); } catch (e) { prefs = {}; }
      }
      if (prefs.weekly_report === false) {
        console.log(`Skipping weekly report for ${user.email} (opted out)`);
        continue;
      }

      // Calculate stats for LAST 7 DAYS
      const weeklyStatsQuery = await db.query(`
        SELECT 
          COUNT(*) as graded_count,
          SUM(grade) as total_score,
          AVG(grade) as avg_score
        FROM investigations 
        WHERE user_id = ? 
        AND status = 'graded' 
        AND updated_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
      `, [user.id]);

      const assignedQuery = await db.query(`
        SELECT COUNT(*) as assigned_count 
        FROM investigations 
        WHERE user_id = ? 
        AND created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
      `, [user.id]);

      // Calculate LIFETIME stats
      const lifetimeQuery = await db.query(`
        SELECT 
          COUNT(*) as total_investigations,
          SUM(grade) as total_xp,
          AVG(grade) as lifetime_avg,
          MAX(grade) as best_score
        FROM investigations 
        WHERE user_id = ? 
        AND status = 'graded'
      `, [user.id]);

      // Calculate leaderboard rank
      const rankQuery = await db.query(`
        SELECT COUNT(*) + 1 as rank 
        FROM (
          SELECT user_id, SUM(grade) as total 
          FROM investigations WHERE status='graded' 
          GROUP BY user_id
        ) as scores 
        WHERE total > (
          SELECT COALESCE(SUM(grade), 0) 
          FROM investigations 
          WHERE user_id = ? AND status = 'graded'
        )
      `, [user.id]);

      const weeklyStats = weeklyStatsQuery.rows[0];
      const assigned = assignedQuery.rows[0];
      const lifetime = lifetimeQuery.rows[0];
      const rank = rankQuery.rows[0];

      // If user has no graded investigations, they are "Unranked"
      const hasGradedWork = (lifetime.total_investigations || 0) > 0;

      const reportStats = {
        // Weekly stats
        assigned: assigned.assigned_count || 0,
        graded: weeklyStats.graded_count || 0,
        avgScore: Math.round(weeklyStats.avg_score || 0),
        totalScore: weeklyStats.total_score || 0,
        // Lifetime stats
        totalInvestigations: lifetime.total_investigations || 0,
        totalXP: lifetime.total_xp || 0,
        lifetimeAvg: Math.round(lifetime.lifetime_avg || 0),
        bestScore: lifetime.best_score || 0,
        rank: hasGradedWork ? (rank.rank || 1) : 'Unranked'
      };

      // Prevent sending empty reports if the user was completely inactive this week
      const hasWeeklyActivity = reportStats.assigned > 0 || reportStats.graded > 0;
      
      if (!hasWeeklyActivity) {
        console.log(`Skipping weekly report for ${user.email} (no activity this week)`);
        continue;
      }

      console.log(`Sending weekly report to ${user.email}`);
      await sendWeeklyReport(user.email, user.name, reportStats);
    }

    console.log('Weekly reports sent successfully.');
  } catch (error) {
    console.error('Error generating weekly reports:', error);
  }
}, { recoverMissedExecutions: false });

// Schedule Automatic Alert Generation (Checks every hour)
cron.schedule('0 * * * *', async () => {
  try {
    const enabledResult = await db.query(
      "SELECT value FROM system_settings WHERE `key` = 'ai_auto_gen_enabled'"
    );
    let enabled = enabledResult.rows.length > 0 ? enabledResult.rows[0].value : 'false';
    if (typeof enabled === 'string') enabled = enabled.replace(/^"|"$/g, '');

    if (enabled === 'true') {
      // Fetch AI Settings
      const settingsResult = await db.query(
        "SELECT `key`, value FROM system_settings WHERE category = 'ai_integration'"
      );
      const settings = {};
      settingsResult.rows.forEach(row => {
        try { settings[row.key] = JSON.parse(row.value); }
        catch (e) { settings[row.key] = row.value; }
      });

      // Clean up string values
      if (settings.ai_api_key && typeof settings.ai_api_key === 'string') {
        settings.ai_api_key = settings.ai_api_key.replace(/^"|"$/g, '');
      }

      // Check Schedule
      const frequency = settings.ai_alert_frequency || 'daily'; // hourly, 6hours, 12hours, daily, weekly
      const currentHour = new Date().getHours();
      const currentDay = new Date().getDay(); // 0 = Sunday

      let shouldRun = false;

      switch (frequency) {
        case 'hourly':
          shouldRun = true;
          break;
        case '6hours':
          if (currentHour % 6 === 0) shouldRun = true;
          break;
        case '12hours':
          if (currentHour % 12 === 0) shouldRun = true; // 00:00 and 12:00
          break;
        case 'weekly':
          // Run on Monday at 08:00
          if (currentDay === 1 && currentHour === 8) shouldRun = true;
          break;
        case 'daily':
        default:
          // Run every day at 08:00
          if (currentHour === 8) shouldRun = true;
          break;
      }

      if (shouldRun) {
        // Prevent duplicate runs within the same hour (cross-process or after restart)
        const lastRunResult = await db.query(
          "SELECT value FROM system_settings WHERE `key` = 'ai_auto_gen_last_run'"
        );
        let lastRun = lastRunResult.rows.length > 0 ? lastRunResult.rows[0].value : null;
        const currentHourTimestamp = new Date().toISOString().substring(0, 13); // e.g., "2026-01-02T10"
        
        if (lastRun && lastRun.includes(currentHourTimestamp)) {
          console.log(`[Cron] Alert generation already ran for this hour (${currentHourTimestamp}). Skipping to prevent duplicates.`);
          return;
        }

        console.log(`Running automatic alert generation (Frequency: ${frequency})...`);

        const { generateAlertContent } = require('./utils/aiService');

        // Determine number of alerts to generate (Default 2, Max 10)
        let count = parseInt(settings.ai_alert_count) || 2;
        if (count < 1) count = 1;
        if (count > 10) count = 10;


        // Calculate Verdict Distribution based on Strategy
        const strategy = settings.ai_strategy || 'realistic';
        const distributionHours = parseInt(settings.ai_distribution_hours) || 0;

        console.log(`Auto-Generating with Strategy: ${strategy}, Distribution: ${distributionHours}h`);

        let tp = 0, fp = 0, benign = 0;

        // Strategy Logic (Consistent with Frontend)
        if (count > 0) {
          switch (strategy) {
            case 'high_threat':
              tp = Math.ceil(count * 0.9);
              fp = Math.floor(count * 0.05);
              benign = count - tp - fp;
              break;
            case 'noisy':
              tp = Math.floor(count * 0.2);
              fp = Math.ceil(count * 0.7);
              benign = count - tp - fp;
              break;
            case 'random':
              // For random, we don't strictly enforce TP/FP counts in the "verdictDistribution" object passed to AI
              // We let the AI prompt handle the randomness, OR we randomize the distribution here.
              // Let's randomize here for better control.
              const rand = Math.random();
              if (rand < 0.33) {
                tp = Math.floor(count * 0.4); fp = Math.floor(count * 0.4); benign = count - tp - fp;
              } else if (rand < 0.66) {
                tp = Math.ceil(count * 0.8); fp = Math.floor(count * 0.1); benign = count - tp - fp;
              } else {
                tp = Math.floor(count * 0.2); fp = Math.ceil(count * 0.6); benign = count - tp - fp;
              }
              break;
            case 'realistic':
            default:
              tp = Math.ceil(count * 0.6);
              fp = Math.floor(count * 0.3);
              benign = count - tp - fp;
              break;
          }
          // Adjust for rounding
          const currentSum = tp + fp + benign;
          if (currentSum > count) tp -= (currentSum - count);
          if (currentSum < count) benign += (count - currentSum);

          tp = Math.max(0, tp); fp = Math.max(0, fp); benign = Math.max(0, benign);
        }

        // Generate alerts
        // Correct Signature: count, apiKey, provider, options
        const generatedAlerts = await generateAlertContent(
          count,
          settings.ai_api_key,
          settings.ai_provider || 'openai',
          {
            sources: [], // Random/All
            verdictDistribution: { true_positive: tp, false_positive: fp, benign },
            includeRawLogs: true,
            strategy: strategy, // Pass strategy to AI service
            useV2: true // FORCE V2 GENERATION
          }
        );

        console.log(`[Cron] Generated ${generatedAlerts.length} alerts. Sample Playbook:`, generatedAlerts[0]?.playbook_solution ? 'Present (V2)' : 'Missing');

        for (const alertData of generatedAlerts) {
          // Calculate created_at
          let createdAt = new Date();
          if (distributionHours > 0) {
            // Random delay between 0 and distributionHours (DISTRIBUTED OVER THE PAST)
            const randomDelayMs = Math.random() * distributionHours * 60 * 60 * 1000;
            createdAt = new Date(createdAt.getTime() - randomDelayMs);
          }

          // Generate Playbook Solution (Prefer AI generated one)
          let playbookSolObj = alertData.playbook_solution;
          if (!playbookSolObj) {
             playbookSolObj = generatePlaybook(alertData.alert_type, alertData.severity, alertData.title, alertData.raw_log, alertData.details);
          }
          const playbookSolution = JSON.stringify(playbookSolObj);

          await db.query(
            'INSERT INTO alerts (title, severity, status, source, details, alert_type, mitre_technique, real_world_example, raw_log, created_at, playbook_solution, is_ai_generated, is_false_positive) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?)',
            [
              alertData.title,
              alertData.severity,
              'New',
              alertData.source,
              alertData.details,
              alertData.alert_type,
              alertData.mitre_technique,
              alertData.real_world_example ? 1 : 0,
              JSON.stringify(alertData.raw_log),
              createdAt,
              playbookSolution,
              alertData.is_false_positive ? 1 : 0
            ]
          );
        }

        // Mark as run for this hour to prevent duplicate runs from other processes
        const nowIso = new Date().toISOString().substring(0, 13); // e.g. "2026-01-02T10"
        await db.query(
           "UPDATE system_settings SET value = ?, updated_at = NOW() WHERE `key` = 'ai_auto_gen_last_run'",
           [nowIso]
        );

        console.log(`[Cron] Automatic alert generation complete. Generated ${generatedAlerts.length} alerts.`);

        // Notify connected clients
        if (global.io) {
          global.io.emit('notification', { message: `${generatedAlerts.length} new AI alerts have been generated!` });
        }
      }
    }
  } catch (error) {
    console.error('Error in auto-generation cron:', error);
  }
}, { recoverMissedExecutions: false });

console.log('DB Config:', {
  host: process.env.DB_HOST,
  user: process.env.DB_USER,
  port: process.env.DB_PORT,
  name: process.env.DB_NAME,
  jwt_secret_exists: !!process.env.JWT_SECRET
});

const app = express();

// Security Middleware
// Security Middleware
app.use(helmet({
  contentSecurityPolicy: {
    useDefaults: false,
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", "'unsafe-inline'", "'unsafe-eval'", "https://static.cloudflareinsights.com", "https://www.googletagmanager.com", "https://js.stripe.com", "blob:"],
      connectSrc: ["'self'", "https://www.google-analytics.com", "https://api.stripe.com", "https://m.stripe.network", "https://stats.g.doubleclick.net"],
      frameSrc: ["'self'", "https://js.stripe.com", "https://hooks.stripe.com"],
      imgSrc: ["'self'", "data:", "https://*.stripe.com", "https://www.google-analytics.com"],
      styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
      fontSrc: ["'self'", "https://fonts.gstatic.com", "data:"],
      objectSrc: ["'none'"],
      upgradeInsecureRequests: [],
    },
  },
  crossOriginEmbedderPolicy: false,
  crossOriginResourcePolicy: { policy: "cross-origin" },
}));
app.use('/api/', globalLimiter); // Apply global rate limit to API routes

const allowedOrigins = [process.env.CLIENT_ORIGIN || 'http://localhost:5173', 'http://localhost:5174', 'https://infoseclabs.io', 'https://platform.infoseclabs.io'];
app.use(cors({
  origin: function (origin, callback) {
    // Allow requests with no origin (like mobile apps or curl requests)
    if (!origin) return callback(null, true);
    
    // Explicitly allow localhost for development
    if (origin.includes('localhost') || origin.includes('127.0.0.1')) {
        return callback(null, true);
    }

    if (allowedOrigins.indexOf(origin) === -1) {
      console.log('CORS checking:', origin);
      // For now, allow all to prevent breaking user's current setup during transition
      return callback(null, true);
    }
    return callback(null, true);
  },
  credentials: true
}));

// Use JSON parser for all routes except webhook
app.use((req, res, next) => {
  if (req.originalUrl === '/api/payments/webhook' || req.originalUrl === '/api/payment/webhook') {
    next();
  } else {
    express.json()(req, res, next);
  }
});

app.use('/uploads', express.static('uploads'));

// Middleware to attach io to req
app.use((req, res, next) => {
  req.io = io;
  next();
});

console.log('--- CHECKPOINT 7.1: Loading Basic Routes ---');
const settingsRoutes = require('./routes/settingsRoutes');
const trainingRoutes = require('./routes/trainingRoutes');
const contactRoutes = require('./routes/contactRoutes');
const learningPathRoutes = require('./routes/learningPaths');
console.log('--- CHECKPOINT 7.2: Basic Routes Loaded ---');

app.use('/api/auth', authRoutes);
app.use('/api/alerts', alertRoutes);
app.use('/api/profile', profileRoutes);
app.use('/api/payments', paymentRoutes);
app.use('/api/payment', paymentRoutes); // Also mount at singular for frontend compatibility
app.use('/api/admin', adminRoutes);
app.use('/api/referrals', referralRoutes);
app.use('/api/settings', settingsRoutes);
app.use('/api/logs', logRoutes);
app.use('/api/training', trainingRoutes);
app.use('/api/contact', contactRoutes);
app.use('/api/feedback', require('./routes/feedbackRoutes'));
app.use('/api/news', newsRoutes);
const blogRoutes = require('./routes/blogRoutes');
app.use('/api/blog', blogRoutes);
app.use('/api/learning-paths', learningPathRoutes);

console.log('--- CHECKPOINT 7.3: Loading Operation Routes ---');
// Operations (Narrative Campaigns)
const operationRoutes = require('./routes/operationRoutes');
app.use('/api/operations', operationRoutes);
console.log('--- CHECKPOINT 7.4: Operation Routes Loaded ---');

// Specialization Paths & Assignments (Autonomous Training System)
const assignmentRoutes = require('./routes/assignmentRoutes');
app.use('/api/assignments', assignmentRoutes);

// AI Generator API
const aiRoutes = require('./routes/aiRoutes');
app.use('/api/ai/generate', aiGenLimiter); // Apply strictly to generation
app.use('/api/ai/tutor', aiTutorLimiter);  // Apply strictly to tutor
app.use('/api/ai', aiRoutes); // Mount all AI routes

// Mobile App API
const mobileRoutes = require('./routes/mobileRoutes');
app.use('/api/mobile', mobileRoutes);

// CTF Progress API
const ctfRoutes = require('./routes/ctfRoutes');
app.use('/api/ctf', ctfRoutes);

console.log('--- CHECKPOINT 7.5: Loading Organization Routes ---');
// Organization/Enterprise API
const organizationRoutes = require('./routes/organizationRoutes');
app.use('/api/organizations', organizationRoutes);
console.log('--- CHECKPOINT 7.6: Organization Routes Loaded ---');

// Skill Assessment API
const assessmentRoutes = require('./routes/assessmentRoutes');
app.use('/api/assessments', assessmentRoutes);

// XP & Skill Stats API
const xpRoutes = require('./routes/xpRoutes');
app.use('/api/xp', xpRoutes);

// PDF Reports API
const reportRoutes = require('./routes/reportRoutes');
app.use('/api/reports', reportRoutes);

// Admin Organization Management
const { protect: orgProtect, admin: orgAdmin } = require('./middleware/authMiddleware');
const adminOrgController = require('./controllers/adminOrgController');
app.get('/api/admin/organizations', orgProtect, orgAdmin, adminOrgController.getAllOrganizations);
app.put('/api/admin/organizations/:id', orgProtect, orgAdmin, adminOrgController.adminUpdateOrganization);
app.get('/api/admin/organizations/:id/stats', orgProtect, orgAdmin, adminOrgController.getOrganizationStats);

// TEMP TEST ROUTE - REMOVE LATER
app.post('/api/admin/test-weekly-report', orgProtect, orgAdmin, async (req, res) => {
  console.log('--- TEST: Generating Weekly Analyst Reports ---');
  try {
    const { sendWeeklyReport } = require('./utils/emailService');

    const usersResult = await db.query("SELECT id, name, email, email_preferences FROM users WHERE role != 'admin' LIMIT 5");
    const users = usersResult.rows;

    let sentCount = 0;
    let skippedCount = 0;

    for (const user of users) {
      let prefs = user.email_preferences || {};
      if (typeof prefs === 'string') {
        try { prefs = JSON.parse(prefs); } catch (e) { prefs = {}; }
      }
      if (prefs.weekly_report === false) {
        console.log(`[TEST] Skipping ${user.email} (opted out)`);
        skippedCount++;
        continue;
      }

      const weeklyStatsQuery = await db.query(`
        SELECT COUNT(*) as graded_count, SUM(grade) as total_score, AVG(grade) as avg_score
        FROM investigations WHERE user_id = ? AND status = 'graded' AND updated_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
      `, [user.id]);

      const assignedQuery = await db.query(`
        SELECT COUNT(*) as assigned_count FROM investigations WHERE user_id = ? AND created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
      `, [user.id]);

      const weeklyStats = weeklyStatsQuery.rows[0];
      const assigned = assignedQuery.rows[0];

      // Get lifetime stats for test
      const lifetimeQuery = await db.query(`
        SELECT COUNT(*) as total_investigations, SUM(grade) as total_xp, AVG(grade) as lifetime_avg, MAX(grade) as best_score
        FROM investigations WHERE user_id = ? AND status = 'graded'
      `, [user.id]);

      const rankQuery = await db.query(`
        SELECT COUNT(*) + 1 as rank 
        FROM (
          SELECT user_id, SUM(grade) as total 
          FROM investigations WHERE status='graded' 
          GROUP BY user_id
        ) as scores 
        WHERE total > (
          SELECT COALESCE(SUM(grade), 0) FROM investigations WHERE user_id = ? AND status = 'graded'
        )
      `, [user.id]);

      const lifetime = lifetimeQuery.rows[0];
      const rank = rankQuery.rows[0];
      const hasGradedWork = (lifetime.total_investigations || 0) > 0;

      const reportStats = {
        // Weekly
        assigned: assigned.assigned_count || 0,
        graded: weeklyStats.graded_count || 0,
        avgScore: Math.round(weeklyStats.avg_score || 0),
        totalScore: weeklyStats.total_score || 0,
        // Lifetime
        totalInvestigations: lifetime.total_investigations || 0,
        totalXP: lifetime.total_xp || 0,
        bestScore: lifetime.best_score || 0,
        rank: hasGradedWork ? (rank.rank || 1) : 'Unranked'
      };

      const hasWeeklyActivity = reportStats.assigned > 0 || reportStats.graded > 0;
      
      if (!hasWeeklyActivity) {
        console.log(`[TEST] Skipping weekly report for ${user.email} (no activity)`);
        skippedCount++;
        continue;
      }

      console.log(`[TEST] Sending weekly report to ${user.email} (Assigned: ${reportStats.assigned}, Graded: ${reportStats.graded})`);
      sentCount++;
    }

    res.json({ message: 'Test complete', sent: sentCount, skipped: skippedCount });
  } catch (error) {
    console.error('Test error:', error);
    res.status(500).json({ error: error.message });
  }
});

// Public Newsletter Subscribe (no auth required)
const newsletterController = require('./controllers/newsletterController');
app.post('/api/newsletter/subscribe', newsletterController.publicSubscribe);

// Dynamic Sitemap
const sitemapController = require('./controllers/sitemapController');
app.get('/sitemap.xml', sitemapController.getSitemap);

// Simple health check
app.get('/api/health', (req, res) => res.json({ status: 'ok' }));

// Version Check
app.get('/api/version', (req, res) => {
  try {
    const versionPath = path.join(__dirname, '../version.json');
    if (require('fs').existsSync(versionPath)) {
      const versionData = require(versionPath);
      res.json(versionData);
    } else {
      res.json({ version: 'dev', timestamp: new Date().toISOString() });
    }
  } catch (error) {
    res.status(500).json({ message: 'Error reading version' });
  }
});

// Handle 404 for API routes specifically to avoid falling back to HTML
app.use('/api/*', (req, res) => {
  res.status(404).json({ message: `API route not found: ${req.originalUrl}` });
});

// Serve static files in production
// Serve static files in production
// Serve static files (SPA Support)
// Run this regardless of NODE_ENV to ensure it works on cPanel even if env var is missing
const localPublicPath = path.join(__dirname, '../public');
const devDistPath = path.join(__dirname, '../frontend/dist');
const fs = require('fs');

if (fs.existsSync(localPublicPath)) {
  console.log('Serving static files from local public directory:', localPublicPath);
  
  // 1. Dynamic SEO Handlers (MUST BE BEFORE express.static to capture these specific routes)

  // Dynamic SEO for Mission Pages (for crawlers and social media)
  app.get('/missions/:slug', async (req, res) => {
    try {
      const { slug } = req.params;
      
      // Fetch operation data from database
      const result = await db.query(
        'SELECT title, apt_group, description, story_intro, seo_title, seo_description FROM operations WHERE slug = ?',
        [slug]
      );
      
      if (result.rows.length > 0) {
        const operation = result.rows[0];
        const seoTitle = operation.seo_title || `${operation.title} - ${operation.apt_group || 'Cyber Mission'} | InfoSecLabs`;
        const seoDescription = operation.seo_description || operation.story_intro?.substring(0, 160) || operation.description?.substring(0, 160) || `Investigate ${operation.title} - a realistic cyber threat simulation.`;
        const canonicalUrl = `https://infoseclabs.io/missions/${slug}`;
        
        // Read the index.html template
        let html = fs.readFileSync(path.join(localPublicPath, 'index.html'), 'utf8');
        
        // Helper function for more robust tag replacement (handles spaces, single/double quotes)
        const replaceTag = (htmlText, tagType, attribute, value, replacement) => {
            const regex = new RegExp(`<${tagType}[^>]*?${attribute}\\s*=\\s*["']${value}["'][^>]*?>`, 'gi');
            return htmlText.replace(regex, replacement);
        };

        // Replace meta tags dynamically
        html = html.replace(/<title>.*?<\/title>/i, `<title>${seoTitle}</title>`);
        html = replaceTag(html, 'meta', 'name', 'description', `<meta name="description" content="${seoDescription.replace(/"/g, '&quot;')}" />`);
        html = replaceTag(html, 'meta', 'property', 'og:title', `<meta property="og:title" content="${seoTitle.replace(/"/g, '&quot;')}" />`);
        html = replaceTag(html, 'meta', 'property', 'og:description', `<meta property="og:description" content="${seoDescription.replace(/"/g, '&quot;')}" />`);
        html = replaceTag(html, 'meta', 'property', 'og:url', `<meta property="og:url" content="${canonicalUrl}" />`);
        
        // Add image support for missions if not present
        const ogImage = 'https://infoseclabs.io/assets/marketing/missions-hero.png';
        html = replaceTag(html, 'meta', 'property', 'og:image', `<meta property="og:image" content="${ogImage}" />`);

        html = replaceTag(html, 'meta', 'property', 'twitter:title', `<meta property="twitter:title" content="${seoTitle.replace(/"/g, '&quot;')}" />`);
        html = replaceTag(html, 'meta', 'property', 'twitter:description', `<meta property="twitter:description" content="${seoDescription.replace(/"/g, '&quot;')}" />`);
        html = replaceTag(html, 'meta', 'property', 'twitter:url', `<meta property="twitter:url" content="${canonicalUrl}" />`);
        html = replaceTag(html, 'meta', 'property', 'twitter:image', `<meta property="twitter:image" content="${ogImage}" />`);
        
        return res.send(html);
      }
    } catch (error) {
      console.error('Error generating dynamic SEO for mission:', error);
    }
    
    // Fallback to standard index.html
    res.sendFile(path.join(localPublicPath, 'index.html'));
  });

  // Dynamic SEO for Blog Posts (for crawlers and social media)
  app.get('/blog/:slug', async (req, res) => {
    try {
      const { slug } = req.params;
      
      // Fetch blog post data from database
      const result = await db.query(
        'SELECT title, excerpt, content, featured_image FROM blog_posts WHERE slug = ? AND status = ?',
        [slug, 'published']
      );
      
      if (result.rows.length > 0) {
        const post = result.rows[0];
        const seoTitle = `${post.title} | InfoSecLabs Blog`;
        const seoDescription = post.excerpt?.substring(0, 160) || post.content?.substring(0, 160).replace(/<[^>]*>/g, '') || `Read ${post.title} on InfoSecLabs Blog.`;
        const canonicalUrl = `https://infoseclabs.io/blog/${slug}`;
        const ogImage = post.featured_image || 'https://infoseclabs.io/wp-content/uploads/2024/12/infoSecLabs_Favicon.png';
        
        // Read the index.html template
        let html = fs.readFileSync(path.join(localPublicPath, 'index.html'), 'utf8');
        
        // Helper function for more robust tag replacement (handles spaces, single/double quotes)
        const replaceTag = (htmlText, tagType, attribute, value, replacement) => {
            const regex = new RegExp(`<${tagType}[^>]*?${attribute}\\s*=\\s*["']${value}["'][^>]*?>`, 'gi');
            return htmlText.replace(regex, replacement);
        };

        // Replace meta tags dynamically
        html = html.replace(/<title>.*?<\/title>/i, `<title>${seoTitle}</title>`);
        html = replaceTag(html, 'meta', 'name', 'description', `<meta name="description" content="${seoDescription.replace(/"/g, '&quot;')}" />`);
        html = replaceTag(html, 'meta', 'property', 'og:type', `<meta property="og:type" content="article" />`);
        html = replaceTag(html, 'meta', 'property', 'og:title', `<meta property="og:title" content="${seoTitle.replace(/"/g, '&quot;')}" />`);
        html = replaceTag(html, 'meta', 'property', 'og:description', `<meta property="og:description" content="${seoDescription.replace(/"/g, '&quot;')}" />`);
        html = replaceTag(html, 'meta', 'property', 'og:url', `<meta property="og:url" content="${canonicalUrl}" />`);
        html = replaceTag(html, 'meta', 'property', 'og:image', `<meta property="og:image" content="${ogImage}" />`);
        html = replaceTag(html, 'meta', 'property', 'twitter:title', `<meta property="twitter:title" content="${seoTitle.replace(/"/g, '&quot;')}" />`);
        html = replaceTag(html, 'meta', 'property', 'twitter:description', `<meta property="twitter:description" content="${seoDescription.replace(/"/g, '&quot;')}" />`);
        html = replaceTag(html, 'meta', 'property', 'twitter:url', `<meta property="twitter:url" content="${canonicalUrl}" />`);
        html = replaceTag(html, 'meta', 'property', 'twitter:image', `<meta property="twitter:image" content="${ogImage}" />`);
        
        return res.send(html);
      }
    } catch (error) {
      console.error('Error generating dynamic SEO for blog:', error);
    }
    
    // Fallback to standard index.html
    res.sendFile(path.join(localPublicPath, 'index.html'));
  });

  // Dynamic SEO for Public Profiles (for crawlers and social media)
  app.get('/user/:username', async (req, res) => {
    try {
      const { username } = req.params;
      
      // Fetch rich user data from database including dynamic rank and fallback stats
      const result = await db.query(`
        SELECT 
          u.username, u.name, u.profile_picture,
          COALESCE(ux.current_level, 1) as level, 
          COALESCE(ux.total_xp, 0) as total_xp,
          (SELECT COUNT(*) + 1 FROM user_xp WHERE total_xp > COALESCE(ux.total_xp, 0)) as \`rank\`
        FROM users u
        LEFT JOIN user_xp ux ON u.id = ux.user_id
        WHERE u.username = ? OR u.name = ?
        LIMIT 1
      `, [username, username]);
      
      if (result.rows.length > 0) {
        const user = result.rows[0];
        const displayName = user.name || user.username;
        const levelText = `Level ${user.level}`;
        const rankText = user.total_xp > 0 ? `Rank #${user.rank}` : 'Unranked Analyst';
        
        const seoTitle = `${displayName} (${levelText}) | InfoSecLabs SOC Analyst Profile`;
        const seoDescription = `🛡️ ${displayName} • ${levelText} Analyst • 🏆 ${rankText} • 📈 ${user.total_xp.toLocaleString()} XP. See my investigation history and badges on InfoSecLabs.`;
        const canonicalUrl = `https://infoseclabs.io/user/${encodeURIComponent(username)}`;
        
        // Image logic: use profile picture with fallback to a branded card
        let ogImage = 'https://infoseclabs.io/assets/marketing/dashboard-hero.png';
        if (user.profile_picture) {
          ogImage = user.profile_picture.startsWith('http') 
            ? user.profile_picture 
            : `https://infoseclabs.io${user.profile_picture}`;
        }
        
        // Read the index.html template from the deployment location
        let indexHtmlPath = path.join(localPublicPath, 'index.html');
        if (!fs.existsSync(indexHtmlPath)) {
            indexHtmlPath = path.join(devDistPath, 'index.html');
        }
        
        let html = fs.readFileSync(indexHtmlPath, 'utf8');
        
        const replaceTag = (htmlText, tagType, attribute, value, replacement) => {
            const regex = new RegExp(`<${tagType}[^>]*?${attribute}\\s*=\\s*["']${value}["'][^>]*?>`, 'gi');
            return htmlText.replace(regex, replacement);
        };

        // Inject dynamic values directly into the HTML before sending to crawler
        html = html.replace(/<title>.*?<\/title>/i, `<title>${seoTitle}</title>`);
        html = replaceTag(html, 'meta', 'name', 'description', `<meta name="description" content="${seoDescription.replace(/"/g, '&quot;')}" />`);
        html = replaceTag(html, 'meta', 'property', 'og:type', `<meta property="og:type" content="profile" />`);
        html = replaceTag(html, 'meta', 'property', 'og:title', `<meta property="og:title" content="${seoTitle.replace(/"/g, '&quot;')}" />`);
        html = replaceTag(html, 'meta', 'property', 'og:description', `<meta property="og:description" content="${seoDescription.replace(/"/g, '&quot;')}" />`);
        html = replaceTag(html, 'meta', 'property', 'og:url', `<meta property="og:url" content="${canonicalUrl}" />`);
        html = replaceTag(html, 'meta', 'property', 'og:image', `<meta property="og:image" content="${ogImage}" />`);
        
        // Twitter Card Support
        html = replaceTag(html, 'meta', 'property', 'twitter:title', `<meta property="twitter:title" content="${seoTitle.replace(/"/g, '&quot;')}" />`);
        html = replaceTag(html, 'meta', 'property', 'twitter:description', `<meta property="twitter:description" content="${seoDescription.replace(/"/g, '&quot;')}" />`);
        html = replaceTag(html, 'meta', 'property', 'twitter:url', `<meta property="twitter:url" content="${canonicalUrl}" />`);
        html = replaceTag(html, 'meta', 'property', 'twitter:image', `<meta property="twitter:image" content="${ogImage}" />`);
        
        res.setHeader('X-SEO-Generated', 'true');
        return res.send(html);
      }
    } catch (error) {
      console.error('Error in Dynamic SEO Social Preview Generation:', error);
    }
    
    // Fallback to standard index.html if error or user not found
    res.sendFile(path.join(localPublicPath, 'index.html'));
  });

  // Dynamic SEO for Learning Paths (for crawlers and social media)
  app.get('/academy/path/:id', async (req, res) => {
    try {
      const { id } = req.params;
      
      const result = await db.query(
        'SELECT title, description FROM learning_paths WHERE id = ?',
        [id]
      );
      
      if (result.rows.length > 0) {
        const pathData = result.rows[0];
        const seoTitle = `${pathData.title} | InfoSecLabs Academy`;
        const seoDescription = pathData.description?.substring(0, 160) || `Master ${pathData.title} with hands-on labs on InfoSecLabs.`;
        const canonicalUrl = `https://infoseclabs.io/academy/path/${id}`;
        
        // Read the index.html template
        let html = fs.readFileSync(path.join(localPublicPath, 'index.html'), 'utf8');
        
        // Helper function for more robust tag replacement (handles spaces, single/double quotes)
        const replaceTag = (htmlText, tagType, attribute, value, replacement) => {
            const regex = new RegExp(`<${tagType}[^>]*?${attribute}\\s*=\\s*["']${value}["'][^>]*?>`, 'gi');
            return htmlText.replace(regex, replacement);
        };

        // Replace meta tags dynamically
        html = html.replace(/<title>.*?<\/title>/i, `<title>${seoTitle}</title>`);
        html = replaceTag(html, 'meta', 'name', 'description', `<meta name="description" content="${seoDescription.replace(/"/g, '&quot;')}" />`);
        html = replaceTag(html, 'meta', 'property', 'og:title', `<meta property="og:title" content="${seoTitle.replace(/"/g, '&quot;')}" />`);
        html = replaceTag(html, 'meta', 'property', 'og:description', `<meta property="og:description" content="${seoDescription.replace(/"/g, '&quot;')}" />`);
        html = replaceTag(html, 'meta', 'property', 'og:url', `<meta property="og:url" content="${canonicalUrl}" />`);
        html = replaceTag(html, 'meta', 'property', 'twitter:title', `<meta property="twitter:title" content="${seoTitle.replace(/"/g, '&quot;')}" />`);
        html = replaceTag(html, 'meta', 'property', 'twitter:description', `<meta property="twitter:description" content="${seoDescription.replace(/"/g, '&quot;')}" />`);
        html = replaceTag(html, 'meta', 'property', 'twitter:url', `<meta property="twitter:url" content="${canonicalUrl}" />`);
        
        return res.send(html);
      }
    } catch (error) {
      console.error('Error generating dynamic SEO for path:', error);
    }
    
    // Fallback to standard index.html
    res.sendFile(path.join(localPublicPath, 'index.html'));
  });

  // 2. Static Asset Handlers
  app.use(express.static(localPublicPath, {
    setHeaders: (res, filePath) => {
      if (filePath.endsWith('index.html')) {
        // Never cache index.html to avoid version mismatches on deployment
        res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
        res.setHeader('Pragma', 'no-cache');
        res.setHeader('Expires', '0');
      } else {
         // Cache other assets (JS/CSS images) aggressively as they have hashes
         res.setHeader('Cache-Control', 'public, max-age=31536000');
      }
    }
  }));

  // SPA Catch-all: direct all non-API requests to index.html
  // Only apply if request didn't match an API route or SEO route (handled above)
  app.get('*', (req, res) => {
    res.sendFile(path.join(localPublicPath, 'index.html'));
  });
} else if (process.env.NODE_ENV !== 'production') {
  // Fallback for local development
  console.log('Serving static files from dev dist directory:', devDistPath);
  app.use(express.static(devDistPath));
  app.get('*', (req, res) => {
    res.sendFile(path.join(devDistPath, 'index.html'));
  });
}

console.log('--- CHECKPOINT 7.9: Setup Static Files Done ---');

const server = http.createServer(app);
const io = new Server(server, {
  cors: {
    origin: function (origin, callback) {
      const allowedOrigins = [process.env.CLIENT_ORIGIN || 'http://localhost:5173', 'http://localhost:5174', 'http://localhost:5175', 'https://infoseclabs.io', 'https://platform.infoseclabs.io'];
      
      // Allow requests with no origin (like mobile apps or curl requests)
      if (!origin) return callback(null, true);
      
      // Explicitly allow localhost for development
      if (origin.includes('localhost') || origin.includes('127.0.0.1')) {
          return callback(null, true);
      }

      if (allowedOrigins.indexOf(origin) !== -1) {
        return callback(null, true);
      }
      
      // Default to allowing for now to prevent breakage
      return callback(null, true);
    },
    methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
    credentials: true
  }
});
global.io = io;

// --- Live User Tracking ---
global.connectedUsers = new Map(); // socketId -> { userId, name, email, role, type: 'registered'|'guest' }

io.on('connection', (socket) => {
  // Detect platform from User-Agent for initial guest tracking
  const ua = (socket.handshake.headers['user-agent'] || '').toLowerCase();
  let detectedPlatform = 'web';
  if (ua.includes('android')) detectedPlatform = 'android';
  else if (ua.includes('iphone') || ua.includes('ipad') || ua.includes('ipod')) detectedPlatform = 'ios';

  // Initially track as guest
  global.connectedUsers.set(socket.id, { type: 'guest', platform: detectedPlatform, connectedAt: new Date() });

  // Client authenticates by sending JWT + optional platform identifier
  socket.on('authenticate', (token, platform) => {
    try {
      const decoded = jwt.verify(token, process.env.JWT_SECRET);
      global.connectedUsers.set(socket.id, {
        type: 'registered',
        userId: decoded.id,
        name: decoded.name || null,
        email: decoded.email || null,
        role: decoded.role || 'normal',
        platform: platform || detectedPlatform, // android | ios | web
        connectedAt: new Date()
      });
    } catch (err) {
      // Invalid token — keep as guest
    }
  });

  socket.on('disconnect', () => {
    global.connectedUsers.delete(socket.id);
  });

  // Attach Cyber Duel socket handlers
  require('./sockets/duelSocket')(io, socket);
});





// Start background email jobs
require('./cron/emailJobs').startEmailJobs();

const PORT = process.env.PORT || 5001;
server.listen(PORT, () => {
  console.log(`Server listening on port ${PORT}`);
});

module.exports = { app, server, io };
// Force restart Tue Dec 30 20:55:59 EST 2025
