import crypto from 'node:crypto';
import { config } from './config.js';
import { audit, getSetting, getSettings, setSetting, withTransaction } from './db.js';
import { ensureUserAccounts, getBalance, getUserBalances, getUserTransactions, userAccountId } from './ledger.js';
import { clampInt, nowIso, nowSeconds, sanitizeText, uuid } from './utils.js';
import { estimatePresaleTokens, formatBaseUnits, markPresalePaymentFundedInTx, presaleConfig, presaleUserSummary } from './presale.js';
import { listUserWallets } from './wallets.js';

const TRANSFER_TOPIC = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef';

function fail(code, message, status = 400, details = undefined) {
  throw Object.assign(new Error(message), { code, status, details });
}

function parseJson(value, fallback = {}) {
  if (value == null) return fallback;
  try { return JSON.parse(value); } catch { return fallback; }
}

function normalizeHex(value, expectedBytes = null) {
  const text = String(value || '').trim().toLowerCase();
  if (!/^0x[0-9a-f]+$/.test(text)) return '';
  if (expectedBytes != null && text.length !== 2 + expectedBytes * 2) return '';
  return text;
}

function normalizeAddress(value) {
  return normalizeHex(value, 20);
}

function normalizeTxHash(value) {
  return normalizeHex(value, 32);
}

function runIdempotent(db, scope, key, fn) {
  if (!key || key.length < 8 || key.length > 160) fail('IDEMPOTENCY_KEY_REQUIRED', 'Geçerli bir Idempotency-Key gönderilmelidir.', 400);
  return withTransaction(db, () => {
    const existing = db.prepare('SELECT response_json FROM idempotency_responses WHERE scope=? AND key=?').get(scope, key);
    if (existing) return { ...parseJson(existing.response_json), duplicate: true };
    const result = fn();
    db.prepare('INSERT INTO idempotency_responses(scope,key,response_json,created_at) VALUES(?,?,?,?)')
      .run(scope, key, JSON.stringify(result), nowIso());
    return result;
  });
}

function postBalancedTransactionInTx(db, { type, ref, idempotencyKey, entries, metadata = {} }) {
  if (!Array.isArray(entries) || entries.length < 2) fail('LEDGER_ENTRIES_REQUIRED', 'Ledger işlemi için en az iki giriş gereklidir.', 500);
  const existing = db.prepare('SELECT id FROM ledger_transactions WHERE idempotency_key=?').get(idempotencyKey);
  if (existing) return { id: existing.id, duplicate: true };

  const accountIds = [...new Set(entries.map((entry) => entry.accountId))];
  const placeholders = accountIds.map(() => '?').join(',');
  const accounts = db.prepare(`SELECT * FROM ledger_accounts WHERE id IN (${placeholders})`).all(...accountIds);
  const byId = new Map(accounts.map((item) => [item.id, item]));
  if (accounts.length !== accountIds.length) fail('LEDGER_ACCOUNT_NOT_FOUND', 'Ledger hesabı bulunamadı.', 500);
  const assets = new Set(accounts.map((item) => item.asset));
  if (assets.size !== 1) fail('ASSET_MISMATCH', 'Ledger varlıkları eşleşmiyor.', 500);

  let total = 0;
  const outgoing = new Map();
  for (const entry of entries) {
    if (!Number.isSafeInteger(entry.amount) || entry.amount === 0) fail('INVALID_LEDGER_AMOUNT', 'Ledger miktarı geçersiz.', 500);
    total += entry.amount;
    if (entry.amount < 0) outgoing.set(entry.accountId, (outgoing.get(entry.accountId) || 0) + Math.abs(entry.amount));
  }
  if (total !== 0) fail('UNBALANCED_LEDGER_TRANSACTION', 'Ledger işlemi dengeli değil.', 500);
  for (const [accountId, amount] of outgoing) {
    const account = byId.get(accountId);
    if (!account.allow_negative && account.balance < amount) fail('INSUFFICIENT_BALANCE', 'Yetersiz bakiye.', 409);
  }

  const id = uuid();
  const createdAt = nowIso();
  db.prepare('INSERT INTO ledger_transactions(id,type,ref,idempotency_key,metadata,created_at) VALUES(?,?,?,?,?,?)')
    .run(id, type, ref, idempotencyKey, JSON.stringify(metadata), createdAt);
  const insert = db.prepare('INSERT INTO ledger_entries(id,transaction_id,account_id,amount,created_at) VALUES(?,?,?,?,?)');
  const update = db.prepare('UPDATE ledger_accounts SET balance=balance+? WHERE id=?');
  for (const entry of entries) {
    insert.run(uuid(), id, entry.accountId, entry.amount, createdAt);
    update.run(entry.amount, entry.accountId);
  }
  return { id, duplicate: false, asset: accounts[0].asset };
}

function activePresaleStage(db) {
  return db.prepare(`SELECT * FROM presale_stages WHERE enabled=1 AND status='active' ORDER BY sort_order,stage_no LIMIT 1`).get()
    || db.prepare(`SELECT * FROM presale_stages WHERE enabled=1 AND status IN ('upcoming','active') ORDER BY sort_order,stage_no LIMIT 1`).get()
    || null;
}

function sitePageRow(row) {
  return row ? { ...row, config: parseJson(row.config_json) } : null;
}

function siteSectionRow(row) {
  return row ? { ...row, content: parseJson(row.content_json) } : null;
}

function projectTokenWhole(db, baseValue) {
  try {
    const decimals = Math.max(0, Math.min(36, Number(getSetting(db, 'project_token_decimals', 18)) || 18));
    return Number(BigInt(String(baseValue || '0')) / (10n ** BigInt(decimals)));
  } catch { return 0; }
}

function publicPresaleTotals(db) {
  const paid = db.prepare(`SELECT p.user_id,p.final_token_base FROM presale_purchases p JOIN payment_orders o ON o.id=p.order_id WHERE o.status='paid' AND p.status NOT IN ('cancelled','refunded')`).all();
  const cfg = presaleConfig(db);
  let finalBase = 0n;
  for (const row of paid) { try { finalBase += BigInt(String(row.final_token_base || '0')); } catch {} }
  return {
    raised_usdt_micros: cfg.totalPaidUsdtMicros,
    sold_token_base: projectTokenWhole(db, finalBase),
    buyers: new Set(paid.map((row) => row.user_id)).size
  };
}

function pageStats(db) {
  const users = db.prepare(`SELECT COUNT(*) AS count FROM users WHERE status <> 'deleted'`).get().count;
  const activeUsers = db.prepare(`SELECT COUNT(*) AS count FROM users WHERE last_login_at >= datetime('now','-1 day')`).get().count;
  const presale = publicPresaleTotals(db);
  const holders = db.prepare(`SELECT COUNT(*) AS count FROM ledger_accounts WHERE asset='OC_TOKEN' AND user_id IS NOT NULL AND balance>0`).get().count;
  return { users, activeUsers, raisedUsdtMicros: presale.raised_usdt_micros, presaleTokens: presale.sold_token_base, holders };
}

