/* =========================================================
   ZA-FARM-KH — Express Server + Telegram Bot
   🛡️ Security Hardened v2
   ========================================================= */

require("dotenv").config();
const express = require("express");
const cors = require("cors");
const path = require("path");
const helmet = require("helmet");
const rateLimit = require("express-rate-limit");
const crypto = require("crypto");

const authRoutes = require("./routes/auth");
const orderRoutes = require("./routes/orders");
const serviceRoutes = require("./routes/services");
const paymentRoutes = require("./routes/payment");
const licenseRoutes = require("./routes/license");
const adminRoutes = require("./routes/admin");
const apiTopupRoutes = require("./routes/apitopup");
const otpRoutes = require("./routes/otp");
const bot = require("./telegram");
const { notifySecurityAlert } = require("./telegram");

const app = express();
const PORT = process.env.PORT || 3000;

// 🛡️ Disable X-Powered-By header (hide Express fingerprint)
app.disable("x-powered-by");

// 🛡️ Trust proxy (for rate limiting behind reverse proxy / cPanel)
app.set("trust proxy", 1);

// ═══════════════════════════════════════════════════════════
// 🛡️ IP BLACKLIST — ការពារ IP ដែលវាយប្រហារ
// ═══════════════════════════════════════════════════════════
const ipBlacklist = new Map(); // IP -> { count, blockedUntil }
const IP_MAX_VIOLATIONS = 10; // ចំនួន violations មុន block
const IP_BLOCK_DURATION = 30 * 60 * 1000; // Block 30 នាទី

function checkIpBlacklist(req, res, next) {
  const ip = req.ip || req.connection.remoteAddress;
  const record = ipBlacklist.get(ip);
  
  if (record && record.blockedUntil) {
    if (Date.now() < record.blockedUntil) {
      return res.status(403).json({ message: "IP blocked — ព្យាយាមច្រើនពេក" });
    } else {
      // Unblock after duration
      ipBlacklist.delete(ip);
    }
  }
  next();
}

function recordViolation(ip) {
  const record = ipBlacklist.get(ip) || { count: 0, blockedUntil: null };
  record.count++;
  if (record.count >= IP_MAX_VIOLATIONS) {
    record.blockedUntil = Date.now() + IP_BLOCK_DURATION;
    console.log(`🚫 [Security] IP blocked: ${ip} (${record.count} violations)`);
    // 🛡️ Alert admin via Telegram
    notifySecurityAlert("IP Blocked", `IP: ${ip} — ${record.count} violations`);
  }
  ipBlacklist.set(ip, record);
}

// Apply IP blacklist check first
app.use(checkIpBlacklist);

// ═══════════════════════════════════════════════════════════
// 🛡️ REQUEST LOGGING — កត់ត្រា suspicious activity
// ═══════════════════════════════════════════════════════════
const suspiciousLog = [];

function logSuspicious(req, reason) {
  const entry = {
    time: new Date().toISOString(),
    ip: req.ip || req.connection.remoteAddress,
    method: req.method,
    path: req.path,
    reason,
    userAgent: (req.headers["user-agent"] || "").substring(0, 100),
  };
  suspiciousLog.push(entry);
  // Keep only last 500 entries
  if (suspiciousLog.length > 500) suspiciousLog.shift();
  console.log(`⚠️  [Security] ${reason} — IP: ${entry.ip} — ${req.method} ${req.path}`);
}

// 🛡️ Security — Helmet with strict CSP
app.use((req, res, next) => {
  res.locals.cspNonce = crypto.randomBytes(16).toString("base64");
  next();
});

app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", "'unsafe-inline'", "https://accounts.google.com"],
      styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
      fontSrc: ["'self'", "https://fonts.gstatic.com"],
      imgSrc: ["'self'", "data:", "https://api.qrserver.com", "https://*.googleapis.com"],
      connectSrc: ["'self'", "https://accounts.google.com"],
      frameSrc: ["'none'"],
      objectSrc: ["'none'"],
      baseUri: ["'self'"],
      formAction: ["'self'", "https://accounts.google.com"],
      upgradeInsecureRequests: [],
    },
  },
  crossOriginEmbedderPolicy: false,
  crossOriginOpenerPolicy: { policy: "same-origin" },
  crossOriginResourcePolicy: { policy: "same-origin" },
  referrerPolicy: { policy: "strict-origin-when-cross-origin" },
  hsts: { maxAge: 63072000, includeSubDomains: true, preload: true },
}));

