// server.js

require('dotenv').config();
const express = require('express');
const path = require('path');
const cors = require('cors');
const cron = require('node-cron');
const authRoutes = require('./routes/auth');
const blogRoutes = require('./routes/blog');
const projectRoutes = require('./routes/project');
const uploadRoutes = require('./routes/upload');

const initializeDatabase = require('./config/init-db');
const pool = require('./config/db'); // Ensure this path is correct
const fs = require('fs-extra'); // Required for file operations in scheduled tasks
const { Server } = require('socket.io');
const { createServer } = require("http");
const GetRamUsage = require('./routes/ServerInfos');
const AIService = require('./services/AIService'); // Import AIService

const corsOption = {
  origin: 'http://localhost:3000', // Update to 'https://ovidgame.com' in production
  methods: ['GET', 'POST', 'PATCH', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization']
};
const app = express();
// Middleware

app.use(express.json());

// Configure CORS
app.use(cors(corsOption));

// Initialize database
initializeDatabase();

// Custom middleware to check referer
const checkReferer = (req, res, next) => {
  const referer = req.headers.referer;

  if (req.path === '/index.html' || req.path === '/' || req.path.startsWith('/api/') || req.path.startsWith("/socket.io/")) {
    return next();
  }

  if (referer && (
    referer.includes('/index.html') ||
    referer.endsWith('/') ||
    req.headers['sec-fetch-dest'] === 'script' ||
    req.headers['sec-fetch-dest'] === 'style' ||
    req.headers['sec-fetch-mode'] === 'cors'
  )) {
    return next();
  }

  console.warn(`Blocked request to ${req.path} from referer ${referer}`);
  res.status(403).json({ message: 'Direct access not allowed' });
};
// Serve index.html and its assets with security
app.use('/', express.static(path.join(__dirname, 'html'), {
  index: 'index.html',

  setHeaders: (res, filePath) => {
    // Set security headers
    res.setHeader('X-Content-Type-Options', 'nosniff');
    res.setHeader('X-Frame-Options', 'SAMEORIGIN');
    if (filePath.endsWith('.html')) {
      res.setHeader('Cache-Control', 'no-cache');
    }
  }
}));

// Protected static directories with referer checking
app.use('/blogs', checkReferer, express.static(path.join(__dirname, 'html', 'ovid-blogs')));
app.use('/games', checkReferer, express.static(path.join(__dirname, 'html', 'web-games')));
app.use('/projects', checkReferer, express.static(path.join(__dirname, 'html', 'web-projects')));
app.use('/assets', checkReferer, express.static(path.join(__dirname, 'html', 'assets')));

// API Routes
app.use('/api/auth', authRoutes);
app.use('/api/blogs', blogRoutes);
app.use('/api/projects', projectRoutes);
app.use("/api/upload",uploadRoutes)

// Basic route for testing
app.get('/api/status', (req, res) => {
  res.json({ message: 'API is running' });
});

// Error handling middleware
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({ message: 'Internal server error' });
});

// Scheduled task: runs every hour at minute 0
cron.schedule('0 * * * *', async () => {
  console.log('Running scheduled project cleanup task.');

  let connection;
  try {
    connection = await pool.getConnection();

    const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);

    const [projects] = await connection.execute(
      `SELECT * FROM projects WHERE created_at <= ?`,
      [oneDayAgo]
    );

    for (const project of projects) {
      const { id, dislikes, likes, path: projectPath } = project;
      const totalVotes = likes + dislikes;
      const dislikeRatio = totalVotes > 0 ? (dislikes / totalVotes) : 0;

      if (dislikeRatio > 0.7) {
        try {
          // Delete associated votes
          await connection.execute(
            'DELETE FROM votes WHERE project_id = ?',
            [id]
          );

          // Delete the file
          await fs.remove(projectPath);

          // Delete the project from the database
          await connection.execute(
            'DELETE FROM projects WHERE id = ?',
            [id]
          );

          console.log(`Deleted project ID ${id} due to high dislike ratio.`);
        } catch (err) {
          console.error(`Failed to delete project ID ${id}:`, err.message);
        }
      }
    }
  } catch (error) {
    console.error('Error during scheduled cleanup:', error.message);
  } finally {
    if (connection) connection.release();
  }
});

// Initialize HTTP server and Socket.IO
const httpServer = createServer(app);
const io = new Server(httpServer, {
  cors: corsOption,
});

// Instantiate AIService
const aiService = new AIService(2048); // Set max RAM usage to 1024 MB