export function getPublicSite(db) {
  const settings = getSettings(db);
  const pages = db.prepare('SELECT * FROM site_pages WHERE enabled=1 ORDER BY sort_order,slug').all().map(sitePageRow);
  const sections = db.prepare('SELECT * FROM site_sections WHERE enabled=1 ORDER BY page_slug,sort_order,section_key').all().map(siteSectionRow);
  const allocations = db.prepare('SELECT * FROM token_allocations WHERE enabled=1 ORDER BY sort_order').all();
  const roadmap = db.prepare('SELECT * FROM roadmap_phases WHERE enabled=1 ORDER BY sort_order,phase_no').all().map((row) => ({ ...row, items: parseJson(row.items_json, []) }));
  const chapters = db.prepare('SELECT * FROM whitepaper_chapters WHERE enabled=1 ORDER BY sort_order,chapter_no').all();
  const stages = db.prepare('SELECT * FROM presale_stages WHERE enabled=1 ORDER BY sort_order,stage_no').all();
  const activeStage = activePresaleStage(db);
  const presaleTotals = publicPresaleTotals(db);
  const presaleRuntime = presaleConfig(db);
  return {
    branding: {
      appName: settings.app_name,
      supportEmail: settings.support_email,
      launchAt: settings.launch_at,
      launchMessage: settings.launch_message,
      announcement: settings.announcement,
      tokenSymbol: settings.project_token_symbol,
      tokenContract: settings.project_token_contract,
      networkName: settings.usdt_network_name,
      explorerUrl: settings.blockchain_explorer_url,
      social: {
        telegram: settings.social_telegram_url,
        x: settings.social_x_url,
        discord: settings.social_discord_url,
        youtube: settings.social_youtube_url
      }
    },
    pages,
    sections,
    tokenomics: {
      totalSupply: projectTokenWhole(db, presaleRuntime.totalSupplyTokenBase),
      allocations
    },
    roadmap,
    whitepaper: {
      version: getSetting(db, 'whitepaper_version', '1.0'),
      updatedAt: getSetting(db, 'whitepaper_updated_at', 'June 2026'),
      totalPages: Number(getSetting(db, 'whitepaper_total_pages', 42)),
      downloadUrl: settings.whitepaper_download_url,
      chapters
    },
    presale: {
      enabled: Boolean(settings.presale_enabled),
      activeStage,
      stages,
      totals: presaleTotals,
      hardCapUsdtMicros: Number(settings.presale_hard_cap_usdt_micros),
      softCapUsdtMicros: Number(settings.presale_soft_cap_usdt_micros),
      minUsdtMicros: Number(settings.presale_min_usdt_micros),
      maxUsdtMicros: Number(settings.presale_max_usdt_micros),
      treasuryAddress: settings.treasury_deposit_address,
      usdtContract: settings.usdt_contract_address,
      network: settings.usdt_network_name,
      allocationPolicy: presaleRuntime.policy,
      poolTokenBase: presaleRuntime.poolTokenBase,
      referencePriceUsdtMicros: presaleRuntime.referencePriceUsdtMicros,
      launchPriceUsdtMicros: presaleRuntime.launchPriceUsdtMicros,
      finalized: presaleRuntime.locked
    },
    stats: pageStats(db),
    serverTime: nowIso()
  };
}

export function getUserIdentity(db, userId) {
  const user = db.prepare(`SELECT id,telegram_id,telegram_numeric_id,telegram_phone,phone_verified_at,username,display_name,avatar,login_id,referral_code,status,last_login_at,created_at FROM users WHERE id=?`).get(userId);
  if (!user) fail('USER_NOT_FOUND', 'Kullanıcı bulunamadı.', 404);
  return user;
}

export function getPlatformWallet(db, userId) {
  ensureUserAccounts(db, userId);
  const identity = getUserIdentity(db, userId);
  const balances = getUserBalances(db, userId);
  const addresses = db.prepare('SELECT * FROM withdrawal_addresses WHERE user_id=? ORDER BY created_at DESC').all(userId);
  const deposits = db.prepare('SELECT * FROM deposits WHERE user_id=? ORDER BY created_at DESC LIMIT 50').all(userId).map((row) => ({ ...row, metadata: parseJson(row.metadata_json) }));
  const withdrawals = db.prepare('SELECT w.*,a.address,a.label FROM withdrawals w JOIN withdrawal_addresses a ON a.id=w.address_id WHERE w.user_id=? ORDER BY w.requested_at DESC LIMIT 50').all(userId);
  const payments = db.prepare('SELECT * FROM payment_orders WHERE user_id=? ORDER BY created_at DESC LIMIT 50').all(userId).map((row) => ({ ...row, metadata: parseJson(row.metadata_json) }));
  const transfers = db.prepare(`
    SELECT t.*,su.username AS sender_username,su.display_name AS sender_name,ru.username AS recipient_username,ru.display_name AS recipient_name
    FROM point_transfers t
    JOIN users su ON su.id=t.sender_id
    JOIN users ru ON ru.id=t.recipient_id
    WHERE t.sender_id=? OR t.recipient_id=?
    ORDER BY t.created_at DESC LIMIT 50
  `).all(userId, userId);
  const allocations = db.prepare(`SELECT p.*,s.name AS stage_name,o.tx_hash,o.status AS payment_status FROM presale_purchases p JOIN presale_stages s ON s.id=p.stage_id JOIN payment_orders o ON o.id=p.order_id WHERE p.user_id=? ORDER BY p.created_at DESC`).all(userId);
  const presaleSummary = presaleUserSummary(db, userId);
  const managedWallets = listUserWallets(db, userId);
  const distributionDecimals = Math.max(0, Math.min(36, Number(getSetting(db, 'project_token_decimals', 18)) || 18));
  const distributions = db.prepare(`SELECT i.*,c.name AS campaign_name,c.mode AS campaign_mode,c.network,c.chain_id,c.token_contract,c.status AS campaign_status
    FROM distribution_items i JOIN distribution_campaigns c ON c.id=i.campaign_id
    WHERE i.user_id=? ORDER BY i.created_at DESC LIMIT 50`).all(userId).map((row) => ({
      ...row,
      amount_token_display: formatBaseUnits(row.amount_token_base || '0', distributionDecimals, 8),
      presale_token_display: formatBaseUnits(row.presale_token_base || '0', distributionDecimals, 8)
    }));
  return {
    identity,
    balances,
    addresses,
    deposits,
    withdrawals,
    paymentOrders: payments,
    pointTransfers: transfers,
    presalePurchases: allocations,
    presaleSummary,
    managedWallets,
    distributions,
    transactions: getUserTransactions(db, userId, 100),
    settings: {
      pointsTransferEnabled: Boolean(getSetting(db, 'points_transfer_enabled', true)),
      pointsTransferFeeBps: Number(getSetting(db, 'points_transfer_fee_bps', 100)),
      pointsTransferMin: Number(getSetting(db, 'points_transfer_min', 100)),
      pointsTransferMax: Number(getSetting(db, 'points_transfer_max', 1_000_000)),
      depositEnabled: Boolean(getSetting(db, 'deposit_enabled', false)),
      withdrawalEnabled: Boolean(getSetting(db, 'withdrawal_enabled', false)),
      treasuryAddress: getSetting(db, 'treasury_deposit_address', ''),
      network: getSetting(db, 'usdt_network_name', 'EVM'),
      tokenContract: getSetting(db, 'project_token_contract', ''),
      usdtContract: getSetting(db, 'usdt_contract_address', ''),
    }
  };
}

function resolveRecipient(db, identifier) {
  const value = sanitizeText(identifier, 80).trim();
  if (!value) fail('RECIPIENT_REQUIRED', 'Alıcı kimliği gereklidir.', 400);
  const normalized = value.replace(/^@/, '');
  const user = db.prepare(`
    SELECT * FROM users
    WHERE lower(username)=lower(?) OR lower(login_id)=lower(?) OR upper(referral_code)=upper(?) OR telegram_numeric_id=?
    LIMIT 1
  `).get(normalized, normalized, normalized, normalized);
  if (!user || user.status !== 'active') fail('RECIPIENT_NOT_FOUND', 'Aktif alıcı bulunamadı.', 404);
  return user;
}

export function lookupRecipient(db, userId, identifier) {
  const user = resolveRecipient(db, identifier);
  if (user.id === userId) fail('SELF_TRANSFER_NOT_ALLOWED', 'Kendinize transfer yapamazsınız.', 400);
  return { id: user.id, username: user.username, displayName: user.display_name, loginId: user.login_id };
}

