const PUBLIC_CHAIN_HOSTS = new Set([
  'chain.aisfor.kr',
  'chain.plais.kr'
]);

const rateBuckets = new Map();
let lastRateBucketCleanup = 0;

function configuredCorsOrigins() {
  const configured = String(process.env.AISFOR_CHAIN_CORS_ORIGINS || '')
    .split(',')
    .map((value) => value.trim())
    .filter(Boolean);
  return new Set(configured.length ? configured : [
    'https://chain.aisfor.kr',
    'https://chain.plais.kr'
  ]);
}

function corsOptionsDelegate(req, callback) {
  const origin = String(req.get('origin') || '').trim();
  const allowed = !origin || configuredCorsOrigins().has(origin);
  callback(null, {
    origin: allowed,
    credentials: allowed,
    methods: ['GET', 'HEAD', 'POST', 'OPTIONS'],
    allowedHeaders: [
      'Content-Type',
      'X-AISFOR-SERVICE',
      'X-AISFOR-API-KEY',
      'X-AISFOR-SIGNATURE',
      'X-AISFOR-TIMESTAMP',
      'X-AISFOR-NONCE'
    ],
    maxAge: 600
  });
}

function securityHeaders(req, res, next) {
  res.setHeader('X-Content-Type-Options', 'nosniff');
  res.setHeader('X-Frame-Options', 'DENY');
  res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
  res.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=(), payment=()');
  res.setHeader('Content-Security-Policy', "base-uri 'self'; frame-ancestors 'none'; object-src 'none'");
  if (req.secure) {
    res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
  }
  next();
}

function publicHttpsOnly(req, res, next) {
  const host = String(req.hostname || req.get('host') || '').split(':')[0].toLowerCase();
  if (!PUBLIC_CHAIN_HOSTS.has(host) || req.secure) return next();
  return res.redirect(308, `https://${host}${req.originalUrl || '/'}`);
}

function assertSecureRuntimeConfig() {
  if (process.env.AISFOR_CHAIN_SECURITY_ENFORCED !== '1') return;
  const issues = [];
  const bindHost = String(process.env.AISFOR_CHAIN_BIND_HOST || '127.0.0.1').trim().toLowerCase();
  if (!['127.0.0.1', 'localhost', '::1'].includes(bindHost)) issues.push('AISFOR_CHAIN_BIND_HOST must be loopback');
  if (String(process.env.AISFOR_CHAIN_CANONICAL_LEDGER || '').trim().toLowerCase() !== 'besu-qbft') issues.push('AISFOR_CHAIN_CANONICAL_LEDGER must be besu-qbft');
  if (process.env.AISFOR_CHAIN_PUBLIC_API_LOCK !== '1') issues.push('AISFOR_CHAIN_PUBLIC_API_LOCK must be 1');
  if (process.env.AISFOR_CHAIN_AUTH_REQUIRED !== '1') issues.push('AISFOR_CHAIN_AUTH_REQUIRED must be 1');
  if (process.env.AISFOR_CHAIN_REQUIRE_HMAC_V2 !== '1') issues.push('AISFOR_CHAIN_REQUIRE_HMAC_V2 must be 1');
  if (process.env.AISFOR_CHAIN_COOKIE_SECURE !== '1') issues.push('AISFOR_CHAIN_COOKIE_SECURE must be 1');
  if (process.env.AISFOR_CHAIN_MOCK_BLOCK_WRITES_ENABLED !== '0') issues.push('AISFOR_CHAIN_MOCK_BLOCK_WRITES_ENABLED must be 0');
  if (process.env.PLAIS_EVM_CANDIDATE_ENABLED !== '1') issues.push('PLAIS_EVM_CANDIDATE_ENABLED must be 1');
  if (String(process.env.PLAIS_EVM_CONSENSUS || 'QBFT').trim().toUpperCase() !== 'QBFT') issues.push('PLAIS_EVM_CONSENSUS must be QBFT');
  if (!['0', '1'].includes(String(process.env.PLAIS_EVM_EXTERNAL_TRANSFERS_ENABLED || ''))) {
    issues.push('PLAIS_EVM_EXTERNAL_TRANSFERS_ENABLED must be explicitly set to 0 or 1');
  }
  if (/^0x[0-9a-fA-F]{64}$/.test(String(process.env.PLAIS_EVM_OPERATOR_PRIVATE_KEY || '').trim())) issues.push('PLAIS_EVM_OPERATOR_PRIVATE_KEY must not be loaded by the web process');
  if (!/^0x[0-9a-fA-F]{40}$/.test(String(process.env.PLAIS_EVM_OPERATOR_ADDRESS || '').trim())) issues.push('PLAIS_EVM_OPERATOR_ADDRESS must be configured');
  if (String(process.env.PLAIS_EVM_OPERATOR_CUSTODY_MODE || '').trim() !== 'offline-dpapi') issues.push('PLAIS_EVM_OPERATOR_CUSTODY_MODE must be offline-dpapi');
  if (process.env.PLAIS_EVM_OPERATOR_SIGNING_ENABLED !== '0') issues.push('PLAIS_EVM_OPERATOR_SIGNING_ENABLED must be 0');
  if (process.env.PLAIS_MULTICHAIN_TRANSFERS_ENABLED !== '0') issues.push('PLAIS_MULTICHAIN_TRANSFERS_ENABLED must be 0');
  if (String(process.env.PLAIS_EVM_CURRENCY_SYMBOL || '').trim().toUpperCase() !== 'PLAIS') issues.push('PLAIS_EVM_CURRENCY_SYMBOL must be PLAIS');
  if (Number(process.env.PLAIS_EVM_CURRENCY_DECIMALS) !== 18) issues.push('PLAIS_EVM_CURRENCY_DECIMALS must be 18');
  if (String(process.env.AISFOR_TOKEN_SYMBOL || '').trim().toUpperCase() !== 'PLAIS') issues.push('AISFOR_TOKEN_SYMBOL must align with PLAIS');
  if (Number(process.env.AISFOR_TOKEN_DECIMALS) !== 6) issues.push('AISFOR_TOKEN_DECIMALS must be 6 for legacy point storage');
  if (String(process.env.AISFOR_COIN_ISSUANCE || '').trim() !== 'legacy-simulation-locked') issues.push('AISFOR_COIN_ISSUANCE must be legacy-simulation-locked');
  if (String(process.env.AISFOR_TOKEN_PHASE || '').trim() !== 'internal-point') issues.push('AISFOR_TOKEN_PHASE must be internal-point');
  if (String(process.env.AISFOR_REWARD_MODE || '').trim() !== 'simulation') issues.push('AISFOR_REWARD_MODE must be simulation');
  if (String(process.env.AISFOR_PUBLIC_COIN_ISSUANCE || '').trim() !== 'disabled') issues.push('AISFOR_PUBLIC_COIN_ISSUANCE must be disabled');
  if (process.env.AISFOR_TESTNET_TOKEN_RESETTABLE !== '0') issues.push('AISFOR_TESTNET_TOKEN_RESETTABLE must be 0');
  if (String(process.env.AISFOR_TOKEN_MAX_SUPPLY || '').trim() !== String(process.env.PLAIS_EVM_GENESIS_SUPPLY || '').trim()) issues.push('legacy policy cap must match canonical genesis supply');
  if (issues.length) {
    throw new Error(`AISFOR Chain security startup gate failed: ${issues.join('; ')}`);
  }
}

