import http from 'node:http';
import fs from 'node:fs';
import path from 'node:path';
import { config } from './config.js';
import { getDb } from './db.js';
import { bearerToken, issueSession, revokeSession, verifyToken } from './auth.js';
import { sendPhoneRequest, sendTelegramText, validateTelegramInitData, validateTelegramLoginData } from './telegram.js';
import {
  adminAuditLogs,
  adminDashboard,
  adminGrantPoints,
  adminLedger,
  adminListQuests,
  adminListVip,
  adminListWheel,
  adminSetVip,
  adminUpdateQuest,
  adminUpdateSettings,
  adminUpdateVip,
  adminUpdateWheel,
  adminUsers,
  buyVipWithPoints,
  claimQuest,
  completeAdSession,
  convertPoints,
  getGameState,
  getOrCreateUser,
  getPublicConfig,
  leaderboard,
  listQuests,
  referralSummary,
  spinWheel,
  startAdSession,
  tap,
  wallet
} from './game.js';
import {
  addWithdrawalAddress,
  adminDeposits,
  adminFinanceSummary,
  adminPaymentOrders,
  adminPointTransfers,
  adminPresale,
  adminSiteContent,
  adminUpdateAllocation,
  adminUpdateChapter,
  adminUpdatePresaleStage,
  adminUpdateRoadmap,
  adminUpdateSitePage,
  adminUpdateSiteSection,
  adminUpdateUser,
  adminWithdrawals,
  approveDeposit,
  approvePaymentOrder,
  createDepositSubmission,
  createPresaleOrder,
  createVipUsdtOrder,
  getPlatformWallet,
  getPublicSite,
  getUserIdentity,
  linkTelegramContact,
  lookupRecipient,
  rejectDeposit,
  rejectPaymentOrder,
  requestWithdrawal,
  submitPaymentTransaction,
  transferPoints,
  updatePlatformSettings,
  updateWithdrawal,
  verifyEvmPaymentOrder
} from './platform.js';
import {
  adminCreateBankAccount,
  adminPaymentSettings,
  adminTestPaymentProvider,
  adminUpdateBankAccount,
  adminUpdateCryptoAsset,
  adminUpdateCurrency,
  adminUpdatePaymentProvider,
  adminUpdateVipPrice,
  createPaymentCheckout,
  getCheckoutSession,
  getPublicPaymentCatalog,
  processGenericWebhook,
  processPaytrWebhook,
  syncTcmbRates
} from './payments.js';
import {
  adminAuthProviders,
  adminUpdateAuthProvider,
  completeOAuth,
  exchangeLoginCode,
  loginWithEmail,
  publicAuthConfig,
  registerWithEmail,
  requestPasswordReset,
  resendEmailVerification,
  resetPassword,
  startOAuth,
  verifyEmail
} from './identity.js';
import {
  addExternalMainnetWallet,
  adminWallets,
  confirmExternalWallet,
  createTestnetWallet,
  downloadRecoveryPackage,
  listUserWallets,
  verifyWalletPassword
} from './wallets.js';
import {
  approvePresaleSnapshot,
  createPresaleSnapshot,
  finalizePresaleAllocation,
  presaleAdminOverview,
  presaleConfig,
  presaleUserSummary,
  updatePresaleCoreSettings
} from './presale.js';
import {
  broadcastManualDistributionItem,
  confirmDistributionItem,
  createDistributionCampaign,
  distributionOverview,
  launchAutomationTick,
  refreshCampaignWallets,
  runDistributionCampaign
} from './distribution.js';
import { deliverEmailById, deliverPendingEmails, emailOutbox } from './email.js';
import { authenticateAdmin, getAdmin } from './admin-auth.js';
import { completeInstallation, ensureInstallToken, installerStatus, isInstalled, testInstallerSmtp, verifyInstallToken } from './installer.js';
import { maskSecret, secretSummary, setSecret } from './secrets.js';
import { nowIso, sanitizeText, uuid } from './utils.js';

const db = getDb();
const rateBuckets = new Map();

function clientIp(req) {
  if (config.trustProxy) return String(req.headers['x-forwarded-for'] || '').split(',')[0].trim() || req.socket.remoteAddress || '';
  return req.socket.remoteAddress || '';
}

function securityHeaders(res, requestId) {
  res.setHeader('X-Request-ID', requestId);
  res.setHeader('X-Content-Type-Options', 'nosniff');
  res.setHeader('X-Frame-Options', 'SAMEORIGIN');
  res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
  res.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
  res.setHeader('Cross-Origin-Opener-Policy', 'same-origin-allow-popups');
  res.setHeader('Content-Security-Policy', [
    "default-src 'self'",
    "img-src 'self' data: blob:",
    "style-src 'self' 'unsafe-inline'",
    "script-src 'self' 'unsafe-inline' https://telegram.org",
    "connect-src 'self' https://telegram.org https://oauth.telegram.org",
    "frame-src 'self' https://oauth.telegram.org https://telegram.org",
    "frame-ancestors 'self' https://web.telegram.org https://*.telegram.org",
    "base-uri 'self'",
    "form-action 'self'"
  ].join('; '));
  if (config.isProduction) res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
}

function sendJson(res, status, data, requestId) {
  const body = JSON.stringify(data);
  securityHeaders(res, requestId);
  res.writeHead(status, {
    'Content-Type': 'application/json; charset=utf-8',
    'Cache-Control': 'no-store',
    'Content-Length': Buffer.byteLength(body)
  });
  res.end(body);
}

function sendText(res, status, text, requestId, contentType = 'text/plain; charset=utf-8') {
  const body = String(text ?? '');
  securityHeaders(res, requestId);
  res.writeHead(status, {
    'Content-Type': contentType,
    'Cache-Control': 'no-store',
    'Content-Length': Buffer.byteLength(body)
  });
  res.end(body);
}

function rateLimit(req, key, limit, windowSeconds) {
  const now = Date.now();
  const bucketKey = `${key}:${clientIp(req)}`;
  const bucket = rateBuckets.get(bucketKey) || { count: 0, resetAt: now + windowSeconds * 1000 };
  if (bucket.resetAt <= now) {
    bucket.count = 0;
    bucket.resetAt = now + windowSeconds * 1000;
  }
  bucket.count += 1;
  rateBuckets.set(bucketKey, bucket);
  if (bucket.count > limit) {
    const error = Object.assign(new Error('Çok fazla istek gönderildi.'), {
      code: 'RATE_LIMITED',
      status: 429,
      details: { retryAfter: Math.ceil((bucket.resetAt - now) / 1000) }
    });
    throw error;
  }
}

async function readRawBody(req, maxBytes = 1024 * 1024) {
  const chunks = [];
  let size = 0;
  for await (const chunk of req) {
    size += chunk.length;
    if (size > maxBytes) throw Object.assign(new Error('İstek gövdesi çok büyük.'), { code: 'BODY_TOO_LARGE', status: 413 });
    chunks.push(chunk);
  }
  return Buffer.concat(chunks);
}