export function transferPoints(db, userId, input, idempotencyKey) {
  return runIdempotent(db, `point-transfer:${userId}`, idempotencyKey, () => {
    if (!getSetting(db, 'points_transfer_enabled', true)) fail('POINT_TRANSFERS_DISABLED', 'Oyuncular arası point transferi kapalıdır.', 403);
    const amount = clampInt(input.amount, 1, 2_000_000_000, 0);
    const min = Number(getSetting(db, 'points_transfer_min', 100));
    const max = Number(getSetting(db, 'points_transfer_max', 1_000_000));
    if (amount < min || amount > max) fail('POINT_TRANSFER_LIMIT', `Transfer miktarı ${min} ile ${max} arasında olmalıdır.`, 400);
    const recipient = resolveRecipient(db, input.recipient);
    if (recipient.id === userId) fail('SELF_TRANSFER_NOT_ALLOWED', 'Kendinize transfer yapamazsınız.', 400);
    const dailyLimit = Number(getSetting(db, 'points_transfer_daily_limit', 5_000_000));
    const sentToday = db.prepare(`SELECT COALESCE(SUM(amount_points+fee_points),0) AS total FROM point_transfers WHERE sender_id=? AND status='completed' AND created_at>=datetime('now','start of day')`).get(userId).total;
    const feeBps = Number(getSetting(db, 'points_transfer_fee_bps', 100));
    const fee = Math.max(0, Math.floor(amount * feeBps / 10000));
    if (sentToday + amount + fee > dailyLimit) fail('POINT_TRANSFER_DAILY_LIMIT', 'Günlük point transfer limiti aşıldı.', 409);

    ensureUserAccounts(db, userId);
    ensureUserAccounts(db, recipient.id);
    const entries = [
      { accountId: userAccountId(userId, 'OC_POINTS'), amount: -(amount + fee) },
      { accountId: userAccountId(recipient.id, 'OC_POINTS'), amount }
    ];
    if (fee > 0) entries.push({ accountId: 'sys-points-transfer-fee', amount: fee });
    const transferId = uuid();
    const tx = postBalancedTransactionInTx(db, {
      type: 'point_transfer',
      ref: transferId,
      idempotencyKey: `ledger:point-transfer:${idempotencyKey}`,
      entries,
      metadata: { senderId: userId, recipientId: recipient.id, note: sanitizeText(input.note, 120), feeBps }
    });
    db.prepare(`INSERT INTO point_transfers(id,sender_id,recipient_id,amount_points,fee_points,note,idempotency_key,status,created_at) VALUES(?,?,?,?,?,?,?,'completed',?)`)
      .run(transferId, userId, recipient.id, amount, fee, sanitizeText(input.note, 120), idempotencyKey, nowIso());
    return {
      id: transferId,
      transactionId: tx.id,
      amount,
      fee,
      recipient: { username: recipient.username, displayName: recipient.display_name, loginId: recipient.login_id },
      balance: getBalance(db, userAccountId(userId, 'OC_POINTS'))
    };
  });
}

function orderReference(prefix) {
  return `${prefix}-${crypto.randomBytes(5).toString('hex').toUpperCase()}`;
}

function createPaymentOrderInTx(db, { userId, purpose, purposeRef, amountUsdtMicros, metadata = {} }) {
  const id = uuid();
  const createdAt = nowIso();
  const destination = sanitizeText(getSetting(db, 'treasury_deposit_address', ''), 120);
  const network = sanitizeText(getSetting(db, 'usdt_network_name', 'EVM'), 80);
  const ref = orderReference(purpose === 'vip' ? 'VIP' : purpose === 'presale' ? 'SALE' : 'DEP');
  const expiresAt = new Date(Date.now() + 24 * 3600 * 1000).toISOString();
  db.prepare(`INSERT INTO payment_orders(id,user_id,purpose,purpose_ref,network,asset,amount_usdt_micros,destination_address,payment_reference,status,metadata_json,expires_at,created_at,updated_at) VALUES(?,?,?,?,?,'USDT',?,?,?,'created',?,?,?,?)`)
    .run(id, userId, purpose, purposeRef, network, amountUsdtMicros, destination, ref, JSON.stringify(metadata), expiresAt, createdAt, createdAt);
  return db.prepare('SELECT * FROM payment_orders WHERE id=?').get(id);
}

export function createVipUsdtOrder(db, userId, tierInput, idempotencyKey) {
  return runIdempotent(db, `vip-usdt-order:${userId}`, idempotencyKey, () => {
    const tier = clampInt(tierInput, 1, 5, 0);
    const plan = db.prepare('SELECT * FROM vip_plans WHERE tier=? AND enabled=1').get(tier);
    if (!plan) fail('VIP_PLAN_NOT_FOUND', 'VIP planı bulunamadı.', 404);
    if (plan.price_usdt_micros <= 0) fail('VIP_USDT_PRICE_NOT_SET', 'VIP için USDT fiyatı tanımlanmamış.', 409);
    const order = createPaymentOrderInTx(db, {
      userId,
      purpose: 'vip',
      purposeRef: String(tier),
      amountUsdtMicros: plan.price_usdt_micros,
      metadata: { tier, durationDays: plan.duration_days, planName: plan.name }
    });
    return { order, plan };
  });
}

export function createPresaleOrder(db, userId, amountInput, stageIdInput, idempotencyKey) {
  return runIdempotent(db, `presale-order:${userId}`, idempotencyKey, () => {
    if (!getSetting(db, 'presale_enabled', true)) fail('PRESALE_DISABLED', 'Presale satışları kapalıdır.', 403);
    if (Boolean(getSetting(db, 'presale_settings_locked', false))) fail('PRESALE_FINALIZED', 'Presale kesinleştirildiği için yeni deposit kabul edilmiyor.', 409);
    const launchAt = new Date(getSetting(db, 'launch_at', 0)).getTime();
    if (getSetting(db, 'presale_require_launch', false) && Date.now() < launchAt) fail('PRESALE_LOCKED', 'Presale henüz açılmadı.', 403);
    const stage = stageIdInput
      ? db.prepare(`SELECT * FROM presale_stages WHERE id=? AND enabled=1 AND status IN ('active','upcoming')`).get(String(stageIdInput))
      : activePresaleStage(db);
    if (!stage) fail('PRESALE_STAGE_NOT_FOUND', 'Aktif satış aşaması bulunamadı.', 404);
    const amount = clampInt(amountInput, 1, Number.MAX_SAFE_INTEGER, 0);
    const globalMin = Number(getSetting(db, 'presale_min_usdt_micros', 10_000_000));
    const globalMax = Number(getSetting(db, 'presale_max_usdt_micros', 100_000_000_000));
    const min = Math.max(globalMin, stage.min_usdt_micros);
    const max = Math.min(globalMax, stage.max_usdt_micros);
    if (amount < min || amount > max) fail('PRESALE_AMOUNT_LIMIT', `Deposit tutarı ${min / 1e6} ile ${max / 1e6} USDT arasında olmalıdır.`, 400);
    const cfg = presaleConfig(db);
    const selectedPolicy = cfg.policy;
    const estimate = estimatePresaleTokens(db, {
      usdtMicros: amount,
      bonusBps: stage.bonus_bps,
      priceSnapshotUsdtMicros: stage.price_usdt_micros,
      allocationPolicy: selectedPolicy
    });
    const legacyWholeTokens = Math.max(1, Math.min(Number.MAX_SAFE_INTEGER, Math.floor(amount / Math.max(1, stage.price_usdt_micros))));
    const legacyBonus = Math.max(0, Math.min(Number.MAX_SAFE_INTEGER, Math.floor(legacyWholeTokens * stage.bonus_bps / 10000)));
    const order = createPaymentOrderInTx(db, {
      userId,
      purpose: 'presale',
      purposeRef: stage.id,
      amountUsdtMicros: amount,
      metadata: {
        stageId: stage.id,
        depositUsdtMicros: amount,
        pricingPolicy: selectedPolicy,
        quotedPriceUsdtMicros: estimate.priceUsdtMicros,
        estimatedTokenBase: estimate.estimatedTokenBase,
        bonusBps: stage.bonus_bps
      }
    });
    const purchaseId = uuid();
    db.prepare(`INSERT INTO presale_purchases(
      id,order_id,user_id,stage_id,usdt_micros,token_base,bonus_token_base,status,created_at,
      pricing_policy,price_snapshot_usdt_micros,estimated_token_base,weighted_usdt_micros,final_token_base,terms_version,refund_status
    ) VALUES(?,?,?,?,?,?,?,'pending',?,?,?,?,?,'0',?,'none')`)
      .run(
        purchaseId, order.id, userId, stage.id, amount, legacyWholeTokens, legacyBonus, nowIso(),
        selectedPolicy, estimate.priceUsdtMicros, estimate.estimatedTokenBase, estimate.weightedUsdtMicros,
        String(getSetting(db, 'presale_terms_version', '1.0'))
      );
    return {
      order,
      purchase: db.prepare('SELECT * FROM presale_purchases WHERE id=?').get(purchaseId),
      stage,
      estimate: {
        depositUsdtMicros: amount,
        estimatedTokenBase: estimate.estimatedTokenBase,
        policy: selectedPolicy,
        priceUsdtMicros: estimate.priceUsdtMicros,
        decimals: cfg.decimals
      }
    };
  });
}