// ═══════════════════════════════════════════════════════════
// 🛡️ RATE LIMITING — ការពារ brute force / DDoS
// ═══════════════════════════════════════════════════════════
const apiLimiter = rateLimit({
  windowMs: 1 * 60 * 1000,
  max: 60,
  standardHeaders: true,
  legacyHeaders: false,
  message: { message: "សូមរង់ចាំ — request ច្រើនពេក" },
  handler: (req, res, next, options) => {
    recordViolation(req.ip);
    res.status(429).json(options.message);
  },
});
const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 7, // ⬇️ Reduced from 10 to 7
  standardHeaders: true,
  legacyHeaders: false,
  message: { message: "ព្យាយាមច្រើនពេក — សូមរង់ចាំ 15 នាទី" },
  handler: (req, res, next, options) => {
    recordViolation(req.ip);
    logSuspicious(req, "Auth rate limit exceeded");
    res.status(429).json(options.message);
  },
});
const paymentLimiter = rateLimit({
  windowMs: 1 * 60 * 1000,
  max: 5,
  standardHeaders: true,
  legacyHeaders: false,
  message: { message: "សូមរង់ចាំ — បង្កើត QR ច្រើនពេក" },
});
const forgotLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 3,
  standardHeaders: true,
  legacyHeaders: false,
  message: { message: "ព្យាយាមច្រើនពេក — សូមរង់ចាំ 15 នាទី" },
  handler: (req, res, next, options) => {
    recordViolation(req.ip);
    res.status(429).json(options.message);
  },
});
// 🛡️ Strict order rate limit — ការពារ spam orders
const orderLimiter = rateLimit({
  windowMs: 1 * 60 * 1000,
  max: 10, // Max 10 orders per minute
  standardHeaders: true,
  legacyHeaders: false,
  message: { message: "ទិញច្រើនពេក — សូមរង់ចាំ 1 នាទី" },
});

// 🛡️ CORS — strict origin control
app.use(cors({
  origin: process.env.ALLOWED_ORIGINS
    ? process.env.ALLOWED_ORIGINS.split(",").map(s => s.trim())
    : true, // Allow all in dev, set ALLOWED_ORIGINS in production!
  credentials: true,
  methods: ["GET", "POST"],
  allowedHeaders: ["Content-Type", "Authorization", "X-API-Key"],
  maxAge: 86400,
}));

// 🛡️ Body parser with strict size limit
app.use(express.json({ limit: "100kb" }));

// ═══════════════════════════════════════════════════════════
// 🛡️ ANTI-ATTACK MIDDLEWARE
// ═══════════════════════════════════════════════════════════