// Handle Socket.IO connections
io.on('connection', (socket) => {
  console.log('A user connected:', socket.id);

  // Handle collision collider generation
  socket.on('generateColliders', async (data, callback) => {
    const { imagePath, tsxPath, sensitivity, outputDir } = data;

    try {
      const result = await aiService.processColliders(imagePath, tsxPath, sensitivity, outputDir);
      callback({ success: true, output: result });
    } catch (error) {
      callback({ success: false, error: error.toString() });
    }
  });

  // Handle file conversion
  socket.on('convertFiles', async (data, callback) => {
    const { tsxFiles, outputPath, format } = data;

    try {
      const result = await aiService.convertFiles(tsxFiles, outputPath, format);
      callback({ success: true, output: result });
    } catch (error) {
      callback({ success: false, error: error.toString() });
    }
  });

  // Handle disconnection
  socket.on('disconnect', () => {
    console.log('User disconnected:', socket.id);
  });
});

// Start the server
const PORT = process.env.PORT || 2053;
httpServer.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);

  // RAM usage is sent every second
  setInterval(() => {
    const ramUsage = GetRamUsage(); // Ensure this returns RAM usage in MB
    io.emit("takeRamUsage", ramUsage);
  }, 1000);
});

_____


// services/AIService.js

const { spawn } = require('child_process');
const path = require('path');

class AIService {
  constructor(maxRamUsage = 512) { // maxRamUsage in MB
    this.maxRamUsage = maxRamUsage;
    this.currentRamUsage = 0;
    this.taskQueue = [];
    this.isProcessing = false;
  }

  // Method to check RAM usage before starting a new task
  async canRunTask(taskRamRequirement) {
    const ramUsage = await this.getRamUsage();
    return (ramUsage + taskRamRequirement) <= this.maxRamUsage;
  }

  // Fetch current RAM usage
  getRamUsage() {
    return new Promise((resolve, reject) => {
      const freeMem = os.freemem();
      const totalMem = os.totalmem();
      const usedMem = (totalMem - freeMem) / (1024 * 1024); // in MB
      resolve(usedMem);
    });
  }

  // Enqueue a task
  enqueueTask(task) {
    this.taskQueue.push(task);
    this.processQueue();
  }

  // Process the task queue
  async processQueue() {
    if (this.isProcessing || this.taskQueue.length === 0) return;

    const task = this.taskQueue.shift();
    this.isProcessing = true;

    const canRun = await this.canRunTask(task.ramRequirement);
    if (!canRun) {
      console.warn('RAM usage too high. Task is requeued.');
      this.taskQueue.unshift(task); // Requeue the task
      this.isProcessing = false;
      return;
    }

    try {
      const output = await this.runPythonScript(task.scriptPath, task.args);
      task.resolve(output);
    } catch (error) {
      task.reject(error);
    } finally {
      this.isProcessing = false;
      this.processQueue();
    }
  }

  // General method to run a Python script
  runPythonScript(scriptPath, args = []) {
    return new Promise((resolve, reject) => {
      const pythonProcess = spawn('python', [scriptPath, ...args]);

      let output = '';
      let errorOutput = '';

      pythonProcess.stdout.on('data', (data) => {
        output += data.toString();
      });

      pythonProcess.stderr.on('data', (data) => {
        errorOutput += data.toString();
      });

      pythonProcess.on('close', (code) => {
        if (code === 0) {
          resolve(output);
        } else {
          reject(`Python script exited with code ${code}: ${errorOutput}`);
        }
      });
    });
  }

  // Specific method to process colliders
  processColliders(imagePath, tsxPath, sensitivity, outputDir) {
    const scriptPath = path.join(__dirname, '../scripts/collision_detector.py');
    const args = [imagePath, tsxPath, sensitivity, '--output', outputDir];
    const ramRequirement = 100; // Estimated RAM usage in MB

    return new Promise((resolve, reject) => {
      this.enqueueTask({ scriptPath, args, ramRequirement, resolve, reject });
    });
  }

  // Specific method to convert files
  convertFiles(tsxFilesArray, outputPath, format = 'json') {
    const scriptPath = path.join(__dirname, "../",'../scripts/convertor.py');
    const args = [tsxFilesArray.join(','), outputPath, '--format', format];
    const ramRequirement = 150; // Estimated RAM usage in MB

    return new Promise((resolve, reject) => {
      this.enqueueTask({ scriptPath, args, ramRequirement, resolve, reject });
    });
  }
}

module.exports = AIService;


_________


// routes/upload.js

const express = require('express');
const multer = require('multer');
const path = require('path');
const router = express.Router();

// Set up storage engine
const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, path.join(__dirname, '../uploads',"maps")); // Ensure this directory exists
  },
  filename: function (req, file, cb) {
    cb(null, `${Date.now()}-${file.originalname}`);
  }
});

// Initialize upload
const upload = multer({ storage: storage });

// Upload endpoint
router.post('/', upload.single('file'), (req, res) => {
  if (!req.file) {
    return res.status(400).json({ message: 'No file uploaded.' });
  }

  // You can add more validations here if needed

  res.status(200).json({ message: 'File uploaded successfully.', filename: req.file.filename });
});

module.exports = router;

hata nedir