export function submitPaymentTransaction(db, userId, orderId, txHashInput) {
  const txHash = normalizeTxHash(txHashInput);
  if (!txHash) fail('INVALID_TX_HASH', 'Geçerli bir işlem hash’i girin.', 400);
  return withTransaction(db, () => {
    const order = db.prepare('SELECT * FROM payment_orders WHERE id=? AND user_id=?').get(orderId, userId);
    if (!order) fail('PAYMENT_ORDER_NOT_FOUND', 'Ödeme emri bulunamadı.', 404);
    if (!['created','submitted','verifying'].includes(order.status)) fail('PAYMENT_ORDER_NOT_EDITABLE', 'Bu ödeme emri güncellenemez.', 409);
    const duplicate = db.prepare('SELECT id FROM payment_orders WHERE tx_hash=? AND id<>?').get(txHash, order.id);
    if (duplicate) fail('TX_HASH_ALREADY_USED', 'Bu işlem hash’i daha önce kullanılmış.', 409);
    db.prepare(`UPDATE payment_orders SET tx_hash=?,status='submitted',submitted_at=?,updated_at=? WHERE id=?`).run(txHash, nowIso(), nowIso(), order.id);
    return db.prepare('SELECT * FROM payment_orders WHERE id=?').get(order.id);
  });
}

async function rpcCall(method, params) {
  if (!config.evmRpcUrl) fail('RPC_NOT_CONFIGURED', 'EVM_RPC_URL yapılandırılmamış.', 503);
  const response = await fetch(config.evmRpcUrl, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ jsonrpc: '2.0', id: crypto.randomUUID(), method, params }),
    signal: AbortSignal.timeout(12_000)
  });
  if (!response.ok) fail('RPC_HTTP_ERROR', 'Blockchain RPC yanıt vermedi.', 502);
  const data = await response.json();
  if (data.error) fail('RPC_RESPONSE_ERROR', 'Blockchain RPC işlemi doğrulanamadı.', 502, data.error);
  return data.result;
}

export async function verifyEvmPaymentOrder(db, orderId, actor = 'system', ip = '') {
  const order = db.prepare('SELECT * FROM payment_orders WHERE id=?').get(orderId);
  if (!order) fail('PAYMENT_ORDER_NOT_FOUND', 'Ödeme emri bulunamadı.', 404);
  if (!order.tx_hash) fail('PAYMENT_TX_REQUIRED', 'İşlem hash’i gönderilmemiş.', 409);
  const receipt = await rpcCall('eth_getTransactionReceipt', [order.tx_hash]);
  if (!receipt) fail('TX_NOT_MINED', 'İşlem henüz blok zincirinde onaylanmadı.', 409);
  if (receipt.status !== '0x1') fail('TX_FAILED', 'Blockchain işlemi başarısız.', 409);
  const contract = normalizeAddress(getSetting(db, 'usdt_contract_address', ''));
  const destination = normalizeAddress(order.destination_address || getSetting(db, 'treasury_deposit_address', ''));
  if (!contract || !destination) fail('CONTRACT_SETTINGS_REQUIRED', 'USDT kontratı veya hazine adresi tanımlı değil.', 409);
  let matched = null;
  for (const log of receipt.logs || []) {
    if (String(log.address || '').toLowerCase() !== contract) continue;
    if (String(log.topics?.[0] || '').toLowerCase() !== TRANSFER_TOPIC) continue;
    const to = `0x${String(log.topics?.[2] || '').slice(-40)}`.toLowerCase();
    if (to !== destination) continue;
    const amount = Number(BigInt(log.data || '0x0'));
    if (amount >= order.amount_usdt_micros) {
      matched = { amount, from: `0x${String(log.topics?.[1] || '').slice(-40)}`.toLowerCase(), blockNumber: receipt.blockNumber };
      break;
    }
  }
  if (!matched) fail('USDT_TRANSFER_NOT_FOUND', 'Beklenen USDT transferi işlem içinde bulunamadı.', 409);
  return approvePaymentOrder(db, orderId, actor, ip, { verification: 'evm_rpc', receipt: matched });
}

function activateVipInTx(db, userId, tier, durationDays) {
  const current = db.prepare('SELECT vip_tier,vip_expires_at FROM user_game_state WHERE user_id=?').get(userId);
  if (!current) fail('USER_NOT_FOUND', 'Kullanıcı bulunamadı.', 404);
  const base = current.vip_expires_at && current.vip_expires_at > nowSeconds() ? current.vip_expires_at : nowSeconds();
  const expiresAt = base + Math.max(1, durationDays) * 86400;
  db.prepare('UPDATE user_game_state SET vip_tier=?,vip_expires_at=?,updated_at=? WHERE user_id=?')
    .run(tier, expiresAt, nowIso(), userId);
  return { tier, expiresAt };
}

function applyPaidOrderInTx(db, order, actor) {
  if (order.purpose === 'vip') {
    const plan = db.prepare('SELECT * FROM vip_plans WHERE tier=?').get(Number(order.purpose_ref));
    if (!plan) fail('VIP_PLAN_NOT_FOUND', 'VIP planı bulunamadı.', 404);
    postBalancedTransactionInTx(db, {
      type: 'vip_usdt_payment',
      ref: order.id,
      idempotencyKey: `ledger:payment:${order.id}`,
      entries: [
        { accountId: 'sys-usdt-issuance', amount: -order.amount_usdt_micros },
        { accountId: 'sys-usdt-vip-revenue', amount: order.amount_usdt_micros }
      ],
      metadata: { orderId: order.id, tier: plan.tier, actor }
    });
    return { vip: activateVipInTx(db, order.user_id, plan.tier, plan.duration_days) };
  }
  if (order.purpose === 'presale') {
    return markPresalePaymentFundedInTx(db, order, actor);
  }
  if (order.purpose === 'deposit') {
    ensureUserAccounts(db, order.user_id);
    postBalancedTransactionInTx(db, {
      type: 'usdt_deposit',
      ref: order.id,
      idempotencyKey: `ledger:payment-deposit:${order.id}`,
      entries: [
        { accountId: 'sys-usdt-issuance', amount: -order.amount_usdt_micros },
        { accountId: userAccountId(order.user_id, 'USDT'), amount: order.amount_usdt_micros }
      ],
      metadata: { orderId: order.id, actor }
    });
    return { depositBalance: getBalance(db, userAccountId(order.user_id, 'USDT')) };
  }
  fail('UNSUPPORTED_PAYMENT_PURPOSE', 'Ödeme amacı desteklenmiyor.', 500);
}

export function approvePaymentOrder(db, orderId, actor = 'admin', ip = '', evidence = {}) {
  return withTransaction(db, () => {
    const order = db.prepare('SELECT * FROM payment_orders WHERE id=?').get(orderId);
    if (!order) fail('PAYMENT_ORDER_NOT_FOUND', 'Ödeme emri bulunamadı.', 404);
    if (order.status === 'paid') return { order, duplicate: true };
    if (!['created','submitted','verifying'].includes(order.status)) fail('PAYMENT_ORDER_NOT_APPROVABLE', 'Bu ödeme emri onaylanamaz.', 409);
    const applied = applyPaidOrderInTx(db, order, actor);
    db.prepare(`UPDATE payment_orders SET status='paid',paid_at=?,updated_at=?,metadata_json=? WHERE id=?`)
      .run(nowIso(), nowIso(), JSON.stringify({ ...parseJson(order.metadata_json), ...evidence }), order.id);
    const after = db.prepare('SELECT * FROM payment_orders WHERE id=?').get(order.id);
    audit(db, { actorType: 'admin', actorId: actor, action: 'payment.approve', targetType: 'payment_order', targetId: order.id, before: order, after, ip });
    return { order: after, ...applied };
  });
}

export function rejectPaymentOrder(db, orderId, actor = 'admin', ip = '', reason = '') {
  return withTransaction(db, () => {
    const order = db.prepare('SELECT * FROM payment_orders WHERE id=?').get(orderId);
    if (!order) fail('PAYMENT_ORDER_NOT_FOUND', 'Ödeme emri bulunamadı.', 404);
    if (!['created','submitted','verifying'].includes(order.status)) fail('PAYMENT_ORDER_NOT_REJECTABLE', 'Bu ödeme emri reddedilemez.', 409);
    db.prepare(`UPDATE payment_orders SET status='rejected',metadata_json=?,updated_at=? WHERE id=?`)
      .run(JSON.stringify({ ...parseJson(order.metadata_json), rejectionReason: sanitizeText(reason, 250) }), nowIso(), order.id);
    if (order.purpose === 'presale') db.prepare(`UPDATE presale_purchases SET status='cancelled' WHERE order_id=? AND status='pending'`).run(order.id);
    const after = db.prepare('SELECT * FROM payment_orders WHERE id=?').get(order.id);
    audit(db, { actorType: 'admin', actorId: actor, action: 'payment.reject', targetType: 'payment_order', targetId: order.id, before: order, after, ip });
    return after;
  });
}