function parseRequestBody(req, raw) {
  if (!raw.length) return {};
  const text = raw.toString('utf8');
  const contentType = String(req.headers['content-type'] || '').split(';')[0].trim().toLowerCase();
  if (contentType === 'application/x-www-form-urlencoded') {
    return Object.fromEntries(new URLSearchParams(text));
  }
  if (contentType === 'application/json' || contentType === '' || contentType.endsWith('+json')) {
    try { return JSON.parse(text); }
    catch { throw Object.assign(new Error('Geçersiz JSON.'), { code: 'INVALID_JSON', status: 400 }); }
  }
  throw Object.assign(new Error('Desteklenmeyen içerik türü.'), { code: 'UNSUPPORTED_CONTENT_TYPE', status: 415 });
}

async function readBody(req, maxBytes = 1024 * 1024) {
  const raw = await readRawBody(req, maxBytes);
  return { raw, body: parseRequestBody(req, raw) };
}

async function readJson(req, maxBytes = 1024 * 1024) {
  return (await readBody(req, maxBytes)).body;
}


function parseCookies(req) {
  const result = {};
  for (const part of String(req.headers.cookie || '').split(';')) {
    const index = part.indexOf('=');
    if (index <= 0) continue;
    const key = part.slice(0, index).trim();
    const value = part.slice(index + 1).trim();
    try { result[key] = decodeURIComponent(value); } catch { result[key] = value; }
  }
  return result;
}

function requestToken(req, role = 'user') {
  const bearer = bearerToken(req);
  if (bearer) return bearer;
  const cookies = parseCookies(req);
  return role === 'admin' ? (cookies.oc_admin_session || '') : (cookies.oc_session || '');
}

function cookieHeader(name, value, maxAgeSeconds) {
  const secure = config.isProduction ? '; Secure' : '';
  return `${name}=${encodeURIComponent(value)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${Math.max(0, Number(maxAgeSeconds) || 0)}${secure}`;
}

function setSessionCookie(res, session, role = 'user') {
  const name = role === 'admin' ? 'oc_admin_session' : 'oc_session';
  const maxAge = Math.max(0, Number(session.expiresAt || 0) - Math.floor(Date.now() / 1000));
  res.setHeader('Set-Cookie', cookieHeader(name, session.token, maxAge));
}

function clearSessionCookies(res) {
  res.setHeader('Set-Cookie', [cookieHeader('oc_session', '', 0), cookieHeader('oc_admin_session', '', 0)]);
}

function userAuth(req) {
  return verifyToken(db, requestToken(req, 'user'), 'user');
}

function adminAuth(req) {
  const payload = verifyToken(db, requestToken(req, 'admin'), 'admin');
  const admin = getAdmin(db, payload.sub);
  if (!admin || admin.status !== 'active') throw Object.assign(new Error('Yönetici oturumu geçersiz.'), { code: 'ADMIN_SESSION_INVALID', status: 401 });
  return { ...payload, admin };
}

const mime = {
  '.html': 'text/html; charset=utf-8',
  '.css': 'text/css; charset=utf-8',
  '.js': 'application/javascript; charset=utf-8',
  '.json': 'application/json; charset=utf-8',
  '.png': 'image/png',
  '.jpg': 'image/jpeg',
  '.jpeg': 'image/jpeg',
  '.svg': 'image/svg+xml',
  '.webp': 'image/webp',
  '.ico': 'image/x-icon',
  '.webmanifest': 'application/manifest+json',
  '.txt': 'text/plain; charset=utf-8'
};

function safeFile(root, relativePath) {
  const clean = decodeURIComponent(relativePath).replaceAll('\\', '/').replace(/^\/+/, '');
  const base = path.resolve(root);
  const full = path.resolve(base, clean || 'index.html');
  if (full !== base && !full.startsWith(base + path.sep)) return null;
  return full;
}

