'use strict';

require('dotenv').config();

const fs = require('fs');
const path = require('path');
const http = require('http');
const EventEmitter = require('events');
const express = require('express');
const helmet = require('helmet');

const { loadConfig, validateSecrets, DEFAULT_CONFIG } = require('./config');
const { State } = require('./state');
const { StructuredLogger } = require('./util/log');
const { ProxyRegistry } = require('./proxy/registry');
const { ProxyLease } = require('./proxy/lease');
const { buildIO, buildBroadcaster } = require('./io');
const { verifyToken, parseCookies } = require('./auth');
const { validateStart, validateProxyUpload } = require('./validate');
const { buildAuthRoutes } = require('./routes/auth');
const { buildTestRoutes } = require('./routes/test');
const { buildProxyRoutes } = require('./routes/proxy');
const { buildBotRoutes } = require('./routes/bots');
const { buildStatsRoutes } = require('./routes/stats');
const { buildHealthRoutes } = require('./routes/health');

function createApp() {
  const secretErrors = validateSecrets();
  if (secretErrors.length > 0) {
    for (const e of secretErrors) console.error(`[FATAL] ${e}`);
    if (!process.env.SITE_PASSWORD) {
      process.exit(1);
    }
  }

  const cfg = loadConfig(path.join(__dirname, '..', 'config.yaml'));
  const SITE_PASSWORD = process.env.SITE_PASSWORD;
  const SESSION_SECRET = process.env.SESSION_SECRET || 'default_secret';

  const state = new State();
  const logger = new StructuredLogger({
    maxEntries: cfg.logging.max_log_entries,
    dailyDir: cfg.logging.daily_log_dir,
  });

  const eventBus = new EventEmitter();
  eventBus.setMaxListeners(100);

  const proxyRegistry = new ProxyRegistry({
    maxProxies: cfg.proxy.max_list_size,
    validationTimeoutMs: cfg.proxy.test_timeout_ms,
    validationConcurrency: cfg.proxy.concurrency,
    probeHost: cfg.proxy.probe_host,
    probePort: cfg.proxy.probe_port,
    scoreDecayMs: cfg.proxy.score_decay_ms,
    scorePenaltyPerFail: cfg.proxy.score_penalty_per_fail,
    scoreRecoveryAfterMs: cfg.proxy.score_recovery_after_ms,
    aliveLatencyMaxMs: cfg.proxy.alive_latency_max_ms,
    perProxyInflightCap: cfg.proxy.per_proxy_inflight_cap,
    defaultStrategy: cfg.proxy.default_strategy,
    persistPath: path.join(__dirname, '..', 'proxies.json'),
    txtPath: path.join(__dirname, '..', 'proxies.txt'),
    log: (level, msg) => logger[level] ? logger[level](msg) : logger.info(msg),
  });

  proxyRegistry.load();

  const app = express();
  const server = http.createServer(app);
  const io = buildIO({ server, SESSION_SECRET });

  const broadcaster = buildBroadcaster(io, state, proxyRegistry);

  const requireAuth = (req, res, next) => {
    const cookies = parseCookies(req.headers.cookie);
    if (verifyToken(cookies.auth_token, SESSION_SECRET)) return next();
    if (req.path.startsWith('/api/') && !req.path.startsWith('/api/auth/')) {
      return res.status(401).json({ error: 'Unauthorized' });
    }
    if (req.path === '/' || (!req.path.startsWith('/api/') && !req.path.startsWith('/socket.io/') && !req.path.endsWith('.html'))) {
      return res.redirect('/login.html');
    }
    next();
  };

  app.use((req, res, next) => {
    const start = Date.now();
    res.on('finish', () => {
      if (req.path.startsWith('/api/')) {
        logger.info(`${req.method} ${req.path} → ${res.statusCode} (${Date.now() - start}ms)`);
      }
    });
    next();
  });

  app.use(express.json({ limit: '2mb' }));

  const corsOrigins = cfg.cors?.origins || [];
  app.use((req, res, next) => {
    const origin = req.headers.origin;
    if (corsOrigins.length === 0) {
      res.setHeader('Access-Control-Allow-Origin', 'same-origin');
    } else if (origin && corsOrigins.includes(origin)) {
      res.setHeader('Access-Control-Allow-Origin', origin);
      res.setHeader('Access-Control-Allow-Credentials', 'true');
    }
    res.setHeader('X-Content-Type-Options', 'nosniff');
    res.setHeader('X-Frame-Options', 'DENY');
    res.setHeader('Referrer-Policy', 'no-referrer');
    next();
  });

  app.use(helmet({
    contentSecurityPolicy: {
      directives: {
        defaultSrc: ["'self'"],
        scriptSrc: ["'self'", "'unsafe-inline'", 'cdn.socket.io'],
        styleSrc: ["'self'", "'unsafe-inline'"],
        connectSrc: ["'self'", 'ws:', 'wss:'],
        imgSrc: ["'self'", 'data:'],
      },
    },
    crossOriginEmbedderPolicy: false,
  }));

  app.use('/api/auth', buildAuthRoutes({ SITE_PASSWORD, SESSION_SECRET, sessionHours: cfg.auth.session_hours, requireAuth, rateLimits: cfg.rate_limits }));

  app.use('/api/test', requireAuth, buildTestRoutes({
    cfg, state, proxyRegistry, logger, eventBus,
    validateStart: (body) => validateStart(body, cfg),
    rateLimits: cfg.rate_limits,
  }));

  app.use('/api/proxy', requireAuth, buildProxyRoutes({
    cfg, state, proxyRegistry, logger,
    validateProxyUpload,
    rateLimits: cfg.rate_limits,
  }));

  app.use('/api/bots', requireAuth, buildBotRoutes({ state, logger, rateLimits: cfg.rate_limits }));
  app.use('/api/stats', requireAuth, buildStatsRoutes({ state, proxyRegistry }));
  app.use('/api/logs', requireAuth, buildStatsRoutes({ state, proxyRegistry }));

  const startedAt = Date.now();
  app.use('/api', buildHealthRoutes({ state, proxyRegistry, startedAt }));

  app.get('/login.html', (req, res) => {
    res.sendFile(path.join(__dirname, '..', 'public', 'login.html'));
  });

  app.get('/', requireAuth, (req, res) => {
    res.sendFile(path.join(__dirname, '..', 'public', 'index.html'));
  });

  app.get('/style.css', (req, res) => {
    res.sendFile(path.join(__dirname, '..', 'public', 'style.css'));
  });

  app.get('/client.js', (req, res) => {
    res.sendFile(path.join(__dirname, '..', 'public', 'client.js'));
  });

  app.use(express.static(path.join(__dirname, '..', 'public')));

  app.use((err, req, res, next) => {
    logger.error(`Express error: ${err.message}`);
    if (res.headersSent) return next(err);
    res.status(500).json({ error: 'Internal server error' });
  });

  logger.onEntry((entry) => {
    state.pushLog(entry);
  });

  eventBus.on('log', (entry) => {
    broadcaster.emitLog(entry);
  });

  eventBus.on('bot:state', (data) => {
    broadcaster.emitBotUpdate(data.id);
  });

  eventBus.on('bot:keepalive', () => {
    state.counters.totalKeepAlivesSent++;
    broadcaster.emitStats();
  });

  eventBus.on('bot:handshake', () => {
    state.counters.totalHandshakesOk++;
    broadcaster.emitStats();
  });

  eventBus.on('bot:dead', ({ id, reason }) => {
    state.counters.totalConnectionsFailed++;
    state.removeBot(id);
    broadcaster.forgetBot(id);
    broadcaster.emitStats();
    if (state.testRunning && state.activeBots.size === 0) {
      state.testRunning = false;
      if (state.proxyLease) {
        state.proxyLease.returnAll();
        state.proxyLease = null;
      }
      logger.info('All bots ended — test complete.');
    }
  });

  eventBus.on('proxy:success', ({ key, latencyMs }) => {
    proxyRegistry.markSuccess(key, latencyMs);
  });

  eventBus.on('proxy:failure', ({ key, reason }) => {
    if (proxyRegistry.has(key)) {
      proxyRegistry.markFailure(key, reason || 'unknown');
    }
  });

  eventBus.on('proxy:release', ({ key, botId }) => {
    if (state.proxyLease) {
      state.proxyLease.release(key);
    } else {
      proxyRegistry.release(key);
    }
  });

  const decayInterval = setInterval(() => {
    proxyRegistry.decay();
  }, cfg.proxy.score_decay_ms);

  function shutdown() {
    logger.info('Shutting down...');
    clearInterval(decayInterval);
    state.testRunning = false;
    for (const b of state.activeBots.values()) b.stop('shutdown');
    if (state.proxyLease) state.proxyLease.returnAll();
    proxyRegistry.save();
    broadcaster.shutdown();
    server.close(() => {
      logger.info('Server closed.');
      process.exit(0);
    });
    setTimeout(() => process.exit(1), 5000);
  }

  process.on('SIGINT', shutdown);
  process.on('SIGTERM', shutdown);

  io.on('connection', (socket) => {
    broadcaster.emitStats();
    socket.emit('stats', {
      activeBots: state.activeBots.size,
      totalBotsSpawned: state.counters.totalBotsSpawned,
      totalConnectionsFailed: state.counters.totalConnectionsFailed,
      totalKeepAlivesSent: state.counters.totalKeepAlivesSent,
      totalHandshakesOk: state.counters.totalHandshakesOk,
      testRunning: state.testRunning,
      currentTarget: state.currentTarget,
      activeProxies: proxyRegistry.list({ status: 'alive' }).length,
      deadProxies: proxyRegistry.list({ status: 'dead' }).length,
      bots: state.snapshot(),
    });
  });

  return { app, server, state, logger, proxyRegistry, broadcaster, cfg, shutdown };
}

function start() {
  const { server, cfg, logger, proxyRegistry } = createApp();
  const port = cfg.server.port;
  const host = cfg.server.host;
  server.listen(port, host, () => {
    console.log('');
    console.log('  ╔════════════════════════════════════════════╗');
    console.log('  ║  Eaglercraft Load-Testing Dashboard        ║');
    console.log('  ║  MC 1.12.2 (Protocol 340)                  ║');
    console.log('  ║  For authorized testing only.              ║');
    console.log('  ╚════════════════════════════════════════════╝');
    console.log(`  http://${host === '0.0.0.0' ? 'localhost' : host}:${port}`);
    console.log('');
    logger.info(`Server listening on ${host}:${port}`);
    logger.info(`Proxies loaded: ${proxyRegistry.count()}`);
    if (proxyRegistry.count() === 0) {
      logger.info('No proxies loaded. Use the dashboard to upload a proxy list.');
    }
  });
  return server;
}

if (require.main === module) {
  start();
}

module.exports = { createApp, start };