export function createDepositSubmission(db, userId, input, idempotencyKey) {
  return runIdempotent(db, `deposit:${userId}`, idempotencyKey, () => {
    if (!getSetting(db, 'deposit_enabled', true)) fail('DEPOSITS_DISABLED', 'Deposit işlemleri kapalıdır.', 403);
    const asset = ['USDT','OC_TOKEN'].includes(input.asset) ? input.asset : 'USDT';
    const txHash = normalizeTxHash(input.txHash);
    if (!txHash) fail('INVALID_TX_HASH', 'Geçerli bir işlem hash’i girin.', 400);
    const amount = clampInt(input.amountBase, 1, Number.MAX_SAFE_INTEGER, 0);
    if (!amount) fail('INVALID_AMOUNT', 'Deposit miktarı geçersiz.', 400);
    const existing = db.prepare('SELECT id FROM deposits WHERE tx_hash=?').get(txHash);
    if (existing) fail('TX_HASH_ALREADY_USED', 'Bu işlem hash’i daha önce kullanılmış.', 409);
    const id = uuid();
    const now = nowIso();
    db.prepare(`INSERT INTO deposits(id,user_id,asset,network,amount_base,tx_hash,from_address,to_address,status,metadata_json,created_at,updated_at) VALUES(?,?,?,?,?,?,?,?,'submitted','{}',?,?)`)
      .run(id, userId, asset, sanitizeText(input.network || getSetting(db, 'usdt_network_name', 'EVM'), 80), amount, txHash, sanitizeText(input.fromAddress, 120), sanitizeText(getSetting(db, 'treasury_deposit_address', ''), 120), now, now);
    return db.prepare('SELECT * FROM deposits WHERE id=?').get(id);
  });
}

export function approveDeposit(db, depositId, actor = 'admin', ip = '') {
  return withTransaction(db, () => {
    const deposit = db.prepare('SELECT * FROM deposits WHERE id=?').get(depositId);
    if (!deposit) fail('DEPOSIT_NOT_FOUND', 'Deposit kaydı bulunamadı.', 404);
    if (deposit.status === 'credited') return { deposit, duplicate: true };
    if (!['submitted','verifying','confirmed'].includes(deposit.status)) fail('DEPOSIT_NOT_APPROVABLE', 'Deposit onaylanamaz.', 409);
    ensureUserAccounts(db, deposit.user_id);
    const issuance = deposit.asset === 'USDT' ? 'sys-usdt-issuance' : 'sys-token-issuance';
    postBalancedTransactionInTx(db, {
      type: 'external_deposit',
      ref: deposit.id,
      idempotencyKey: `ledger:deposit:${deposit.id}`,
      entries: [
        { accountId: issuance, amount: -deposit.amount_base },
        { accountId: userAccountId(deposit.user_id, deposit.asset), amount: deposit.amount_base }
      ],
      metadata: { txHash: deposit.tx_hash, network: deposit.network, actor }
    });
    db.prepare(`UPDATE deposits SET status='credited',confirmations=MAX(confirmations,1),credited_at=?,updated_at=? WHERE id=?`).run(nowIso(), nowIso(), deposit.id);
    const after = db.prepare('SELECT * FROM deposits WHERE id=?').get(deposit.id);
    audit(db, { actorType: 'admin', actorId: actor, action: 'deposit.credit', targetType: 'deposit', targetId: deposit.id, before: deposit, after, ip });
    return { deposit: after, balance: getBalance(db, userAccountId(deposit.user_id, deposit.asset)) };
  });
}

export function rejectDeposit(db, depositId, actor = 'admin', ip = '', reason = '') {
  return withTransaction(db, () => {
    const before = db.prepare('SELECT * FROM deposits WHERE id=?').get(depositId);
    if (!before) fail('DEPOSIT_NOT_FOUND', 'Deposit kaydı bulunamadı.', 404);
    if (before.status === 'credited') fail('DEPOSIT_ALREADY_CREDITED', 'Kredilendirilmiş deposit reddedilemez.', 409);
    db.prepare(`UPDATE deposits SET status='rejected',metadata_json=?,updated_at=? WHERE id=?`)
      .run(JSON.stringify({ ...parseJson(before.metadata_json), rejectionReason: sanitizeText(reason, 250) }), nowIso(), before.id);
    const after = db.prepare('SELECT * FROM deposits WHERE id=?').get(before.id);
    audit(db, { actorType: 'admin', actorId: actor, action: 'deposit.reject', targetType: 'deposit', targetId: before.id, before, after, ip });
    return after;
  });
}

export function addWithdrawalAddress(db, userId, input) {
  const asset = ['USDT','OC_TOKEN'].includes(input.asset) ? input.asset : 'OC_TOKEN';
  const address = sanitizeText(input.address, 160).trim();
  if (!address || address.length < 8) fail('INVALID_ADDRESS', 'Geçerli bir cüzdan adresi girin.', 400);
  const id = uuid();
  try {
    db.prepare(`INSERT INTO withdrawal_addresses(id,user_id,asset,network,address,label,status,created_at) VALUES(?,?,?,?,?,?,'active',?)`)
      .run(id, userId, asset, sanitizeText(input.network || getSetting(db, 'usdt_network_name', 'EVM'), 80), address, sanitizeText(input.label || 'Cüzdanım', 60), nowIso());
  } catch (error) {
    if (String(error.message).includes('UNIQUE')) fail('ADDRESS_ALREADY_EXISTS', 'Bu adres zaten kayıtlı.', 409);
    throw error;
  }
  return db.prepare('SELECT * FROM withdrawal_addresses WHERE id=?').get(id);
}

export function requestWithdrawal(db, userId, input, idempotencyKey) {
  return runIdempotent(db, `withdrawal:${userId}`, idempotencyKey, () => {
    if (!getSetting(db, 'withdrawal_enabled', true)) fail('WITHDRAWALS_DISABLED', 'Withdraw işlemleri kapalıdır.', 403);
    const address = db.prepare(`SELECT * FROM withdrawal_addresses WHERE id=? AND user_id=? AND status='active'`).get(String(input.addressId || ''), userId);
    if (!address) fail('WITHDRAWAL_ADDRESS_NOT_FOUND', 'Aktif çekim adresi bulunamadı.', 404);
    const amount = clampInt(input.amountBase, 1, Number.MAX_SAFE_INTEGER, 0);
    const asset = address.asset;
    const min = asset === 'USDT' ? Number(getSetting(db, 'withdrawal_min_usdt_micros', 10_000_000)) : Number(getSetting(db, 'withdrawal_min_token', 1000));
    const fee = asset === 'USDT' ? Number(getSetting(db, 'withdrawal_fee_usdt_micros', 1_000_000)) : Number(getSetting(db, 'withdrawal_fee_token', 10));
    const dailyLimit = asset === 'USDT' ? Number(getSetting(db, 'withdrawal_daily_limit_usdt_micros', 1_000_000_000)) : Number(getSetting(db, 'withdrawal_daily_limit_token', 10_000_000));
    if (amount < min) fail('WITHDRAWAL_MINIMUM', `Minimum çekim miktarı ${min} base unit.`, 400);
    const used = db.prepare(`SELECT COALESCE(SUM(amount_base+fee_base),0) AS total FROM withdrawals WHERE user_id=? AND asset=? AND status NOT IN ('rejected','cancelled','failed') AND requested_at>=datetime('now','start of day')`).get(userId, asset).total;
    if (used + amount + fee > dailyLimit) fail('WITHDRAWAL_DAILY_LIMIT', 'Günlük çekim limiti aşıldı.', 409);
    ensureUserAccounts(db, userId);
    const clearing = asset === 'USDT' ? 'sys-usdt-withdrawal-clearing' : 'sys-token-withdrawal-clearing';
    const feeAccount = asset === 'USDT' ? 'sys-usdt-withdrawal-fee' : 'sys-token-withdrawal-fee';
    const entries = [
      { accountId: userAccountId(userId, asset), amount: -(amount + fee) },
      { accountId: clearing, amount }
    ];
    if (fee > 0) entries.push({ accountId: feeAccount, amount: fee });
    const id = uuid();
    postBalancedTransactionInTx(db, {
      type: 'withdrawal_request',
      ref: id,
      idempotencyKey: `ledger:withdrawal:${idempotencyKey}`,
      entries,
      metadata: { addressId: address.id, address: address.address, network: address.network }
    });
    const now = nowIso();
    db.prepare(`INSERT INTO withdrawals(id,user_id,address_id,asset,network,amount_base,fee_base,status,note,requested_at,updated_at) VALUES(?,?,?,?,?,?,?,'requested',?,?,?)`)
      .run(id, userId, address.id, asset, address.network, amount, fee, sanitizeText(input.note, 120), now, now);
    return db.prepare('SELECT * FROM withdrawals WHERE id=?').get(id);
  });
}