function serveStatic(req, res, url, requestId) {
  let root;
  let relative;
  let spa = false;
  if (url.pathname.startsWith('/install')) {
    root = path.join(config.rootDir, 'apps/installer');
    relative = url.pathname.replace(/^\/install\/?/, '');
    spa = true;
  } else if (url.pathname.startsWith('/game')) {
    root = path.join(config.rootDir, 'apps/game');
    relative = url.pathname.replace(/^\/game\/?/, '');
    spa = true;
  } else if (url.pathname.startsWith('/admin')) {
    root = path.join(config.rootDir, 'apps/admin');
    relative = url.pathname.replace(/^\/admin\/?/, '');
    spa = true;
  } else {
    root = path.join(config.rootDir, 'apps/website');
    relative = url.pathname.replace(/^\//, '');
    spa = true;
  }
  let file = safeFile(root, relative || 'index.html');
  if (!file) return false;
  if (fs.existsSync(file) && fs.statSync(file).isDirectory()) file = path.join(file, 'index.html');
  if (!fs.existsSync(file) && spa && !path.extname(relative)) file = path.join(root, 'index.html');
  if (!fs.existsSync(file) || !fs.statSync(file).isFile()) return false;
  const stat = fs.statSync(file);
  securityHeaders(res, requestId);
  res.writeHead(200, {
    'Content-Type': mime[path.extname(file).toLowerCase()] || 'application/octet-stream',
    'Content-Length': stat.size,
    'Cache-Control': path.basename(file) === 'service-worker.js' ? 'no-cache' : (path.extname(file) === '.html' ? 'no-cache' : 'public, max-age=86400')
  });
  fs.createReadStream(file).pipe(res);
  return true;
}

async function handleApi(req, res, url, requestId) {
  const method = req.method || 'GET';
  const pathName = url.pathname;

  if (method === 'GET' && pathName === '/api/health') {
    return sendJson(res, 200, { ok: true, service: 'onlycoin-api', version: config.appVersion, time: nowIso(), environment: config.environment, installed: isInstalled(), runtimeReady: config.installed }, requestId);
  }
  if (method === 'GET' && pathName === '/api/install/status') {
    return sendJson(res, 200, installerStatus(), requestId);
  }
  if (method === 'POST' && pathName === '/api/install/test-smtp') {
    rateLimit(req, 'install-smtp-test', 8, 900);
    const body = await readJson(req);
    const token = String(req.headers['x-install-token'] || body.installToken || '');
    return sendJson(res, 200, await testInstallerSmtp(body, token), requestId);
  }
  if (method === 'POST' && pathName === '/api/install/complete') {
    rateLimit(req, 'install-complete', 5, 3600);
    const body = await readJson(req);
    const token = String(req.headers['x-install-token'] || body.installToken || '');
    const result = await completeInstallation(db, body, token, clientIp(req));
    sendJson(res, 201, result, requestId);
    if (config.environment !== 'test') setTimeout(() => process.exit(75), 1000).unref?.();
    return;
  }
  if (!isInstalled()) {
    throw Object.assign(new Error('OnlyCoin kurulumu tamamlanmalıdır.'), { code: 'INSTALL_REQUIRED', status: 503, details: { installer: '/install/' } });
  }
  if (method === 'GET' && pathName === '/api/public/config') {
    rateLimit(req, 'public-config', 120, 60);
    return sendJson(res, 200, getPublicConfig(db), requestId);
  }
  if (method === 'GET' && pathName === '/api/public/site') {
    rateLimit(req, 'public-site', 120, 60);
    return sendJson(res, 200, getPublicSite(db), requestId);
  }
  if (method === 'GET' && pathName === '/api/public/payments') {
    rateLimit(req, 'public-payments', 120, 60);
    return sendJson(res, 200, getPublicPaymentCatalog(db, url.searchParams.get('country') || ''), requestId);
  }
  if (method === 'GET' && pathName === '/api/public/auth') {
    rateLimit(req, 'public-auth-config', 120, 60);
    return sendJson(res, 200, publicAuthConfig(db), requestId);
  }
  if (method === 'GET' && pathName === '/api/public/presale') {
    rateLimit(req, 'public-presale-config', 120, 60);
    const configData = presaleConfig(db);
    return sendJson(res, 200, { ...configData, tokenDisplayDecimals: Math.min(8, configData.decimals) }, requestId);
  }
  if (method === 'POST' && pathName === '/api/auth/telegram') {
    rateLimit(req, 'telegram-login', 30, 60);
    const body = await readJson(req);
    const tg = validateTelegramInitData(String(body.initData || ''));
    const user = getOrCreateUser(db, {
      externalId: `telegram:${tg.id}`,
      username: tg.username || `tg_${tg.id}`,
      displayName: [tg.first_name, tg.last_name].filter(Boolean).join(' ') || tg.username || 'Telegram Oyuncusu',
      avatar: tg.photo_url || '',
      referralCode: sanitizeText(body.referralCode || '', 20)
    });
    const session = issueSession(db, { userId: user.id, role: 'user' });
    setSessionCookie(res, session, 'user');
    return sendJson(res, 200, { ...session, user, mode: 'telegram' }, requestId);
  }
  if (method === 'POST' && pathName === '/api/auth/telegram-login') {
    rateLimit(req, 'telegram-web-login', 30, 60);
    const body = await readJson(req);
    const tg = validateTelegramLoginData(body);
    const user = getOrCreateUser(db, {
      externalId: `telegram:${tg.id}`,
      username: tg.username || `tg_${tg.id}`,
      displayName: [tg.first_name, tg.last_name].filter(Boolean).join(' ') || tg.username || 'Telegram Üyesi',
      avatar: tg.photo_url || '',
      referralCode: sanitizeText(body.referralCode || '', 20)
    });
    if (tg.phone_number) {
      db.prepare('UPDATE users SET telegram_phone=?,phone_verified_at=?,updated_at=? WHERE id=?').run(tg.phone_number, nowIso(), nowIso(), user.id);
    }
    const session = issueSession(db, { userId: user.id, role: 'user' });
    setSessionCookie(res, session, 'user');
    return sendJson(res, 200, { ...session, user: getUserIdentity(db, user.id), mode: 'telegram-web' }, requestId);
  }
  if (method === 'POST' && pathName === '/api/auth/register') {
    rateLimit(req, 'email-register', 10, 3600);
    const body = await readJson(req);
    const result = registerWithEmail(db, body);
    const deliveryId = result.emailDeliveryId;
    delete result.emailDeliveryId;
    if (!result.verificationRequired) {
      const session = issueSession(db, { userId: result.user.id, role: 'user' });
      setSessionCookie(res, session, 'user');
      return sendJson(res, 201, { ...result, ...session }, requestId);
    }
    const delivery = deliveryId ? await deliverEmailById(db, deliveryId) : { delivered: false, error: 'E-posta kuyruğu oluşturulamadı.' };
    return sendJson(res, 201, { ...result, emailDelivery: delivery }, requestId);
  }
  if (method === 'POST' && pathName === '/api/auth/email/verify') {
    rateLimit(req, 'email-verify', 20, 3600);
    const body = await readJson(req);
    const result = verifyEmail(db, body);
    const session = issueSession(db, { userId: result.user.id, role: 'user' });
    setSessionCookie(res, session, 'user');
    return sendJson(res, 200, { ...result, ...session }, requestId);
  }
  if (method === 'POST' && pathName === '/api/auth/email/resend') {
    rateLimit(req, 'email-resend', 10, 3600);
    const body = await readJson(req);
    const result = resendEmailVerification(db, body.email);
    const deliveryId = result.emailDeliveryId;
    delete result.emailDeliveryId;
    const delivery = deliveryId ? await deliverEmailById(db, deliveryId) : { delivered: false, skipped: true };
    return sendJson(res, 200, { ...result, emailDelivery: delivery }, requestId);
  }
  if (method === 'POST' && pathName === '/api/auth/email/login') {
    rateLimit(req, 'email-login', 20, 300);
    const body = await readJson(req);
    const result = loginWithEmail(db, body);
    const session = issueSession(db, { userId: result.user.id, role: 'user' });
    setSessionCookie(res, session, 'user');
    return sendJson(res, 200, { ...result, ...session }, requestId);
  }
  if (method === 'POST' && pathName === '/api/auth/password/request') {
    rateLimit(req, 'password-reset-request', 5, 3600);
    const body = await readJson(req);
    const result = requestPasswordReset(db, body.email);
    const delivery = await deliverPendingEmails(db, 5);
    return sendJson(res, 200, { ...result, emailDelivery: delivery }, requestId);
  }
  if (method === 'POST' && pathName === '/api/auth/password/reset') {
    rateLimit(req, 'password-reset', 10, 3600);
    const body = await readJson(req);
    return sendJson(res, 200, resetPassword(db, body), requestId);
  }
  const oauthStart = pathName.match(/^\/api\/auth\/oauth\/([^/]+)\/start$/);
  if (method === 'GET' && oauthStart) {
    rateLimit(req, `oauth-start:${oauthStart[1]}`, 30, 60);
    const provider = decodeURIComponent(oauthStart[1]);
    return sendJson(res, 200, startOAuth(db, provider, { redirectAfter: url.searchParams.get('redirect') || '/game/', referralCode: url.searchParams.get('ref') || '' }), requestId);
  }
  const oauthCallback = pathName.match(/^\/api\/auth\/oauth\/([^/]+)\/callback$/);
  if (oauthCallback && (method === 'GET' || method === 'POST')) {
    rateLimit(req, `oauth-callback:${oauthCallback[1]}`, 60, 60);
    const callback = method === 'GET' ? Object.fromEntries(url.searchParams) : (await readBody(req, 512 * 1024)).body;
    const result = await completeOAuth(db, decodeURIComponent(oauthCallback[1]), callback);
    securityHeaders(res, requestId);
    res.writeHead(302, { Location: result.redirectUrl, 'Cache-Control': 'no-store' });
    return res.end();
  }
  if (method === 'POST' && pathName === '/api/auth/oauth/exchange') {
    rateLimit(req, 'oauth-exchange', 30, 60);
    const body = await readJson(req);
    const user = exchangeLoginCode(db, body.code);
    const session = issueSession(db, { userId: user.id, role: 'user' });
    setSessionCookie(res, session, 'user');
    return sendJson(res, 200, { ...session, user, mode: 'oauth' }, requestId);
  }

  const telegramWebhook = pathName.match(/^\/api\/telegram\/webhook\/([^/]+)$/);
  if (method === 'POST' && telegramWebhook) {
    if (!config.telegramWebhookSecret || telegramWebhook[1] !== config.telegramWebhookSecret) {
      throw Object.assign(new Error('Telegram webhook doğrulanamadı.'), { code: 'FORBIDDEN', status: 403 });
    }
    const update = await readJson(req, 2 * 1024 * 1024);
    const message = update.message || update.edited_message;
    if (message?.contact && String(message.contact.user_id || '') === String(message.from?.id || '')) {
      const result = linkTelegramContact(db, message.from.id, message.contact.phone_number, update.update_id, {
        firstName: message.contact.first_name,
        lastName: message.contact.last_name
      });
      await sendTelegramText(message.chat.id, result.linked
        ? '✅ Telefon numaranız OnlyCoin hesabınızla doğrulandı.'
        : 'Telefon numaranız alındı. Önce OnlyCoin Mini App’i açıp Telegram hesabınızla giriş yapın; sonraki eşleştirmede hesabınıza bağlanacaktır.');
    } else if (message?.text?.startsWith('/start')) {
      await sendPhoneRequest(message.chat.id, `${config.appOrigin}/game/`);
    } else if (message?.text === '/id') {
      await sendTelegramText(message.chat.id, `Telegram kullanıcı kimliğiniz: ${message.from.id}`);
    }
    return sendJson(res, 200, { ok: true }, requestId);
  }
  if (method === 'POST' && pathName === '/api/webhooks/paytr') {
    rateLimit(req, 'webhook-paytr', 600, 60);
    const { body } = await readBody(req, 512 * 1024);
    processPaytrWebhook(db, body, clientIp(req));
    return sendText(res, 200, 'OK', requestId);
  }
  const genericWebhook = pathName.match(/^\/api\/webhooks\/generic\/([^/]+)$/);
  if (method === 'POST' && genericWebhook) {
    rateLimit(req, `webhook-generic:${genericWebhook[1]}`, 600, 60);
    const { raw, body } = await readBody(req, 1024 * 1024);
    const signature = String(req.headers['x-onlycoin-signature'] || req.headers['x-webhook-signature'] || '');
    return sendJson(res, 200, processGenericWebhook(db, decodeURIComponent(genericWebhook[1]), raw, body, signature, clientIp(req)), requestId);
  }

  if (method === 'POST' && pathName === '/api/auth/logout') {
    const token = requestToken(req, 'user') || requestToken(req, 'admin');
    if (token) {
      try { revokeSession(db, verifyToken(db, token).sid); } catch { /* idempotent logout */ }
    }
    clearSessionCookies(res);
    return sendJson(res, 200, { ok: true }, requestId);
  }
  if (method === 'POST' && pathName === '/api/admin/login') {
    rateLimit(req, 'admin-login', 10, 300);
    const body = await readJson(req);
    const admin = authenticateAdmin(db, body, clientIp(req));
    const session = issueSession(db, { subject: admin.id, role: 'admin' });
    setSessionCookie(res, session, 'admin');
    return sendJson(res, 200, { ...session, admin }, requestId);
  }

  if (method === 'GET' && pathName === '/api/admin/me') {
    const auth = adminAuth(req);
    return sendJson(res, 200, auth.admin, requestId);
  }

  if (method === 'GET' && pathName === '/api/game/state') {
    const auth = userAuth(req);
    rateLimit(req, `state:${auth.sub}`, 120, 60);
    return sendJson(res, 200, getGameState(db, auth.sub), requestId);
  }
  if (method === 'POST' && pathName === '/api/game/tap') {
    const auth = userAuth(req);
    rateLimit(req, `tap:${auth.sub}`, 180, 60);
    const body = await readJson(req);
    const key = req.headers['idempotency-key'] || body.idempotencyKey;
    return sendJson(res, 200, tap(db, auth.sub, body.count, String(key || '')), requestId);
  }
  if (method === 'GET' && pathName === '/api/game/quests') {
    const auth = userAuth(req);
    return sendJson(res, 200, listQuests(db, auth.sub), requestId);
  }
  const questClaim = pathName.match(/^\/api\/game\/quests\/([^/]+)\/claim$/);
  if (method === 'POST' && questClaim) {
    const auth = userAuth(req);
    const body = await readJson(req);
    const key = req.headers['idempotency-key'] || body.idempotencyKey;
    return sendJson(res, 200, claimQuest(db, auth.sub, decodeURIComponent(questClaim[1]), String(key || '')), requestId);
  }
  if (method === 'GET' && pathName === '/api/game/wheel') {
    const auth = userAuth(req);
    const publicConfig = getPublicConfig(db);
    const state = getGameState(db, auth.sub);
    return sendJson(res, 200, { segments: publicConfig.wheelSegments, paidSpinCost: publicConfig.game.paidSpinCost, state }, requestId);
  }
  if (method === 'POST' && pathName === '/api/game/wheel/spin') {
    const auth = userAuth(req);
    rateLimit(req, `wheel:${auth.sub}`, 30, 60);
    const body = await readJson(req);
    const key = req.headers['idempotency-key'] || body.idempotencyKey;
    return sendJson(res, 200, spinWheel(db, auth.sub, body.mode, String(key || '')), requestId);
  }
  if (method === 'POST' && pathName === '/api/game/ads/start') {
    const auth = userAuth(req);
    rateLimit(req, `ad-start:${auth.sub}`, 20, 3600);
    return sendJson(res, 200, startAdSession(db, auth.sub), requestId);
  }
  if (method === 'POST' && pathName === '/api/game/ads/complete') {
    const auth = userAuth(req);
    const body = await readJson(req);
    return sendJson(res, 200, completeAdSession(db, auth.sub, body.adToken), requestId);
  }
  if (method === 'POST' && pathName === '/api/game/convert') {
    const auth = userAuth(req);
    rateLimit(req, `convert:${auth.sub}`, 20, 3600);
    const body = await readJson(req);
    const key = req.headers['idempotency-key'] || body.idempotencyKey;
    return sendJson(res, 200, convertPoints(db, auth.sub, body.points, String(key || '')), requestId);
  }
  if (method === 'GET' && pathName === '/api/game/referrals') {
    const auth = userAuth(req);
    return sendJson(res, 200, referralSummary(db, auth.sub), requestId);
  }
  if (method === 'GET' && pathName === '/api/game/leaderboard') {
    userAuth(req);
    return sendJson(res, 200, leaderboard(db, url.searchParams.get('limit')), requestId);
  }
  if (method === 'GET' && pathName === '/api/game/wallet') {
    const auth = userAuth(req);
    const gameWallet = wallet(db, auth.sub);
    const platformWallet = getPlatformWallet(db, auth.sub);
    return sendJson(res, 200, { ...platformWallet, ...gameWallet, distributions: platformWallet.distributions || [] }, requestId);
  }
  if (method === 'GET' && pathName === '/api/game/vip') {
    const auth = userAuth(req);
    return sendJson(res, 200, { plans: adminListVip(db), state: getGameState(db, auth.sub) }, requestId);
  }
  if (method === 'POST' && pathName === '/api/game/vip/purchase-points') {
    const auth = userAuth(req);
    const body = await readJson(req);
    const key = req.headers['idempotency-key'] || body.idempotencyKey;
    return sendJson(res, 200, buyVipWithPoints(db, auth.sub, body.tier, String(key || '')), requestId);
  }

  if (method === 'GET' && pathName === '/api/account/identity') {
    const auth = userAuth(req);
    return sendJson(res, 200, getUserIdentity(db, auth.sub), requestId);
  }
  if (method === 'GET' && pathName === '/api/platform/wallet') {
    const auth = userAuth(req);
    return sendJson(res, 200, getPlatformWallet(db, auth.sub), requestId);
  }
  if (method === 'GET' && pathName === '/api/game/recipient') {
    const auth = userAuth(req);
    return sendJson(res, 200, lookupRecipient(db, auth.sub, url.searchParams.get('query') || ''), requestId);
  }
  if (method === 'POST' && pathName === '/api/game/points/transfer') {
    const auth = userAuth(req);
    rateLimit(req, `point-transfer:${auth.sub}`, 30, 3600);
    const body = await readJson(req);
    const key = req.headers['idempotency-key'] || body.idempotencyKey;
    return sendJson(res, 200, transferPoints(db, auth.sub, body, String(key || '')), requestId);
  }
  if (method === 'POST' && pathName === '/api/game/vip/purchase-usdt') {
    const auth = userAuth(req);
    const body = await readJson(req);
    const key = req.headers['idempotency-key'] || body.idempotencyKey;
    return sendJson(res, 201, createVipUsdtOrder(db, auth.sub, body.tier, String(key || '')), requestId);
  }
  if (method === 'POST' && pathName === '/api/game/presale/order') {
    const auth = userAuth(req);
    const body = await readJson(req);
    const key = req.headers['idempotency-key'] || body.idempotencyKey;
    return sendJson(res, 201, createPresaleOrder(db, auth.sub, body.amountUsdtMicros, body.stageId, String(key || '')), requestId);
  }
  if (method === 'POST' && pathName === '/api/game/payment-checkout') {
    const auth = userAuth(req);
    rateLimit(req, `payment-checkout:${auth.sub}`, 30, 3600);
    const body = await readJson(req);
    const key = req.headers['idempotency-key'] || body.idempotencyKey;
    return sendJson(res, 201, await createPaymentCheckout(db, auth.sub, body, String(key || '')), requestId);
  }
  const checkoutSession = pathName.match(/^\/api\/game\/payment-checkout\/([^/]+)$/);
  if (method === 'GET' && checkoutSession) {
    const auth = userAuth(req);
    return sendJson(res, 200, getCheckoutSession(db, auth.sub, decodeURIComponent(checkoutSession[1])), requestId);
  }

  const paymentSubmit = pathName.match(/^\/api\/game\/payments\/([^/]+)\/submit$/);
  if (method === 'POST' && paymentSubmit) {
    const auth = userAuth(req);
    const body = await readJson(req);
    return sendJson(res, 200, submitPaymentTransaction(db, auth.sub, decodeURIComponent(paymentSubmit[1]), body.txHash), requestId);
  }
  if (method === 'POST' && pathName === '/api/game/deposits') {
    const auth = userAuth(req);
    const body = await readJson(req);
    const key = req.headers['idempotency-key'] || body.idempotencyKey;
    return sendJson(res, 201, createDepositSubmission(db, auth.sub, body, String(key || '')), requestId);
  }
  if (method === 'POST' && pathName === '/api/game/withdrawal-addresses') {
    const auth = userAuth(req);
    const body = await readJson(req);
    return sendJson(res, 201, addWithdrawalAddress(db, auth.sub, body), requestId);
  }
  if (method === 'POST' && pathName === '/api/game/withdrawals') {
    const auth = userAuth(req);
    const body = await readJson(req);
    const key = req.headers['idempotency-key'] || body.idempotencyKey;
    return sendJson(res, 201, requestWithdrawal(db, auth.sub, body, String(key || '')), requestId);
  }

  if (method === 'GET' && pathName === '/api/account/wallets') {
    const auth = userAuth(req);
    return sendJson(res, 200, listUserWallets(db, auth.sub), requestId);
  }
  if (method === 'POST' && pathName === '/api/account/wallets/testnet') {
    const auth = userAuth(req);
    rateLimit(req, `wallet-create:${auth.sub}`, 5, 3600);
    const body = await readJson(req);
    return sendJson(res, 201, createTestnetWallet(db, auth.sub, body), requestId);
  }
  const walletVerifyPassword = pathName.match(/^\/api\/account\/wallets\/([^/]+)\/verify-password$/);
  if (method === 'POST' && walletVerifyPassword) {
    const auth = userAuth(req);
    const body = await readJson(req);
    return sendJson(res, 200, verifyWalletPassword(db, auth.sub, decodeURIComponent(walletVerifyPassword[1]), body.password), requestId);
  }
  const walletRecovery = pathName.match(/^\/api\/account\/wallets\/recovery\/([^/]+)$/);
  if (method === 'GET' && walletRecovery) {
    const auth = userAuth(req);
    const data = downloadRecoveryPackage(db, auth.sub, decodeURIComponent(walletRecovery[1]));
    res.setHeader('Content-Disposition', `attachment; filename="onlycoin-wallet-${String(data.address).slice(2,10)}.json"`);
    return sendJson(res, 200, data, requestId);
  }
  if (method === 'POST' && pathName === '/api/account/wallets/mainnet') {
    const auth = userAuth(req);
    rateLimit(req, `wallet-mainnet:${auth.sub}`, 10, 3600);
    const body = await readJson(req);
    return sendJson(res, 201, addExternalMainnetWallet(db, auth.sub, body), requestId);
  }
  const walletConfirm = pathName.match(/^\/api\/account\/wallets\/([^/]+)\/confirm$/);
  if (method === 'POST' && walletConfirm) {
    const auth = userAuth(req);
    const body = await readJson(req);
    return sendJson(res, 200, confirmExternalWallet(db, auth.sub, decodeURIComponent(walletConfirm[1]), body.code), requestId);
  }
  if (method === 'GET' && pathName === '/api/game/presale/summary') {
    const auth = userAuth(req);
    return sendJson(res, 200, presaleUserSummary(db, auth.sub), requestId);
  }

  if (method === 'GET' && pathName === '/api/admin/dashboard') {
    adminAuth(req);
    return sendJson(res, 200, adminDashboard(db), requestId);
  }
  if (method === 'PUT' && pathName === '/api/admin/settings') {
    const auth = adminAuth(req);
    const body = await readJson(req);
    return sendJson(res, 200, adminUpdateSettings(db, body, auth.sub, clientIp(req)), requestId);
  }
  if (method === 'GET' && pathName === '/api/admin/vip') {
    adminAuth(req);
    return sendJson(res, 200, adminListVip(db), requestId);
  }
  const adminVip = pathName.match(/^\/api\/admin\/vip\/(\d+)$/);
  if (method === 'PUT' && adminVip) {
    const auth = adminAuth(req);
    const body = await readJson(req);
    return sendJson(res, 200, adminUpdateVip(db, adminVip[1], body, auth.sub, clientIp(req)), requestId);
  }
  if (method === 'GET' && pathName === '/api/admin/wheel') {
    adminAuth(req);
    return sendJson(res, 200, adminListWheel(db), requestId);
  }
  const adminWheel = pathName.match(/^\/api\/admin\/wheel\/([^/]+)$/);
  if (method === 'PUT' && adminWheel) {
    const auth = adminAuth(req);
    const body = await readJson(req);
    return sendJson(res, 200, adminUpdateWheel(db, decodeURIComponent(adminWheel[1]), body, auth.sub, clientIp(req)), requestId);
  }
  if (method === 'GET' && pathName === '/api/admin/quests') {
    adminAuth(req);
    return sendJson(res, 200, adminListQuests(db), requestId);
  }
  const adminQuest = pathName.match(/^\/api\/admin\/quests\/([^/]+)$/);
  if (method === 'PUT' && adminQuest) {
    const auth = adminAuth(req);
    const body = await readJson(req);
    return sendJson(res, 200, adminUpdateQuest(db, decodeURIComponent(adminQuest[1]), body, auth.sub, clientIp(req)), requestId);
  }
  if (method === 'GET' && pathName === '/api/admin/users') {
    adminAuth(req);
    return sendJson(res, 200, adminUsers(db, url.searchParams.get('search') || '', url.searchParams.get('limit')), requestId);
  }
  const adminGrant = pathName.match(/^\/api\/admin\/users\/([^/]+)\/grant-points$/);
  if (method === 'POST' && adminGrant) {
    const auth = adminAuth(req);
    const body = await readJson(req);
    const key = req.headers['idempotency-key'] || body.idempotencyKey;
    return sendJson(res, 200, adminGrantPoints(db, decodeURIComponent(adminGrant[1]), body.amount, body.reason, auth.sub, clientIp(req), String(key || '')), requestId);
  }
  const adminAssignVip = pathName.match(/^\/api\/admin\/users\/([^/]+)\/vip$/);
  if (method === 'PUT' && adminAssignVip) {
    const auth = adminAuth(req);
    const body = await readJson(req);
    return sendJson(res, 200, adminSetVip(db, decodeURIComponent(adminAssignVip[1]), body.tier, auth.sub, clientIp(req)), requestId);
  }

  if (method === 'GET' && pathName === '/api/admin/payment-settings') {
    adminAuth(req);
    return sendJson(res, 200, adminPaymentSettings(db), requestId);
  }
  const adminProvider = pathName.match(/^\/api\/admin\/payment-providers\/([^/]+)$/);
  if (method === 'PUT' && adminProvider) {
    const auth = adminAuth(req);
    const body = await readJson(req);
    return sendJson(res, 200, adminUpdatePaymentProvider(db, decodeURIComponent(adminProvider[1]), body, auth.sub, clientIp(req)), requestId);
  }
  const adminProviderTest = pathName.match(/^\/api\/admin\/payment-providers\/([^/]+)\/test$/);
  if (method === 'POST' && adminProviderTest) {
    const auth = adminAuth(req);
    return sendJson(res, 200, await adminTestPaymentProvider(db, decodeURIComponent(adminProviderTest[1]), auth.sub, clientIp(req)), requestId);
  }
  const adminCurrency = pathName.match(/^\/api\/admin\/currencies\/([^/]+)$/);
  if (method === 'PUT' && adminCurrency) {
    const auth = adminAuth(req);
    const body = await readJson(req);
    return sendJson(res, 200, adminUpdateCurrency(db, decodeURIComponent(adminCurrency[1]), body, auth.sub, clientIp(req)), requestId);
  }
  if (method === 'POST' && pathName === '/api/admin/fx/sync-tcmb') {
    const auth = adminAuth(req);
    return sendJson(res, 200, await syncTcmbRates(db, auth.sub, clientIp(req)), requestId);
  }
  if (method === 'POST' && pathName === '/api/admin/bank-accounts') {
    const auth = adminAuth(req);
    const body = await readJson(req);
    return sendJson(res, 201, adminCreateBankAccount(db, body, auth.sub, clientIp(req)), requestId);
  }
  const adminBank = pathName.match(/^\/api\/admin\/bank-accounts\/([^/]+)$/);
  if (method === 'PUT' && adminBank) {
    const auth = adminAuth(req);
    const body = await readJson(req);
    return sendJson(res, 200, adminUpdateBankAccount(db, decodeURIComponent(adminBank[1]), body, auth.sub, clientIp(req)), requestId);
  }
  const adminCrypto = pathName.match(/^\/api\/admin\/crypto-assets\/([^/]+)$/);
  if (method === 'PUT' && adminCrypto) {
    const auth = adminAuth(req);
    const body = await readJson(req);
    return sendJson(res, 200, adminUpdateCryptoAsset(db, decodeURIComponent(adminCrypto[1]), body, auth.sub, clientIp(req)), requestId);
  }
  const adminVipPrice = pathName.match(/^\/api\/admin\/vip-prices\/(\d+)\/([^/]+)$/);
  if (method === 'PUT' && adminVipPrice) {
    const auth = adminAuth(req);
    const body = await readJson(req);
    return sendJson(res, 200, adminUpdateVipPrice(db, adminVipPrice[1], decodeURIComponent(adminVipPrice[2]), body, auth.sub, clientIp(req)), requestId);
  }

  if (method === 'GET' && pathName === '/api/admin/site') {
    adminAuth(req);
    return sendJson(res, 200, adminSiteContent(db), requestId);
  }
  const adminSitePage = pathName.match(/^\/api\/admin\/site\/pages\/([^/]+)$/);
  if (method === 'PUT' && adminSitePage) {
    const auth = adminAuth(req);
    const body = await readJson(req);
    return sendJson(res, 200, adminUpdateSitePage(db, decodeURIComponent(adminSitePage[1]), body, auth.sub, clientIp(req)), requestId);
  }
  const adminSiteSection = pathName.match(/^\/api\/admin\/site\/sections\/([^/]+)$/);
  if (method === 'PUT' && adminSiteSection) {
    const auth = adminAuth(req);
    const body = await readJson(req);
    return sendJson(res, 200, adminUpdateSiteSection(db, decodeURIComponent(adminSiteSection[1]), body, auth.sub, clientIp(req)), requestId);
  }
  const adminAllocation = pathName.match(/^\/api\/admin\/tokenomics\/allocations\/([^/]+)$/);
  if (method === 'PUT' && adminAllocation) {
    const auth = adminAuth(req);
    const body = await readJson(req);
    return sendJson(res, 200, adminUpdateAllocation(db, decodeURIComponent(adminAllocation[1]), body, auth.sub, clientIp(req)), requestId);
  }
  const adminRoadmap = pathName.match(/^\/api\/admin\/roadmap\/([^/]+)$/);
  if (method === 'PUT' && adminRoadmap) {
    const auth = adminAuth(req);
    const body = await readJson(req);
    return sendJson(res, 200, adminUpdateRoadmap(db, decodeURIComponent(adminRoadmap[1]), body, auth.sub, clientIp(req)), requestId);
  }
  const adminChapter = pathName.match(/^\/api\/admin\/whitepaper\/chapters\/([^/]+)$/);
  if (method === 'PUT' && adminChapter) {
    const auth = adminAuth(req);
    const body = await readJson(req);
    return sendJson(res, 200, adminUpdateChapter(db, decodeURIComponent(adminChapter[1]), body, auth.sub, clientIp(req)), requestId);
  }
  if (method === 'PUT' && pathName === '/api/admin/platform-settings') {
    const auth = adminAuth(req);
    const body = await readJson(req);
    return sendJson(res, 200, updatePlatformSettings(db, body, auth.sub, clientIp(req)), requestId);
  }
  if (method === 'GET' && pathName === '/api/admin/presale') {
    adminAuth(req);
    return sendJson(res, 200, adminPresale(db), requestId);
  }
  const adminStage = pathName.match(/^\/api\/admin\/presale\/stages\/([^/]+)$/);
  if (method === 'PUT' && adminStage) {
    const auth = adminAuth(req);
    const body = await readJson(req);
    return sendJson(res, 200, adminUpdatePresaleStage(db, decodeURIComponent(adminStage[1]), body, auth.sub, clientIp(req)), requestId);
  }
  if (method === 'GET' && pathName === '/api/admin/payment-orders') {
    adminAuth(req);
    return sendJson(res, 200, adminPaymentOrders(db, url.searchParams.get('purpose') || '', url.searchParams.get('limit')), requestId);
  }
  const adminPaymentAction = pathName.match(/^\/api\/admin\/payment-orders\/([^/]+)\/(approve|reject|verify)$/);
  if (method === 'POST' && adminPaymentAction) {
    const auth = adminAuth(req);
    const body = await readJson(req);
    const id = decodeURIComponent(adminPaymentAction[1]);
    const action = adminPaymentAction[2];
    const result = action === 'approve'
      ? approvePaymentOrder(db, id, auth.sub, clientIp(req), { verification: 'admin' })
      : action === 'reject'
        ? rejectPaymentOrder(db, id, auth.sub, clientIp(req), body.reason)
        : await verifyEvmPaymentOrder(db, id, auth.sub, clientIp(req));
    return sendJson(res, 200, result, requestId);
  }
  if (method === 'GET' && pathName === '/api/admin/deposits') {
    adminAuth(req);
    return sendJson(res, 200, adminDeposits(db, url.searchParams.get('limit')), requestId);
  }
  const adminDepositAction = pathName.match(/^\/api\/admin\/deposits\/([^/]+)\/(approve|reject)$/);
  if (method === 'POST' && adminDepositAction) {
    const auth = adminAuth(req);
    const body = await readJson(req);
    const result = adminDepositAction[2] === 'approve'
      ? approveDeposit(db, decodeURIComponent(adminDepositAction[1]), auth.sub, clientIp(req))
      : rejectDeposit(db, decodeURIComponent(adminDepositAction[1]), auth.sub, clientIp(req), body.reason);
    return sendJson(res, 200, result, requestId);
  }
  if (method === 'GET' && pathName === '/api/admin/withdrawals') {
    adminAuth(req);
    return sendJson(res, 200, adminWithdrawals(db, url.searchParams.get('limit')), requestId);
  }
  const adminWithdrawalAction = pathName.match(/^\/api\/admin\/withdrawals\/([^/]+)\/(review|approve|broadcast|confirm|reject)$/);
  if (method === 'POST' && adminWithdrawalAction) {
    const auth = adminAuth(req);
    const body = await readJson(req);
    return sendJson(res, 200, updateWithdrawal(db, decodeURIComponent(adminWithdrawalAction[1]), adminWithdrawalAction[2], body, auth.sub, clientIp(req)), requestId);
  }
  if (method === 'GET' && pathName === '/api/admin/point-transfers') {
    adminAuth(req);
    return sendJson(res, 200, adminPointTransfers(db, url.searchParams.get('limit')), requestId);
  }
  if (method === 'GET' && pathName === '/api/admin/finance-summary') {
    adminAuth(req);
    return sendJson(res, 200, adminFinanceSummary(db), requestId);
  }
  const adminUserUpdate = pathName.match(/^\/api\/admin\/users\/([^/]+)$/);
  if (method === 'PUT' && adminUserUpdate) {
    const auth = adminAuth(req);
    const body = await readJson(req);
    return sendJson(res, 200, adminUpdateUser(db, decodeURIComponent(adminUserUpdate[1]), body, auth.sub, clientIp(req)), requestId);
  }

  if (method === 'GET' && pathName === '/api/admin/auth-providers') {
    adminAuth(req);
    return sendJson(res, 200, adminAuthProviders(db), requestId);
  }
  const authProviderRoute = pathName.match(/^\/api\/admin\/auth-providers\/([^/]+)$/);
  if (method === 'PUT' && authProviderRoute) {
    const auth = adminAuth(req);
    const body = await readJson(req);
    return sendJson(res, 200, adminUpdateAuthProvider(db, decodeURIComponent(authProviderRoute[1]), body, auth.sub, clientIp(req)), requestId);
  }
  if (method === 'GET' && pathName === '/api/admin/wallets') {
    adminAuth(req);
    return sendJson(res, 200, adminWallets(db, url.searchParams.get('limit')), requestId);
  }
  if (method === 'GET' && pathName === '/api/admin/presale-allocation') {
    adminAuth(req);
    return sendJson(res, 200, presaleAdminOverview(db), requestId);
  }
  if (method === 'PUT' && pathName === '/api/admin/presale-allocation/settings') {
    const auth = adminAuth(req);
    const body = await readJson(req);
    return sendJson(res, 200, updatePresaleCoreSettings(db, body, auth.sub, clientIp(req)), requestId);
  }
  if (method === 'POST' && pathName === '/api/admin/presale-allocation/snapshots') {
    const auth = adminAuth(req);
    return sendJson(res, 201, createPresaleSnapshot(db, auth.sub, clientIp(req)), requestId);
  }
  const snapshotAction = pathName.match(/^\/api\/admin\/presale-allocation\/snapshots\/([^/]+)\/(approve|finalize)$/);
  if (method === 'POST' && snapshotAction) {
    const auth = adminAuth(req);
    const body = await readJson(req);
    const id = decodeURIComponent(snapshotAction[1]);
    const result = snapshotAction[2] === 'approve'
      ? approvePresaleSnapshot(db, id, auth.sub, clientIp(req), body.note)
      : finalizePresaleAllocation(db, id, auth.sub, clientIp(req));
    return sendJson(res, 200, result, requestId);
  }
  if (method === 'GET' && pathName === '/api/admin/distributions') {
    adminAuth(req);
    return sendJson(res, 200, distributionOverview(db), requestId);
  }
  if (method === 'POST' && pathName === '/api/admin/distributions') {
    const auth = adminAuth(req);
    const body = await readJson(req);
    return sendJson(res, 201, createDistributionCampaign(db, body, auth.sub, clientIp(req)), requestId);
  }
  const distributionAction = pathName.match(/^\/api\/admin\/distributions\/([^/]+)\/(run|refresh-wallets)$/);
  if (method === 'POST' && distributionAction) {
    const auth = adminAuth(req);
    const id = decodeURIComponent(distributionAction[1]);
    const result = distributionAction[2] === 'run'
      ? await runDistributionCampaign(db, id, auth.sub, clientIp(req))
      : refreshCampaignWallets(db, id, auth.sub, clientIp(req));
    return sendJson(res, 200, result, requestId);
  }
  const distributionItemAction = pathName.match(/^\/api\/admin\/distribution-items\/([^/]+)\/(broadcast|confirm)$/);
  if (method === 'POST' && distributionItemAction) {
    const auth = adminAuth(req);
    const body = await readJson(req);
    const itemId = decodeURIComponent(distributionItemAction[1]);
    const result = distributionItemAction[2] === 'broadcast'
      ? broadcastManualDistributionItem(db, itemId, body, auth.sub, clientIp(req))
      : confirmDistributionItem(db, itemId, body, auth.sub, clientIp(req));
    return sendJson(res, 200, result, requestId);
  }
  if (method === 'GET' && pathName === '/api/admin/email-outbox') {
    adminAuth(req);
    return sendJson(res, 200, emailOutbox(db, url.searchParams.get('limit')), requestId);
  }
  if (method === 'POST' && pathName === '/api/admin/email-outbox/deliver') {
    adminAuth(req);
    return sendJson(res, 200, await deliverPendingEmails(db, 25), requestId);
  }
  const emailDeliver = pathName.match(/^\/api\/admin\/email-outbox\/([^/]+)\/deliver$/);
  if (method === 'POST' && emailDeliver) {
    adminAuth(req);
    return sendJson(res, 200, await deliverEmailById(db, decodeURIComponent(emailDeliver[1])), requestId);
  }
  if (method === 'GET' && pathName === '/api/admin/secrets') {
    adminAuth(req);
    return sendJson(res, 200, secretSummary(db), requestId);
  }
  if (method === 'PUT' && pathName === '/api/admin/secrets') {
    const auth = adminAuth(req);
    const body = await readJson(req);
    const allowed = new Set(['smtp_password','distribution_signer_secret']);
    if (!allowed.has(String(body.key || ''))) throw Object.assign(new Error('Bu secret anahtarı düzenlenemez.'), { code: 'SECRET_KEY_NOT_ALLOWED', status: 400 });
    setSecret(db, String(body.key), String(body.value || ''));
    return sendJson(res, 200, { key: body.key, masked: maskSecret(body.value), updatedBy: auth.sub }, requestId);
  }
  if (method === 'POST' && pathName === '/api/admin/launch/tick') {
    adminAuth(req);
    return sendJson(res, 200, await launchAutomationTick(db), requestId);
  }

  if (method === 'GET' && pathName === '/api/admin/ledger') {
    adminAuth(req);
    return sendJson(res, 200, adminLedger(db, url.searchParams.get('limit')), requestId);
  }
  if (method === 'GET' && pathName === '/api/admin/audit') {
    adminAuth(req);
    return sendJson(res, 200, adminAuditLogs(db, url.searchParams.get('limit')), requestId);
  }

  throw Object.assign(new Error('API endpoint bulunamadı.'), { code: 'NOT_FOUND', status: 404 });
}

const launchAutomationTimer = setInterval(() => {
  launchAutomationTick(db).catch((error) => {
    if (!config.isProduction) console.warn('[OnlyCoin launch automation]', error.message);
  });
}, 60_000);
launchAutomationTimer.unref?.();

const emailDeliveryTimer = setInterval(() => {
  if (!isInstalled()) return;
  deliverPendingEmails(db, 20).catch((error) => console.error('[OnlyCoin e-posta kuyruğu]', error.message));
}, 15_000);
emailDeliveryTimer.unref?.();

export const server = http.createServer(async (req, res) => {
  const requestId = String(req.headers['x-request-id'] || uuid()).slice(0, 80);
  const started = Date.now();
  try {
    const url = new URL(req.url || '/', config.appOrigin);
    if (url.pathname.startsWith('/api/')) {
      await handleApi(req, res, url, requestId);
    } else if (!isInstalled()) {
      if (url.pathname.startsWith('/install')) {
        if (!serveStatic(req, res, url, requestId)) sendJson(res, 404, { code: 'NOT_FOUND', message: 'Kurulum dosyası bulunamadı.', requestId }, requestId);
      } else {
        securityHeaders(res, requestId);
        const token = ensureInstallToken();
        res.writeHead(302, { Location: `/install/?token=${encodeURIComponent(token)}`, 'Cache-Control': 'no-store' });
        res.end();
      }
    } else if (url.pathname.startsWith('/install')) {
      securityHeaders(res, requestId);
      res.writeHead(302, { Location: '/admin/', 'Cache-Control': 'no-store' });
      res.end();
    } else if (!serveStatic(req, res, url, requestId)) {
      sendJson(res, 404, { code: 'NOT_FOUND', message: 'Sayfa bulunamadı.', requestId }, requestId);
    }
  } catch (error) {
    const status = Number(error.status) || 500;
    const code = error.code || 'INTERNAL_ERROR';
    if (!res.headersSent) {
      sendJson(res, status, {
        code,
        message: status >= 500 ? 'Beklenmeyen bir sunucu hatası oluştu.' : error.message,
        requestId,
        details: error.details || undefined
      }, requestId);
    } else {
      res.destroy();
    }
    if (status >= 500) console.error(`[${requestId}]`, error);
  } finally {
    const elapsed = Date.now() - started;
    if (config.environment !== 'test') console.log(`${req.method} ${req.url} ${res.statusCode || 0} ${elapsed}ms ${requestId}`);
  }
});

export function startServer({ host = config.host, port = config.port } = {}) {
  if (server.listening) return server;
  server.listen(port, host, () => {
    if (config.environment !== 'test') {
      console.log(`OnlyCoin running at http://${host}:${port}`);
      if (!isInstalled()) {
        const token = ensureInstallToken();
        console.log(`Installer: http://${host}:${port}/install/?token=${encodeURIComponent(token)}`);
        console.log(`Installation token: ${token}`);
      } else {
        console.log(`Game:  http://${host}:${port}/game/`);
        console.log(`Admin: http://${host}:${port}/admin/`);
      }
    }
  });
  return server;
}