// Block suspicious request patterns (path traversal, SQL injection, XSS)
app.use((req, res, next) => {
  const suspicious = /(\.\.|%2e%2e|%00|<script|javascript:|data:text\/html|union\s+select|drop\s+table|insert\s+into|delete\s+from|update\s+.*set|exec\s*\(|eval\s*\()/i;
  if (suspicious.test(req.url) || suspicious.test(req.path) || suspicious.test(JSON.stringify(req.body || {}))) {
    logSuspicious(req, "Suspicious pattern detected");
    recordViolation(req.ip);
    return res.status(403).json({ message: "Forbidden" });
  }
  next();
});

// 🛡️ Block bad User-Agents (common attack tools)
app.use((req, res, next) => {
  const ua = (req.headers["user-agent"] || "").toLowerCase();
  const badBots = /sqlmap|nikto|nmap|masscan|zgrab|dirbuster|gobuster|wpscan|hydra|burpsuite|metasploit/i;
  if (badBots.test(ua)) {
    logSuspicious(req, `Bad bot: ${ua.substring(0, 50)}`);
    recordViolation(req.ip);
    return res.status(403).end();
  }
  next();
});

// 🛡️ Block requests with too many headers (header injection)
app.use((req, res, next) => {
  const headerCount = Object.keys(req.headers).length;
  if (headerCount > 50) {
    logSuspicious(req, `Too many headers: ${headerCount}`);
    return res.status(400).end();
  }
  next();
});

// 🛡️ Security headers
app.use((req, res, next) => {
  res.setHeader("X-Content-Type-Options", "nosniff");
  res.setHeader("X-Frame-Options", "DENY");
  res.setHeader("X-XSS-Protection", "0");
  res.setHeader("Permissions-Policy", "camera=(), microphone=(), geolocation=(), interest-cohort=()");
  res.setHeader("X-Download-Options", "noopen");
  res.setHeader("X-Permitted-Cross-Domain-Policies", "none");
  if (req.path.startsWith("/zapi/")) {
    res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate, private");
    res.setHeader("Pragma", "no-cache");
  }
  next();
});

// 🛡️ Block access to sensitive files
app.use((req, res, next) => {
  const blocked = /\.(env|git|htaccess|htpasswd|log|sql|bak|swp|DS_Store|php|json)$/i;
  const blockedPaths = /^\/(server|node_modules|\.git|\.env|config\.php|package\.json|package-lock\.json|\.well-known\/)/i;
  if (blocked.test(req.path) || blockedPaths.test(req.path)) {
    // Allow .json only for API responses
    if (req.path.endsWith(".json") && req.path.startsWith("/zapi/")) {
      return next();
    }
    logSuspicious(req, "Blocked file access attempt");
    return res.status(403).json({ message: "Forbidden" });
  }
  next();
});

// 🌐 GLOBAL UTF-8 ENCODING GUARANTEE
// Forces charset=utf-8 on all text/HTML/JS/JSON/CSS headers to eliminate Mojibake
app.use((req, res, next) => {
  const origSetHeader = res.setHeader;
  res.setHeader = function (name, value) {
    if (name && name.toLowerCase() === "content-type" && typeof value === "string") {
      if (!value.toLowerCase().includes("charset")) {
        if (value.includes("text/") || value.includes("application/json") || value.includes("application/javascript")) {
          value = value + "; charset=utf-8";
        }
      }
    }
    return origSetHeader.call(this, name, value);
  };
  next();
});

// 🔗 Redirect .html URLs to clean URLs (SEO & Security)
app.use((req, res, next) => {
  if (req.path.endsWith('.html')) {
    const cleanPath = req.path.slice(0, -5);
    // Keep query strings if any
    const queryString = Object.keys(req.query).length ? '?' + new URLSearchParams(req.query).toString() : '';
    return res.redirect(301, cleanPath === '' ? '/' : cleanPath + queryString);
  }
  next();
});

// Serve static frontend
app.use(express.static(path.join(__dirname, "..", "public"), {
  dotfiles: "deny",
  index: "index.html",
  maxAge: "1h",
  etag: true,
  setHeaders: (res, filepath) => {
    if (filepath.endsWith(".html")) {
      res.setHeader("Content-Type", "text/html; charset=utf-8");
    } else if (filepath.endsWith(".js")) {
      res.setHeader("Content-Type", "application/javascript; charset=utf-8");
    } else if (filepath.endsWith(".css")) {
      res.setHeader("Content-Type", "text/css; charset=utf-8");
    }
  }
}));

// ═══════════════════════════════════════════════════════════
// 🛡️ API ROUTES
// ── NOTE: prefix `/zapi/` is used (instead of `/api/`)
//    to avoid conflict with the existing `api/` folder on
//    the cPanel hosting (which contains other PHP/static files).
// ═══════════════════════════════════════════════════════════
app.use("/zapi/auth/login", authLimiter);
app.use("/zapi/auth/register", authLimiter);
app.use("/zapi/auth/forgot", forgotLimiter);
app.use("/zapi/auth/reset", forgotLimiter);
app.use("/zapi/auth", authRoutes);
app.use("/zapi/orders", orderLimiter, apiLimiter, orderRoutes);
app.use("/zapi/services", apiLimiter, serviceRoutes);
app.use("/zapi/payment/khqr", paymentLimiter);
app.use("/zapi/payment", apiLimiter, paymentRoutes);
app.use("/zapi/license", apiLimiter, licenseRoutes);
app.use("/zapi/admin", adminRoutes);
app.use("/zapi/apitopup", apiLimiter, apiTopupRoutes);
app.use("/zapi/otp", apiLimiter, otpRoutes);

// Track online visitors (in-memory, unique IPs in the last 5 minutes)
const activeUsers = new Map();

app.use((req, res, next) => {
  const ip = req.ip || req.connection.remoteAddress;
  activeUsers.set(ip, Date.now());
  next();
});

// Clean up inactive users every 1 minute
setInterval(() => {
  const now = Date.now();
  for (const [ip, lastActive] of activeUsers.entries()) {
    if (now - lastActive > 5 * 60 * 1000) {
      activeUsers.delete(ip);
    }
  }
}, 60 * 1000);

app.get("/zapi/online-count", (req, res) => {
  const count = Math.max(1, activeUsers.size);
  res.json({ count });
});

// Health check
app.get("/zapi/health", (req, res) => {
  res.json({ status: "ok" });
});

// 🛡️ Admin security log endpoint (protected by admin token)
app.get("/zapi/admin/security-log", (req, res) => {
  const adminToken = req.headers["x-admin-token"];
  if (!adminToken || adminToken !== process.env.ADMIN_SECRET) {
    return res.status(403).json({ message: "Forbidden" });
  }
  res.json({
    blacklistedIPs: Array.from(ipBlacklist.entries()).map(([ip, data]) => ({ ip, ...data })),
    recentSuspicious: suspiciousLog.slice(-50),
    totalViolations: suspiciousLog.length,
  });
});

// 🛡️ Block common attack paths
app.use("/wp-admin", (req, res) => { logSuspicious(req, "WP attack"); recordViolation(req.ip); res.status(404).end(); });
app.use("/wp-login", (req, res) => { logSuspicious(req, "WP attack"); recordViolation(req.ip); res.status(404).end(); });
app.use("/wp-content", (req, res) => { recordViolation(req.ip); res.status(404).end(); });
app.use("/wp-includes", (req, res) => { recordViolation(req.ip); res.status(404).end(); });
app.use("/phpmyadmin", (req, res) => { logSuspicious(req, "phpMyAdmin probe"); recordViolation(req.ip); res.status(404).end(); });
app.use("/phpinfo", (req, res) => { recordViolation(req.ip); res.status(404).end(); });
app.use("/.env", (req, res) => { logSuspicious(req, ".env probe"); recordViolation(req.ip); res.status(404).end(); });
app.use("/.git", (req, res) => { recordViolation(req.ip); res.status(404).end(); });
app.use("/xmlrpc", (req, res) => { recordViolation(req.ip); res.status(404).end(); });
app.use("/actuator", (req, res) => { recordViolation(req.ip); res.status(404).end(); });

// ═══════════════════════════════════════════════════════════
// 🔗 CLEAN URLs — serve .html files without extension
//    e.g. /login → public/login.html
// ═══════════════════════════════════════════════════════════
const fs = require("fs");

app.get("*", (req, res, next) => {
  // Bypass Express SPA fallback for external PHP api/API folders to prevent conflicts
  if (req.path.startsWith("/api/") || req.path.startsWith("/API/") || req.path === "/api" || req.path === "/API") {
    return next();
  }

  // If path has a file extension, it's a missing static file
  if (req.path.includes(".")) {
    return res.status(404).json({ message: "Not found" });
  }

  // Try to serve a matching .html file (e.g. /login → login.html)
  const cleanPath = req.path.endsWith("/") ? req.path.slice(0, -1) : req.path;
  if (cleanPath && cleanPath !== "/") {
    const htmlFile = path.join(__dirname, "..", "public", cleanPath + ".html");
    if (fs.existsSync(htmlFile)) {
      res.setHeader("Content-Type", "text/html; charset=utf-8");
      return res.sendFile(htmlFile);
    }
  }

  // SPA fallback — serve index.html
  res.setHeader("Content-Type", "text/html; charset=utf-8");
  res.sendFile(path.join(__dirname, "..", "public", "index.html"));
});

// 🛡️ Global error handler
app.use((err, req, res, next) => {
  console.error("[Server Error]", err.message);
  res.status(500).json({ message: "Internal server error" });
});

// Export app for Phusion Passenger (cPanel)
module.exports = app;

// Only listen if not running under Passenger
if (!process.env.PASSENGER_BASE_URI && !process.env.PASSENGER_APP_ENV) {
  app.listen(PORT, () => {
    console.log(`\n🟢 ZA-FARM-KH server running at http://localhost:${PORT}`);
    console.log(`🛡️  Security: IP blacklist, rate limiting, attack detection ACTIVE`);
    console.log(`📦 Telegram bot: ${bot.isActive ? "Active ✅" : "Inactive (set TELEGRAM_BOT_TOKEN in .env)"}\n`);
  });
}

// 🛡️ Clean up old blacklist entries every 30 minutes
setInterval(() => {
  const now = Date.now();
  for (const [ip, data] of ipBlacklist.entries()) {
    if (data.blockedUntil && now > data.blockedUntil + 60000) {
      ipBlacklist.delete(ip);
    }
  }
}, 30 * 60 * 1000);