function cleanupRateBuckets(now) {
  if (now - lastRateBucketCleanup < 60 * 1000) return;
  lastRateBucketCleanup = now;
  for (const [key, bucket] of rateBuckets.entries()) {
    if (bucket.resetAt <= now) rateBuckets.delete(key);
  }
}

function createRateLimiter({ scope, windowMs, max, keyGenerator } = {}) {
  const safeWindowMs = Math.max(1000, Number(windowMs) || 60 * 1000);
  const safeMax = Math.max(1, Number(max) || 60);
  return function rateLimiter(req, res, next) {
    const now = Date.now();
    cleanupRateBuckets(now);
    const identity = keyGenerator ? keyGenerator(req) : (req.ip || req.socket?.remoteAddress || 'unknown');
    const key = `${scope || 'request'}:${identity}`;
    let bucket = rateBuckets.get(key);
    if (!bucket || bucket.resetAt <= now) {
      bucket = { count: 0, resetAt: now + safeWindowMs };
      rateBuckets.set(key, bucket);
    }
    bucket.count += 1;
    res.setHeader('X-RateLimit-Limit', String(safeMax));
    res.setHeader('X-RateLimit-Remaining', String(Math.max(0, safeMax - bucket.count)));
    res.setHeader('X-RateLimit-Reset', String(Math.ceil(bucket.resetAt / 1000)));
    if (bucket.count <= safeMax) return next();
    res.setHeader('Retry-After', String(Math.max(1, Math.ceil((bucket.resetAt - now) / 1000))));
    return res.status(429).json({
      ok: false,
      error: 'RATE_LIMITED',
      message: 'Too many requests. Try again later.'
    });
  };
}

const apiRequestRateLimit = createRateLimiter({
  scope: 'api-request',
  windowMs: Number(process.env.AISFOR_CHAIN_API_RATE_WINDOW_MS || 60 * 1000),
  max: Number(process.env.AISFOR_CHAIN_API_RATE_MAX || 600)
});

const adminLoginRateLimit = createRateLimiter({
  scope: 'admin-login',
  windowMs: Number(process.env.AISFOR_CHAIN_ADMIN_LOGIN_RATE_WINDOW_MS || 15 * 60 * 1000),
  max: Number(process.env.AISFOR_CHAIN_ADMIN_LOGIN_RATE_MAX || 10),
  keyGenerator: (req) => `${req.ip || 'unknown'}:${String(req.body?.username || '').trim().toLowerCase()}`
});

const walletMutationRateLimit = createRateLimiter({
  scope: 'wallet-mutation',
  windowMs: Number(process.env.AISFOR_CHAIN_WALLET_RATE_WINDOW_MS || 10 * 60 * 1000),
  max: Number(process.env.AISFOR_CHAIN_WALLET_RATE_MAX || 20),
  keyGenerator: (req) => `${req.ip || 'unknown'}:${String(req.walletUser?.userId || 'anonymous')}`
});

module.exports = {
  adminLoginRateLimit,
  apiRequestRateLimit,
  assertSecureRuntimeConfig,
  corsOptionsDelegate,
  publicHttpsOnly,
  securityHeaders,
  walletMutationRateLimit
};