export function updateWithdrawal(db, withdrawalId, action, values, actor = 'admin', ip = '') {
  return withTransaction(db, () => {
    const before = db.prepare('SELECT * FROM withdrawals WHERE id=?').get(withdrawalId);
    if (!before) fail('WITHDRAWAL_NOT_FOUND', 'Çekim kaydı bulunamadı.', 404);
    let nextStatus = before.status;
    let txHash = before.tx_hash;
    if (action === 'approve' && ['requested','review'].includes(before.status)) nextStatus = 'approved';
    else if (action === 'review' && before.status === 'requested') nextStatus = 'review';
    else if (action === 'broadcast' && before.status === 'approved') {
      txHash = normalizeTxHash(values.txHash) || sanitizeText(values.txHash, 100);
      if (!txHash) fail('TX_HASH_REQUIRED', 'Broadcast işlem hash’i gereklidir.', 400);
      nextStatus = 'broadcast';
    } else if (action === 'confirm' && ['approved','broadcast'].includes(before.status)) nextStatus = 'confirmed';
    else if (action === 'reject' && ['requested','review','approved'].includes(before.status)) {
      const clearing = before.asset === 'USDT' ? 'sys-usdt-withdrawal-clearing' : 'sys-token-withdrawal-clearing';
      const feeAccount = before.asset === 'USDT' ? 'sys-usdt-withdrawal-fee' : 'sys-token-withdrawal-fee';
      const entries = [
        { accountId: clearing, amount: -before.amount_base },
        { accountId: userAccountId(before.user_id, before.asset), amount: before.amount_base + before.fee_base }
      ];
      if (before.fee_base > 0) entries.push({ accountId: feeAccount, amount: -before.fee_base });
      postBalancedTransactionInTx(db, {
        type: 'withdrawal_reversal',
        ref: before.id,
        idempotencyKey: `ledger:withdrawal-reversal:${before.id}`,
        entries,
        metadata: { reason: sanitizeText(values.reason, 250), actor }
      });
      nextStatus = 'rejected';
    } else fail('INVALID_WITHDRAWAL_TRANSITION', 'Geçersiz çekim durumu geçişi.', 409);
    const reviewedAt = ['review','approved','rejected'].includes(nextStatus) ? nowIso() : before.reviewed_at;
    const completedAt = ['confirmed','rejected'].includes(nextStatus) ? nowIso() : before.completed_at;
    db.prepare(`UPDATE withdrawals SET status=?,tx_hash=?,note=?,reviewed_at=?,completed_at=?,updated_at=? WHERE id=?`)
      .run(nextStatus, txHash, sanitizeText(values.note || values.reason || before.note, 250), reviewedAt, completedAt, nowIso(), before.id);
    const after = db.prepare('SELECT * FROM withdrawals WHERE id=?').get(before.id);
    audit(db, { actorType: 'admin', actorId: actor, action: `withdrawal.${action}`, targetType: 'withdrawal', targetId: before.id, before, after, ip });
    return after;
  });
}

export function adminSiteContent(db) {
  return {
    pages: db.prepare('SELECT * FROM site_pages ORDER BY sort_order,slug').all().map(sitePageRow),
    sections: db.prepare('SELECT * FROM site_sections ORDER BY page_slug,sort_order,section_key').all().map(siteSectionRow),
    allocations: db.prepare('SELECT * FROM token_allocations ORDER BY sort_order').all(),
    roadmap: db.prepare('SELECT * FROM roadmap_phases ORDER BY sort_order,phase_no').all().map((row) => ({ ...row, items: parseJson(row.items_json, []) })),
    chapters: db.prepare('SELECT * FROM whitepaper_chapters ORDER BY sort_order,chapter_no').all()
  };
}

export function adminUpdateSitePage(db, slug, values, actor, ip) {
  return withTransaction(db, () => {
    const before = db.prepare('SELECT * FROM site_pages WHERE slug=?').get(slug);
    if (!before) fail('SITE_PAGE_NOT_FOUND', 'Site sayfası bulunamadı.', 404);
    const next = {
      nav_label: sanitizeText(values.nav_label ?? before.nav_label, 40),
      title: sanitizeText(values.title ?? before.title, 100),
      hero_title: sanitizeText(values.hero_title ?? before.hero_title, 100),
      hero_accent: sanitizeText(values.hero_accent ?? before.hero_accent, 100),
      hero_subtitle: sanitizeText(values.hero_subtitle ?? before.hero_subtitle, 200),
      hero_description: sanitizeText(values.hero_description ?? before.hero_description, 1000),
      primary_cta_label: sanitizeText(values.primary_cta_label ?? before.primary_cta_label, 60),
      primary_cta_url: sanitizeText(values.primary_cta_url ?? before.primary_cta_url, 300),
      secondary_cta_label: sanitizeText(values.secondary_cta_label ?? before.secondary_cta_label, 60),
      secondary_cta_url: sanitizeText(values.secondary_cta_url ?? before.secondary_cta_url, 300),
      meta_title: sanitizeText(values.meta_title ?? before.meta_title, 160),
      meta_description: sanitizeText(values.meta_description ?? before.meta_description, 320),
      config_json: JSON.stringify(values.config ?? parseJson(before.config_json)),
      enabled: values.enabled === false || values.enabled === 0 ? 0 : 1,
      sort_order: clampInt(values.sort_order ?? before.sort_order, 0, 1000, before.sort_order)
    };
    db.prepare(`UPDATE site_pages SET nav_label=?,title=?,hero_title=?,hero_accent=?,hero_subtitle=?,hero_description=?,primary_cta_label=?,primary_cta_url=?,secondary_cta_label=?,secondary_cta_url=?,meta_title=?,meta_description=?,config_json=?,enabled=?,sort_order=?,updated_at=? WHERE slug=?`)
      .run(next.nav_label,next.title,next.hero_title,next.hero_accent,next.hero_subtitle,next.hero_description,next.primary_cta_label,next.primary_cta_url,next.secondary_cta_label,next.secondary_cta_url,next.meta_title,next.meta_description,next.config_json,next.enabled,next.sort_order,nowIso(),slug);
    const after = db.prepare('SELECT * FROM site_pages WHERE slug=?').get(slug);
    audit(db, { actorType:'admin',actorId:actor,action:'site.page.update',targetType:'site_page',targetId:slug,before,after,ip });
    return sitePageRow(after);
  });
}

export function adminUpdateSiteSection(db, id, values, actor, ip) {
  return withTransaction(db, () => {
    const before = db.prepare('SELECT * FROM site_sections WHERE id=?').get(id);
    if (!before) fail('SITE_SECTION_NOT_FOUND', 'Site bölümü bulunamadı.', 404);
    const next = {
      title: sanitizeText(values.title ?? before.title, 140),
      subtitle: sanitizeText(values.subtitle ?? before.subtitle, 300),
      content_json: JSON.stringify(values.content ?? parseJson(before.content_json)),
      enabled: values.enabled === false || values.enabled === 0 ? 0 : 1,
      sort_order: clampInt(values.sort_order ?? before.sort_order, 0, 1000, before.sort_order)
    };
    db.prepare('UPDATE site_sections SET title=?,subtitle=?,content_json=?,enabled=?,sort_order=?,updated_at=? WHERE id=?')
      .run(next.title,next.subtitle,next.content_json,next.enabled,next.sort_order,nowIso(),id);
    const after = db.prepare('SELECT * FROM site_sections WHERE id=?').get(id);
    audit(db, { actorType:'admin',actorId:actor,action:'site.section.update',targetType:'site_section',targetId:id,before,after,ip });
    return siteSectionRow(after);
  });
}

export function adminUpdateAllocation(db, id, values, actor, ip) {
  return withTransaction(db, () => {
    const before = db.prepare('SELECT * FROM token_allocations WHERE id=?').get(id);
    if (!before) fail('ALLOCATION_NOT_FOUND', 'Token tahsisatı bulunamadı.', 404);
    const next = {
      label: sanitizeText(values.label ?? before.label, 80),
      percentage_bps: clampInt(values.percentage_bps ?? before.percentage_bps, 0, 10000, before.percentage_bps),
      token_amount: clampInt(values.token_amount ?? before.token_amount, 0, Number.MAX_SAFE_INTEGER, before.token_amount),
      description: sanitizeText(values.description ?? before.description, 400),
      vesting: sanitizeText(values.vesting ?? before.vesting, 240),
      color: /^#[0-9a-f]{6}$/i.test(String(values.color || '')) ? values.color : before.color,
      sort_order: clampInt(values.sort_order ?? before.sort_order, 0, 1000, before.sort_order),
      enabled: values.enabled === false || values.enabled === 0 ? 0 : 1
    };
    db.prepare('UPDATE token_allocations SET label=?,percentage_bps=?,token_amount=?,description=?,vesting=?,color=?,sort_order=?,enabled=?,updated_at=? WHERE id=?')
      .run(next.label,next.percentage_bps,next.token_amount,next.description,next.vesting,next.color,next.sort_order,next.enabled,nowIso(),id);
    const after = db.prepare('SELECT * FROM token_allocations WHERE id=?').get(id);
    audit(db, { actorType:'admin',actorId:actor,action:'tokenomics.update',targetType:'token_allocation',targetId:id,before,after,ip });
    return after;
  });
}

export function adminUpdateRoadmap(db, id, values, actor, ip) {
  return withTransaction(db, () => {
    const before = db.prepare('SELECT * FROM roadmap_phases WHERE id=?').get(id);
    if (!before) fail('ROADMAP_PHASE_NOT_FOUND', 'Roadmap aşaması bulunamadı.', 404);
    const status = ['completed','current','upcoming','paused'].includes(values.status) ? values.status : before.status;
    const items = Array.isArray(values.items) ? values.items.map((x) => sanitizeText(x, 180)).filter(Boolean).slice(0, 20) : parseJson(before.items_json, []);
    db.prepare(`UPDATE roadmap_phases SET phase_no=?,title=?,subtitle=?,status=?,completion_percent=?,start_at=?,end_at=?,items_json=?,icon=?,enabled=?,sort_order=?,updated_at=? WHERE id=?`)
      .run(clampInt(values.phase_no ?? before.phase_no,1,100,before.phase_no),sanitizeText(values.title ?? before.title,100),sanitizeText(values.subtitle ?? before.subtitle,200),status,clampInt(values.completion_percent ?? before.completion_percent,0,100,before.completion_percent),values.start_at ?? before.start_at,values.end_at ?? before.end_at,JSON.stringify(items),sanitizeText(values.icon ?? before.icon,40),values.enabled === false || values.enabled === 0 ? 0 : 1,clampInt(values.sort_order ?? before.sort_order,0,1000,before.sort_order),nowIso(),id);
    const after = db.prepare('SELECT * FROM roadmap_phases WHERE id=?').get(id);
    audit(db, { actorType:'admin',actorId:actor,action:'roadmap.update',targetType:'roadmap_phase',targetId:id,before,after,ip });
    return { ...after, items: parseJson(after.items_json, []) };
  });
}

export function adminUpdateChapter(db, id, values, actor, ip) {
  return withTransaction(db, () => {
    const before = db.prepare('SELECT * FROM whitepaper_chapters WHERE id=?').get(id);
    if (!before) fail('WHITEPAPER_CHAPTER_NOT_FOUND', 'Whitepaper bölümü bulunamadı.', 404);
    db.prepare(`UPDATE whitepaper_chapters SET chapter_no=?,title=?,summary=?,content_html=?,icon=?,enabled=?,sort_order=?,updated_at=? WHERE id=?`)
      .run(clampInt(values.chapter_no ?? before.chapter_no,1,100,before.chapter_no),sanitizeText(values.title ?? before.title,120),sanitizeText(values.summary ?? before.summary,400),sanitizeText(values.content_html ?? before.content_html,20000),sanitizeText(values.icon ?? before.icon,40),values.enabled === false || values.enabled === 0 ? 0 : 1,clampInt(values.sort_order ?? before.sort_order,0,1000,before.sort_order),nowIso(),id);
    const after = db.prepare('SELECT * FROM whitepaper_chapters WHERE id=?').get(id);
    audit(db, { actorType:'admin',actorId:actor,action:'whitepaper.update',targetType:'whitepaper_chapter',targetId:id,before,after,ip });
    return after;
  });
}

export function adminPresale(db) {
  return {
    stages: db.prepare('SELECT * FROM presale_stages ORDER BY sort_order,stage_no').all(),
    orders: adminPaymentOrders(db, 'presale', 300),
    purchases: db.prepare(`SELECT p.*,u.username,u.display_name,s.name AS stage_name,o.status AS payment_status,o.tx_hash FROM presale_purchases p JOIN users u ON u.id=p.user_id JOIN presale_stages s ON s.id=p.stage_id JOIN payment_orders o ON o.id=p.order_id ORDER BY p.created_at DESC LIMIT 300`).all(),
    totals: db.prepare(`SELECT COALESCE(SUM(usdt_micros),0) AS usdt_micros,COALESCE(SUM(token_base+bonus_token_base),0) AS tokens,COUNT(*) AS purchases FROM presale_purchases WHERE status='allocated'`).get()
  };
}

export function adminUpdatePresaleStage(db, id, values, actor, ip) {
  return withTransaction(db, () => {
    const before = db.prepare('SELECT * FROM presale_stages WHERE id=?').get(id);
    if (!before) fail('PRESALE_STAGE_NOT_FOUND', 'Presale aşaması bulunamadı.', 404);
    const status = ['draft','upcoming','active','completed','paused'].includes(values.status) ? values.status : before.status;
    if (status === 'active') db.prepare(`UPDATE presale_stages SET status='upcoming',updated_at=? WHERE status='active' AND id<>?`).run(nowIso(), id);
    db.prepare(`UPDATE presale_stages SET name=?,stage_no=?,price_usdt_micros=?,bonus_bps=?,allocation_token_base=?,min_usdt_micros=?,max_usdt_micros=?,starts_at=?,ends_at=?,status=?,enabled=?,sort_order=?,updated_at=? WHERE id=?`)
      .run(sanitizeText(values.name ?? before.name,80),clampInt(values.stage_no ?? before.stage_no,1,100,before.stage_no),clampInt(values.price_usdt_micros ?? before.price_usdt_micros,1,Number.MAX_SAFE_INTEGER,before.price_usdt_micros),clampInt(values.bonus_bps ?? before.bonus_bps,0,100000,before.bonus_bps),clampInt(values.allocation_token_base ?? before.allocation_token_base,0,Number.MAX_SAFE_INTEGER,before.allocation_token_base),clampInt(values.min_usdt_micros ?? before.min_usdt_micros,1,Number.MAX_SAFE_INTEGER,before.min_usdt_micros),clampInt(values.max_usdt_micros ?? before.max_usdt_micros,1,Number.MAX_SAFE_INTEGER,before.max_usdt_micros),values.starts_at ?? before.starts_at,values.ends_at ?? before.ends_at,status,values.enabled === false || values.enabled === 0 ? 0 : 1,clampInt(values.sort_order ?? before.sort_order,0,1000,before.sort_order),nowIso(),id);
    const after = db.prepare('SELECT * FROM presale_stages WHERE id=?').get(id);
    audit(db,{actorType:'admin',actorId:actor,action:'presale.stage.update',targetType:'presale_stage',targetId:id,before,after,ip});
    return after;
  });
}

export function adminPaymentOrders(db, purpose = '', limit = 300) {
  const safeLimit = Math.max(1, Math.min(1000, Number(limit) || 300));
  const where = purpose ? 'WHERE o.purpose=?' : '';
  const args = purpose ? [purpose, safeLimit] : [safeLimit];
  return db.prepare(`SELECT o.*,u.username,u.display_name,u.login_id FROM payment_orders o JOIN users u ON u.id=o.user_id ${where} ORDER BY o.created_at DESC LIMIT ?`).all(...args).map((row) => ({ ...row, metadata: parseJson(row.metadata_json) }));
}

export function adminDeposits(db, limit = 300) {
  const safe = Math.max(1, Math.min(1000, Number(limit) || 300));
  return db.prepare(`SELECT d.*,u.username,u.display_name,u.login_id FROM deposits d JOIN users u ON u.id=d.user_id ORDER BY d.created_at DESC LIMIT ?`).all(safe).map((row) => ({ ...row, metadata: parseJson(row.metadata_json) }));
}

export function adminWithdrawals(db, limit = 300) {
  const safe = Math.max(1, Math.min(1000, Number(limit) || 300));
  return db.prepare(`SELECT w.*,u.username,u.display_name,u.login_id,a.address,a.label FROM withdrawals w JOIN users u ON u.id=w.user_id JOIN withdrawal_addresses a ON a.id=w.address_id ORDER BY w.requested_at DESC LIMIT ?`).all(safe);
}

export function adminPointTransfers(db, limit = 300) {
  const safe = Math.max(1, Math.min(1000, Number(limit) || 300));
  return db.prepare(`SELECT t.*,su.username AS sender_username,su.login_id AS sender_login,ru.username AS recipient_username,ru.login_id AS recipient_login FROM point_transfers t JOIN users su ON su.id=t.sender_id JOIN users ru ON ru.id=t.recipient_id ORDER BY t.created_at DESC LIMIT ?`).all(safe);
}

export function adminUpdateUser(db, userId, values, actor, ip) {
  return withTransaction(db, () => {
    const before = db.prepare('SELECT * FROM users WHERE id=?').get(userId);
    if (!before) fail('USER_NOT_FOUND', 'Kullanıcı bulunamadı.', 404);
    const status = ['active','review','blocked','deleted'].includes(values.status) ? values.status : before.status;
    db.prepare('UPDATE users SET status=?,telegram_phone=?,phone_verified_at=?,notes=?,updated_at=? WHERE id=?')
      .run(status,sanitizeText(values.telegram_phone ?? before.telegram_phone,40),values.phone_verified_at ?? before.phone_verified_at,sanitizeText(values.notes ?? before.notes,1000),nowIso(),userId);
    const after = db.prepare('SELECT * FROM users WHERE id=?').get(userId);
    audit(db,{actorType:'admin',actorId:actor,action:'user.update',targetType:'user',targetId:userId,before,after,ip});
    return after;
  });
}

export function linkTelegramContact(db, telegramUserId, phoneNumber, updateId, names = {}) {
  const tgId = String(telegramUserId || '');
  const phone = sanitizeText(phoneNumber, 40);
  if (!tgId || !phone) return { linked: false };
  return withTransaction(db, () => {
    const existingUpdate = db.prepare('SELECT * FROM telegram_contacts WHERE update_id=?').get(String(updateId));
    if (existingUpdate) return { linked: Boolean(existingUpdate.user_id), duplicate: true, userId: existingUpdate.user_id };
    const user = db.prepare(`SELECT * FROM users WHERE telegram_numeric_id=? OR telegram_id=? LIMIT 1`).get(tgId, `telegram:${tgId}`);
    const id = uuid();
    db.prepare(`INSERT INTO telegram_contacts(id,telegram_user_id,user_id,phone_number,first_name,last_name,verified,update_id,created_at) VALUES(?,?,?,?,?,?,?, ?,?)`)
      .run(id,tgId,user?.id || null,phone,sanitizeText(names.firstName,80),sanitizeText(names.lastName,80),user?1:0,String(updateId),nowIso());
    if (user) db.prepare('UPDATE users SET telegram_phone=?,phone_verified_at=?,updated_at=? WHERE id=?').run(phone,nowIso(),nowIso(),user.id);
    return { linked: Boolean(user), userId: user?.id || null, phone };
  });
}

export function adminFinanceSummary(db) {
  return {
    payments: {
      pending: db.prepare(`SELECT COUNT(*) AS count,COALESCE(SUM(amount_usdt_micros),0) AS amount FROM payment_orders WHERE status IN ('created','submitted','verifying')`).get(),
      paid: db.prepare(`SELECT COUNT(*) AS count,COALESCE(SUM(amount_usdt_micros),0) AS amount FROM payment_orders WHERE status='paid'`).get()
    },
    deposits: db.prepare(`SELECT status,COUNT(*) AS count,COALESCE(SUM(amount_base),0) AS amount FROM deposits GROUP BY status`).all(),
    withdrawals: db.prepare(`SELECT status,COUNT(*) AS count,COALESCE(SUM(amount_base),0) AS amount FROM withdrawals GROUP BY status`).all(),
    transfers: db.prepare(`SELECT COUNT(*) AS count,COALESCE(SUM(amount_points),0) AS amount,COALESCE(SUM(fee_points),0) AS fees FROM point_transfers WHERE status='completed'`).get()
  };
}

export function updatePlatformSettings(db, changes, actor, ip) {
  const editable = new Set([
    'site_enabled','telegram_login_enabled','telegram_bot_username','telegram_phone_collection_enabled',
    'project_token_symbol','project_token_decimals','project_token_contract','usdt_network','usdt_network_name','usdt_chain_id','usdt_contract_address','usdt_decimals','treasury_deposit_address','blockchain_explorer_url','blockchain_rpc_mode',
    'presale_enabled','presale_require_launch','presale_min_usdt_micros','presale_max_usdt_micros','presale_hard_cap_usdt_micros','presale_soft_cap_usdt_micros','presale_total_token_base_units',
    'deposit_enabled','withdrawal_enabled','withdrawal_min_token','withdrawal_fee_token','withdrawal_daily_limit_token','withdrawal_min_usdt_micros','withdrawal_fee_usdt_micros','withdrawal_daily_limit_usdt_micros',
    'points_transfer_enabled','points_transfer_min','points_transfer_max','points_transfer_daily_limit','points_transfer_fee_bps',
    'whitepaper_download_url','whitepaper_version','whitepaper_updated_at','whitepaper_total_pages','contact_address','social_telegram_url','social_x_url','social_discord_url','social_youtube_url',
    'base_currency','display_currency','settlement_currency','enabled_fiat_currencies','fiat_payments_enabled','bank_transfer_enabled',
    'crypto_funding_enabled','crypto_payment_for_goods_enabled','crypto_payment_region_mode','country_code',
    'fx_rate_source','fx_auto_update_enabled','fx_update_interval_minutes','fx_rate_margin_bps',
    'payment_order_expiry_minutes','payment_callback_base_url','payment_success_url','payment_failure_url','payment_max_installment',
    'payment_3d_secure_required','payment_test_mode','payment_provider_timeout_seconds','payment_webhook_replay_window_seconds',
    'kyc_required_for_crypto','kyc_required_for_withdrawal','aml_screening_enabled','sanctions_screening_enabled','min_user_age','allowed_country_codes','blocked_country_codes',
    'smtp_enabled','smtp_host','smtp_port','smtp_secure','smtp_username','smtp_from_email','smtp_from_name','sms_provider','sms_sender','push_enabled','firebase_project_id',
    'maintenance_message','support_phone','company_legal_name','company_tax_office','company_tax_number','company_mersis_number','company_address','privacy_contact_email',
    'default_language','enabled_languages','timezone','seo_default_title','seo_default_description','analytics_enabled','analytics_provider','analytics_measurement_id',
    'backup_enabled','backup_retention_days','audit_retention_days','session_duration_minutes','admin_session_duration_minutes','rate_limit_profile',
    'email_registration_enabled','email_verification_required','social_login_enabled',
    'wallet_testnet_enabled','wallet_mainnet_enabled','wallet_testnet_network_name','wallet_testnet_chain_id','wallet_mainnet_network_name','wallet_mainnet_chain_id','wallet_password_min_length','wallet_recovery_email_enabled','wallet_email_confirmation_required','wallet_address_cooldown_hours','wallet_recovery_link_ttl_minutes',
    'smtp_starttls','smtp_ehlo_name','distribution_enabled','distribution_auto_start','distribution_mode','distribution_signer_mode','distribution_signer_url','distribution_batch_size','distribution_confirmations_required'
  ]);
  return withTransaction(db, () => {
    const before = getSettings(db);
    for (const [key,value] of Object.entries(changes || {})) if (editable.has(key)) setSetting(db,key,value);
    const after = getSettings(db);
    audit(db,{actorType:'admin',actorId:actor,action:'platform.settings.update',targetType:'settings',targetId:'platform',before,after,ip});
    return after;
  });
}
