import crypto from 'node:crypto';
import { config } from './config.js';
import { audit, getSetting, getSettings, withTransaction } from './db.js';
import { approvePaymentOrder, createPresaleOrder, createVipUsdtOrder, rejectPaymentOrder } from './platform.js';
import { clampInt, nowIso, sanitizeText, safeEqual, uuid } from './utils.js';

const SECRET_PLACEHOLDER = '••••••••';
const TCMB_URL = 'https://www.tcmb.gov.tr/kurlar/today.xml';

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

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

function encryptionKey() {
  return crypto.createHash('sha256').update(config.authSecret).digest();
}

function encryptSecrets(value) {
  const payload = JSON.stringify(value || {});
  if (payload === '{}') return '';
  const iv = crypto.randomBytes(12);
  const cipher = crypto.createCipheriv('aes-256-gcm', encryptionKey(), iv);
  const encrypted = Buffer.concat([cipher.update(payload, 'utf8'), cipher.final()]);
  const tag = cipher.getAuthTag();
  return `v1.${iv.toString('base64url')}.${tag.toString('base64url')}.${encrypted.toString('base64url')}`;
}

function decryptSecrets(value) {
  if (!value) return {};
  try {
    const [version, ivText, tagText, encryptedText] = String(value).split('.');
    if (version !== 'v1') return {};
    const decipher = crypto.createDecipheriv('aes-256-gcm', encryptionKey(), Buffer.from(ivText, 'base64url'));
    decipher.setAuthTag(Buffer.from(tagText, 'base64url'));
    const plain = Buffer.concat([decipher.update(Buffer.from(encryptedText, 'base64url')), decipher.final()]).toString('utf8');
    return parseJson(plain, {});
  } catch {
    return {};
  }
}

function maskSecret(value) {
  const text = String(value || '');
  if (!text) return '';
  return `${SECRET_PLACEHOLDER}${text.length > 4 ? text.slice(-4) : ''}`;
}

function providerRow(row, includeSecrets = false) {
  if (!row) return null;
  const secrets = decryptSecrets(row.secrets_enc);
  return {
    ...row,
    enabled: Boolean(row.enabled),
    test_mode: Boolean(row.test_mode),
    config: parseJson(row.config_json),
    capabilities: parseJson(row.capabilities_json),
    secrets: Object.fromEntries(Object.entries(secrets).map(([key, value]) => [key, includeSecrets ? value : maskSecret(value)])),
    secretKeys: Object.keys(secrets),
    secrets_enc: undefined,
    config_json: undefined,
    capabilities_json: undefined
  };
}

function cryptoRow(row) {
  return row ? {
    ...row,
    enabled: Boolean(row.enabled),
    purchase_enabled: Boolean(row.purchase_enabled),
    deposit_enabled: Boolean(row.deposit_enabled),
    withdrawal_enabled: Boolean(row.withdrawal_enabled),
    metadata: parseJson(row.metadata_json),
    metadata_json: undefined
  } : null;
}

function bankRow(row) {
  return row ? { ...row, enabled: Boolean(row.enabled) } : null;
}

function normalizeCode(value, max = 16) {
  return sanitizeText(value, max).toUpperCase().replace(/[^A-Z0-9_-]/g, '');
}

function normalizeProviderId(value) {
  return sanitizeText(value, 50).toLowerCase().replace(/[^a-z0-9_-]/g, '');
}

function toBigInt(value, fallback = 0n) {
  try { return BigInt(value); } catch { return fallback; }
}

function pow10(decimals) {
  return 10n ** BigInt(decimals);
}

function rateRow(db, code) {
  return db.prepare(`SELECT c.*,r.rate_to_try_e8,r.source,r.is_demo,r.fetched_at,r.updated_at AS rate_updated_at
    FROM fiat_currencies c LEFT JOIN fx_rates r ON r.currency_code=c.code WHERE c.code=?`).get(code);
}

function convertUsdtMicrosToFiatMinor(db, amountUsdtMicros, currencyCode) {
  const code = normalizeCode(currencyCode, 8);
  const target = rateRow(db, code);
  const usd = rateRow(db, 'USD');
  if (!target || !target.enabled || !target.rate_to_try_e8) fail('CURRENCY_NOT_AVAILABLE', 'Seçilen para birimi aktif değil.', 409);
  if (!usd?.rate_to_try_e8) fail('USD_RATE_NOT_CONFIGURED', 'USD döviz kuru tanımlı değil.', 409);
  const usdE8 = toBigInt(amountUsdtMicros) * 100n;
  const targetE8 = usdE8 * toBigInt(usd.rate_to_try_e8) / toBigInt(target.rate_to_try_e8);
  const divisor = 100000000n / pow10(target.decimals);
  const amountMinor = Number((targetE8 + divisor / 2n) / divisor);
  if (!Number.isSafeInteger(amountMinor) || amountMinor <= 0) fail('PAYMENT_AMOUNT_OUT_OF_RANGE', 'Ödeme tutarı güvenli sınırların dışında.', 400);
  return {
    amountMinor,
    decimals: target.decimals,
    rateToTryE8: target.rate_to_try_e8,
    source: target.source,
    isDemo: Boolean(target.is_demo)
  };
}

function convertUsdtMicrosToCryptoBase(amountUsdtMicros, asset) {
  const price = toBigInt(asset.manual_price_usd_e8);
  if (price <= 0n) fail('CRYPTO_PRICE_NOT_CONFIGURED', `${asset.symbol} USD fiyatı tanımlı değil.`, 409);
  const usdE8 = toBigInt(amountUsdtMicros) * 100n;
  const amountBase = usdE8 * pow10(asset.decimals) / price;
  if (amountBase <= 0n) fail('CRYPTO_AMOUNT_TOO_LOW', 'Kripto ödeme miktarı çok düşük.', 400);
  if (amountBase > BigInt(Number.MAX_SAFE_INTEGER)) {
    return { amountBase: amountBase.toString(), amountBaseBigInt: amountBase };
  }
  return { amountBase: Number(amountBase), amountBaseBigInt: amountBase };
}

function formatBaseAmount(base, decimals) {
  const n = toBigInt(base);
  const unit = pow10(decimals);
  const whole = n / unit;
  const fraction = (n % unit).toString().padStart(decimals, '0').replace(/0+$/, '');
  return fraction ? `${whole}.${fraction}` : String(whole);
}

function mergeSecrets(existing, updates) {
  const result = { ...existing };
  for (const [key, raw] of Object.entries(updates || {})) {
    const value = String(raw ?? '').trim();
    if (!value || value.startsWith(SECRET_PLACEHOLDER) || /^\*{4,}/.test(value)) continue;
    if (value === '__CLEAR__') delete result[key];
    else result[key] = value;
  }
  return result;
}

function providerCredentialsValid(provider, secrets) {
  if (provider.adapter === 'paytr_link') return Boolean(secrets.merchantId && secrets.merchantKey && secrets.merchantSalt);
  if (provider.adapter === 'iyzico_link') return Boolean(secrets.apiKey && secrets.secretKey && provider.config.checkoutUrl);
  if (provider.adapter === 'generic_hosted') return Boolean(provider.config.checkoutUrl);
  if (provider.adapter === 'manual_bank' || provider.adapter === 'crypto_direct') return true;
  return false;
}

export function getPublicPaymentCatalog(db, countryCode = '') {
  const settings = getSettings(db);
  const country = normalizeCode(countryCode || settings.country_code || 'TR', 3);
  const activeBankCount = settings.bank_transfer_enabled
    ? Number(db.prepare('SELECT COUNT(*) AS c FROM bank_accounts WHERE enabled=1').get()?.c || 0)
    : 0;
  const activeCryptoCount = settings.crypto_funding_enabled
    ? Number(db.prepare('SELECT COUNT(*) AS c FROM crypto_assets WHERE enabled=1 AND purchase_enabled=1 AND length(trim(deposit_address))>0').get()?.c || 0)
    : 0;
  const providers = db.prepare(`SELECT * FROM payment_providers WHERE enabled=1 ORDER BY sort_order,name`).all()
    .map((row) => providerRow(row, false))
    .filter((provider) => {
      if (provider.adapter === 'manual_bank') return activeBankCount > 0;
      if (provider.adapter === 'crypto_direct') return activeCryptoCount > 0;
      if (provider.category === 'crypto') return Boolean(settings.crypto_funding_enabled) && ['configured', 'verified'].includes(provider.status);
      if (provider.category === 'card') return Boolean(settings.fiat_payments_enabled) && ['configured', 'verified'].includes(provider.status);
      return false;
    })
    .map((provider) => ({
      id: provider.id,
      name: provider.name,
      category: provider.category,
      adapter: provider.adapter,
      description: provider.description,
      testMode: provider.test_mode,
      status: provider.status,
      currencies: db.prepare(`SELECT pc.*,c.name,c.symbol,c.decimals FROM payment_provider_currencies pc LEFT JOIN fiat_currencies c ON c.code=pc.currency_code WHERE pc.provider_id=? AND pc.enabled=1 AND COALESCE(c.enabled,1)=1 ORDER BY COALESCE(c.sort_order,999),pc.currency_code`).all(provider.id)
    }));
  const currencies = db.prepare(`SELECT c.*,r.rate_to_try_e8,r.source,r.is_demo,r.fetched_at FROM fiat_currencies c LEFT JOIN fx_rates r ON r.currency_code=c.code WHERE c.enabled=1 ORDER BY c.sort_order,c.code`).all();
  const banks = settings.bank_transfer_enabled
    ? db.prepare(`SELECT * FROM bank_accounts WHERE enabled=1 ORDER BY sort_order,currency_code`).all().map(bankRow)
    : [];
  let cryptoAssets = [];
  if (settings.crypto_funding_enabled) {
    cryptoAssets = db.prepare(`SELECT * FROM crypto_assets WHERE enabled=1 AND purchase_enabled=1 ORDER BY sort_order,name`).all().map(cryptoRow);
  }
  return {
    country,
    baseCurrency: settings.base_currency,
    displayCurrency: settings.display_currency,
    fiatPaymentsEnabled: Boolean(settings.fiat_payments_enabled),
    cryptoFundingEnabled: Boolean(settings.crypto_funding_enabled),
    cryptoPaymentMode: settings.crypto_payment_region_mode,
    cryptoForGoodsEnabled: Boolean(settings.crypto_payment_for_goods_enabled),
    currencies,
    providers,
    bankAccounts: banks,
    cryptoAssets,
    vipPrices: db.prepare(`SELECT vp.tier,vp.currency_code,vp.amount_minor,vp.enabled,v.name AS vip_name,c.symbol,c.decimals
      FROM vip_plan_prices vp JOIN vip_plans v ON v.tier=vp.tier
      LEFT JOIN fiat_currencies c ON c.code=vp.currency_code
      WHERE vp.enabled=1 ORDER BY vp.tier,vp.currency_code`).all(),
    compliance: {
      kycRequiredForCrypto: Boolean(settings.kyc_required_for_crypto),
      minUserAge: Number(settings.min_user_age || 18),
      allowedCountries: settings.allowed_country_codes || [],
      blockedCountries: settings.blocked_country_codes || []
    },
    serverTime: nowIso()
  };
}

export function adminPaymentSettings(db) {
  return {
    settings: getSettings(db),
    currencies: db.prepare(`SELECT c.*,r.rate_to_try_e8,r.source,r.is_demo,r.fetched_at,r.updated_at AS rate_updated_at FROM fiat_currencies c LEFT JOIN fx_rates r ON r.currency_code=c.code ORDER BY c.sort_order,c.code`).all(),
    providers: db.prepare('SELECT * FROM payment_providers ORDER BY sort_order,name').all().map((row) => ({
      ...providerRow(row, false),
      currencies: db.prepare(`SELECT pc.*,c.name,c.symbol,c.decimals FROM payment_provider_currencies pc LEFT JOIN fiat_currencies c ON c.code=pc.currency_code WHERE pc.provider_id=? ORDER BY COALESCE(c.sort_order,999),pc.currency_code`).all(row.id)
    })),
    bankAccounts: db.prepare('SELECT * FROM bank_accounts ORDER BY sort_order,currency_code').all().map(bankRow),
    cryptoAssets: db.prepare('SELECT * FROM crypto_assets ORDER BY sort_order,name').all().map(cryptoRow),
    vipPrices: db.prepare('SELECT vp.*,v.name AS vip_name FROM vip_plan_prices vp JOIN vip_plans v ON v.tier=vp.tier ORDER BY vp.tier,vp.currency_code').all(),
    callbackUrls: {
      paytr: `${getSetting(db, 'payment_callback_base_url', '') || config.appOrigin}/api/webhooks/paytr`,
      iyzico: `${getSetting(db, 'payment_callback_base_url', '') || config.appOrigin}/api/webhooks/generic/iyzico`,
      generic: `${getSetting(db, 'payment_callback_base_url', '') || config.appOrigin}/api/webhooks/generic/{providerId}`
    }
  };
}

export function adminUpdatePaymentProvider(db, providerIdInput, values, actor, ip) {
  const providerId = normalizeProviderId(providerIdInput);
  return withTransaction(db, () => {
    const before = db.prepare('SELECT * FROM payment_providers WHERE id=?').get(providerId);
    if (!before) fail('PAYMENT_PROVIDER_NOT_FOUND', 'Ödeme sağlayıcısı bulunamadı.', 404);
    const currentSecrets = decryptSecrets(before.secrets_enc);
    const nextSecrets = mergeSecrets(currentSecrets, values.secrets);
    const configValue = { ...parseJson(before.config_json), ...(values.config || {}) };
    const enabled = values.enabled == null ? before.enabled : (values.enabled ? 1 : 0);
    const testMode = values.test_mode == null ? before.test_mode : (values.test_mode ? 1 : 0);
    const status = enabled ? (providerCredentialsValid({ ...before, config: configValue }, nextSecrets) ? 'configured' : 'not_configured') : 'disabled';
    db.prepare(`UPDATE payment_providers SET name=?,description=?,enabled=?,test_mode=?,sort_order=?,config_json=?,secrets_enc=?,status=?,last_error='',updated_at=? WHERE id=?`)
      .run(
        sanitizeText(values.name ?? before.name, 80),
        sanitizeText(values.description ?? before.description, 500),
        enabled,
        testMode,
        clampInt(values.sort_order ?? before.sort_order, 0, 1000, before.sort_order),
        JSON.stringify(configValue),
        encryptSecrets(nextSecrets),
        status,
        nowIso(),
        providerId
      );
    if (Array.isArray(values.currencies)) {
      const upsert = db.prepare(`INSERT INTO payment_provider_currencies(provider_id,currency_code,enabled,min_amount_minor,max_amount_minor,fee_bps,fixed_fee_minor) VALUES(?,?,?,?,?,?,?)
        ON CONFLICT(provider_id,currency_code) DO UPDATE SET enabled=excluded.enabled,min_amount_minor=excluded.min_amount_minor,max_amount_minor=excluded.max_amount_minor,fee_bps=excluded.fee_bps,fixed_fee_minor=excluded.fixed_fee_minor`);
      for (const item of values.currencies) {
        const code = normalizeCode(item.currency_code || item.code, 8);
        if (!code) continue;
        upsert.run(providerId, code, item.enabled === false ? 0 : 1,
          clampInt(item.min_amount_minor, 0, Number.MAX_SAFE_INTEGER, 100),
          clampInt(item.max_amount_minor, 0, Number.MAX_SAFE_INTEGER, 100000000000),
          clampInt(item.fee_bps, 0, 10000, 0),
          clampInt(item.fixed_fee_minor, 0, Number.MAX_SAFE_INTEGER, 0));
      }
    }
    const after = db.prepare('SELECT * FROM payment_providers WHERE id=?').get(providerId);
    audit(db, { actorType: 'admin', actorId: actor, action: 'payment.provider.update', targetType: 'payment_provider', targetId: providerId, before: providerRow(before), after: providerRow(after), ip });
    return { ...providerRow(after), currencies: db.prepare('SELECT * FROM payment_provider_currencies WHERE provider_id=? ORDER BY currency_code').all(providerId) };
  });
}

export function adminUpdateCurrency(db, codeInput, values, actor, ip) {
  const code = normalizeCode(codeInput, 8);
  return withTransaction(db, () => {
    const before = db.prepare('SELECT * FROM fiat_currencies WHERE code=?').get(code);
    if (!before) fail('CURRENCY_NOT_FOUND', 'Para birimi bulunamadı.', 404);
    if (values.is_base) db.prepare('UPDATE fiat_currencies SET is_base=0').run();
    db.prepare(`UPDATE fiat_currencies SET name=?,symbol=?,decimals=?,enabled=?,is_base=?,sort_order=?,updated_at=? WHERE code=?`)
      .run(sanitizeText(values.name ?? before.name, 80), sanitizeText(values.symbol ?? before.symbol, 10), clampInt(values.decimals ?? before.decimals, 0, 8, before.decimals), values.enabled === false ? 0 : 1, values.is_base ? 1 : 0, clampInt(values.sort_order ?? before.sort_order, 0, 1000, before.sort_order), nowIso(), code);
    const rateBefore = db.prepare('SELECT * FROM fx_rates WHERE currency_code=?').get(code);
    if (values.rate_to_try_e8 != null) {
      const rate = clampInt(values.rate_to_try_e8, 1, Number.MAX_SAFE_INTEGER, rateBefore?.rate_to_try_e8 || 100000000);
      db.prepare(`INSERT INTO fx_rates(currency_code,rate_to_try_e8,source,is_demo,fetched_at,updated_at) VALUES(?,?,?,0,?,?)
        ON CONFLICT(currency_code) DO UPDATE SET rate_to_try_e8=excluded.rate_to_try_e8,source=excluded.source,is_demo=0,fetched_at=excluded.fetched_at,updated_at=excluded.updated_at`)
        .run(code, rate, sanitizeText(values.source || 'manual', 30), nowIso(), nowIso());
    }
    const after = rateRow(db, code);
    audit(db, { actorType: 'admin', actorId: actor, action: 'currency.update', targetType: 'fiat_currency', targetId: code, before: { ...before, rate: rateBefore }, after, ip });
    return after;
  });
}

export async function syncTcmbRates(db, actor, ip, fetchImpl = fetch) {
  const response = await fetchImpl(TCMB_URL, { signal: AbortSignal.timeout(15000), headers: { 'User-Agent': 'OnlyCoin/0.7 FX Sync' } });
  if (!response.ok) fail('TCMB_RATE_FETCH_FAILED', `TCMB kur servisi ${response.status} döndürdü.`, 502);
  const xml = await response.text();
  const rates = {};
  const blocks = xml.match(/<Currency\b[\s\S]*?<\/Currency>/g) || [];
  for (const block of blocks) {
    const code = block.match(/CurrencyCode="([A-Z]{3})"/)?.[1];
    const unit = Number(block.match(/<Unit>([^<]+)<\/Unit>/)?.[1] || 1);
    const forexSelling = Number(block.match(/<ForexSelling>([^<]+)<\/ForexSelling>/)?.[1] || 0);
    const forexBuying = Number(block.match(/<ForexBuying>([^<]+)<\/ForexBuying>/)?.[1] || 0);
    const rate = forexSelling || forexBuying;
    if (code && rate > 0 && unit > 0) rates[code] = Math.round((rate / unit) * 1e8);
  }
  if (!Object.keys(rates).length) fail('TCMB_RATE_PARSE_FAILED', 'TCMB döviz verisi çözümlenemedi.', 502);
  return withTransaction(db, () => {
    const now = nowIso();
    const upsert = db.prepare(`INSERT INTO fx_rates(currency_code,rate_to_try_e8,source,is_demo,fetched_at,updated_at) VALUES(?,?,'tcmb',0,?,?)
      ON CONFLICT(currency_code) DO UPDATE SET rate_to_try_e8=excluded.rate_to_try_e8,source='tcmb',is_demo=0,fetched_at=excluded.fetched_at,updated_at=excluded.updated_at`);
    upsert.run('TRY', 100000000, now, now);
    let updated = 0;
    for (const [code, rate] of Object.entries(rates)) {
      const currency = db.prepare('SELECT code FROM fiat_currencies WHERE code=?').get(code);
      if (!currency) continue;
      upsert.run(code, rate, now, now);
      updated += 1;
    }
    audit(db, { actorType: 'admin', actorId: actor, action: 'fx.sync.tcmb', targetType: 'fx_rates', targetId: 'tcmb', before: null, after: { updated, fetchedAt: now }, ip });
    return { ok: true, updated, fetchedAt: now, rates: db.prepare(`SELECT c.code,c.name,c.symbol,r.rate_to_try_e8,r.source,r.is_demo,r.fetched_at FROM fiat_currencies c JOIN fx_rates r ON r.currency_code=c.code ORDER BY c.sort_order`).all() };
  });
}

export function adminCreateBankAccount(db, values, actor, ip) {
  return withTransaction(db, () => {
    const id = normalizeProviderId(values.id) || `bank-${uuid().slice(0, 8)}`;
    if (db.prepare('SELECT id FROM bank_accounts WHERE id=?').get(id)) fail('BANK_ACCOUNT_EXISTS', 'Bu banka hesabı zaten var.', 409);
    db.prepare(`INSERT INTO bank_accounts(id,currency_code,bank_name,account_holder,iban,swift_bic,account_number,branch_name,branch_code,country_code,instructions,min_amount_minor,max_amount_minor,enabled,sort_order,updated_at) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`)
      .run(id, normalizeCode(values.currency_code || 'TRY', 8), sanitizeText(values.bank_name, 100), sanitizeText(values.account_holder, 120), sanitizeText(values.iban, 50).replace(/\s+/g, ''), sanitizeText(values.swift_bic, 20).toUpperCase(), sanitizeText(values.account_number, 50), sanitizeText(values.branch_name, 80), sanitizeText(values.branch_code, 20), normalizeCode(values.country_code || 'TR', 3), sanitizeText(values.instructions, 1000), clampInt(values.min_amount_minor, 0, Number.MAX_SAFE_INTEGER, 0), clampInt(values.max_amount_minor, 0, Number.MAX_SAFE_INTEGER, 0), values.enabled ? 1 : 0, clampInt(values.sort_order, 0, 1000, 100), nowIso());
    const after = db.prepare('SELECT * FROM bank_accounts WHERE id=?').get(id);
    audit(db, { actorType: 'admin', actorId: actor, action: 'bank_account.create', targetType: 'bank_account', targetId: id, before: null, after, ip });
    return bankRow(after);
  });
}

export function adminUpdateBankAccount(db, idInput, values, actor, ip) {
  const id = normalizeProviderId(idInput);
  return withTransaction(db, () => {
    const before = db.prepare('SELECT * FROM bank_accounts WHERE id=?').get(id);
    if (!before) fail('BANK_ACCOUNT_NOT_FOUND', 'Banka hesabı bulunamadı.', 404);
    db.prepare(`UPDATE bank_accounts SET currency_code=?,bank_name=?,account_holder=?,iban=?,swift_bic=?,account_number=?,branch_name=?,branch_code=?,country_code=?,instructions=?,min_amount_minor=?,max_amount_minor=?,enabled=?,sort_order=?,updated_at=? WHERE id=?`)
      .run(normalizeCode(values.currency_code ?? before.currency_code, 8), sanitizeText(values.bank_name ?? before.bank_name, 100), sanitizeText(values.account_holder ?? before.account_holder, 120), sanitizeText(values.iban ?? before.iban, 50).replace(/\s+/g, ''), sanitizeText(values.swift_bic ?? before.swift_bic, 20).toUpperCase(), sanitizeText(values.account_number ?? before.account_number, 50), sanitizeText(values.branch_name ?? before.branch_name, 80), sanitizeText(values.branch_code ?? before.branch_code, 20), normalizeCode(values.country_code ?? before.country_code, 3), sanitizeText(values.instructions ?? before.instructions, 1000), clampInt(values.min_amount_minor ?? before.min_amount_minor, 0, Number.MAX_SAFE_INTEGER, before.min_amount_minor), clampInt(values.max_amount_minor ?? before.max_amount_minor, 0, Number.MAX_SAFE_INTEGER, before.max_amount_minor), values.enabled == null ? before.enabled : (values.enabled ? 1 : 0), clampInt(values.sort_order ?? before.sort_order, 0, 1000, before.sort_order), nowIso(), id);
    const after = db.prepare('SELECT * FROM bank_accounts WHERE id=?').get(id);
    audit(db, { actorType: 'admin', actorId: actor, action: 'bank_account.update', targetType: 'bank_account', targetId: id, before, after, ip });
    return bankRow(after);
  });
}

export function adminUpdateCryptoAsset(db, idInput, values, actor, ip) {
  const id = normalizeProviderId(idInput);
  return withTransaction(db, () => {
    const before = db.prepare('SELECT * FROM crypto_assets WHERE id=?').get(id);
    if (!before) fail('CRYPTO_ASSET_NOT_FOUND', 'Kripto varlık bulunamadı.', 404);
    const chainFamily = ['EVM','BITCOIN','TRON','TON','OTHER'].includes(values.chain_family) ? values.chain_family : before.chain_family;
    const assetType = ['native','token'].includes(values.asset_type) ? values.asset_type : before.asset_type;
    const metadata = { ...parseJson(before.metadata_json), ...(values.metadata || {}) };
    db.prepare(`UPDATE crypto_assets SET symbol=?,name=?,network=?,chain_family=?,asset_type=?,contract_address=?,decimals=?,deposit_address=?,rpc_url=?,explorer_url=?,confirmations_required=?,manual_price_usd_e8=?,price_source=?,enabled=?,purchase_enabled=?,deposit_enabled=?,withdrawal_enabled=?,min_payment_base=?,max_payment_base=?,sort_order=?,metadata_json=?,updated_at=? WHERE id=?`)
      .run(normalizeCode(values.symbol ?? before.symbol, 12), sanitizeText(values.name ?? before.name, 80), sanitizeText(values.network ?? before.network, 100), chainFamily, assetType, sanitizeText(values.contract_address ?? before.contract_address, 140), clampInt(values.decimals ?? before.decimals, 0, 30, before.decimals), sanitizeText(values.deposit_address ?? before.deposit_address, 180), sanitizeText(values.rpc_url ?? before.rpc_url, 500), sanitizeText(values.explorer_url ?? before.explorer_url, 500), clampInt(values.confirmations_required ?? before.confirmations_required, 0, 1000, before.confirmations_required), clampInt(values.manual_price_usd_e8 ?? before.manual_price_usd_e8, 0, Number.MAX_SAFE_INTEGER, before.manual_price_usd_e8), sanitizeText(values.price_source ?? before.price_source, 30), values.enabled == null ? before.enabled : (values.enabled ? 1 : 0), values.purchase_enabled == null ? before.purchase_enabled : (values.purchase_enabled ? 1 : 0), values.deposit_enabled == null ? before.deposit_enabled : (values.deposit_enabled ? 1 : 0), values.withdrawal_enabled == null ? before.withdrawal_enabled : (values.withdrawal_enabled ? 1 : 0), clampInt(values.min_payment_base ?? before.min_payment_base, 0, Number.MAX_SAFE_INTEGER, before.min_payment_base), clampInt(values.max_payment_base ?? before.max_payment_base, 0, Number.MAX_SAFE_INTEGER, before.max_payment_base), clampInt(values.sort_order ?? before.sort_order, 0, 1000, before.sort_order), JSON.stringify(metadata), nowIso(), id);
    const after = db.prepare('SELECT * FROM crypto_assets WHERE id=?').get(id);
    audit(db, { actorType: 'admin', actorId: actor, action: 'crypto_asset.update', targetType: 'crypto_asset', targetId: id, before: cryptoRow(before), after: cryptoRow(after), ip });
    return cryptoRow(after);
  });
}

export function adminUpdateVipPrice(db, tierInput, currencyInput, values, actor, ip) {
  const tier = clampInt(tierInput, 1, 5, 0);
  const currency = normalizeCode(currencyInput, 12);
  if (!tier || !currency) fail('VIP_PRICE_INVALID', 'VIP fiyat bilgisi geçersiz.', 400);
  return withTransaction(db, () => {
    const before = db.prepare('SELECT * FROM vip_plan_prices WHERE tier=? AND currency_code=?').get(tier, currency) || null;
    const amount = clampInt(values.amount_minor, 0, Number.MAX_SAFE_INTEGER, before?.amount_minor || 0);
    db.prepare(`INSERT INTO vip_plan_prices(tier,currency_code,amount_minor,enabled,updated_at) VALUES(?,?,?,?,?)
      ON CONFLICT(tier,currency_code) DO UPDATE SET amount_minor=excluded.amount_minor,enabled=excluded.enabled,updated_at=excluded.updated_at`)
      .run(tier, currency, amount, values.enabled === false ? 0 : 1, nowIso());
    const after = db.prepare('SELECT * FROM vip_plan_prices WHERE tier=? AND currency_code=?').get(tier, currency);
    if (currency === 'USDT') db.prepare('UPDATE vip_plans SET price_usdt_micros=?,updated_at=? WHERE tier=?').run(amount, nowIso(), tier);
    audit(db, { actorType: 'admin', actorId: actor, action: 'vip.price.update', targetType: 'vip_price', targetId: `${tier}:${currency}`, before, after, ip });
    return after;
  });
}

export async function adminTestPaymentProvider(db, providerIdInput, actor, ip) {
  const id = normalizeProviderId(providerIdInput);
  const row = db.prepare('SELECT * FROM payment_providers WHERE id=?').get(id);
  if (!row) fail('PAYMENT_PROVIDER_NOT_FOUND', 'Ödeme sağlayıcısı bulunamadı.', 404);
  const provider = providerRow(row, true);
  const secrets = provider.secrets;
  let ok = false;
  let message = '';
  if (provider.adapter === 'manual_bank') {
    ok = db.prepare('SELECT COUNT(*) AS count FROM bank_accounts WHERE enabled=1').get().count > 0;
    message = ok ? 'En az bir aktif banka hesabı var.' : 'Aktif banka hesabı bulunmuyor.';
  } else if (provider.adapter === 'crypto_direct') {
    ok = db.prepare('SELECT COUNT(*) AS count FROM crypto_assets WHERE enabled=1 AND purchase_enabled=1 AND deposit_address<>\'\'').get().count > 0;
    message = ok ? 'En az bir aktif kripto varlık ve cüzdan adresi var.' : 'Aktif kripto varlık/cüzdan adresi bulunmuyor.';
  } else {
    ok = providerCredentialsValid(provider, secrets);
    message = ok ? 'Zorunlu kimlik bilgileri girilmiş. Canlı işlem testi merchant hesabı ve erişilebilir callback URL gerektirir.' : 'Zorunlu API/merchant bilgileri eksik.';
  }
  db.prepare(`UPDATE payment_providers SET status=?,last_tested_at=?,last_error=?,updated_at=? WHERE id=?`)
    .run(ok ? 'configured' : 'error', nowIso(), ok ? '' : message, nowIso(), id);
  audit(db, { actorType: 'admin', actorId: actor, action: 'payment.provider.test', targetType: 'payment_provider', targetId: id, before: null, after: { ok, message }, ip });
  return { ok, message, provider: providerRow(db.prepare('SELECT * FROM payment_providers WHERE id=?').get(id)) };
}

function buildCallbackBase(db) {
  return String(getSetting(db, 'payment_callback_base_url', '') || config.appOrigin).replace(/\/$/, '');
}

function paymentExpiry(db) {
  const minutes = clampInt(getSetting(db, 'payment_order_expiry_minutes', 60), 5, 1440, 60);
  return new Date(Date.now() + minutes * 60000).toISOString();
}

function createCheckoutRecord(db, order, provider, currencyCode, amountMinor) {
  const existing = db.prepare('SELECT * FROM payment_checkout_sessions WHERE order_id=? AND provider_id=?').get(order.id, provider.id);
  if (existing) return existing;
  const id = uuid();
  const now = nowIso();
  db.prepare(`INSERT INTO payment_checkout_sessions(id,order_id,provider_id,currency_code,amount_minor,status,expires_at,created_at,updated_at) VALUES(?,?,?,?,?,'created',?,?,?)`)
    .run(id, order.id, provider.id, currencyCode, amountMinor, paymentExpiry(db), now, now);
  return db.prepare('SELECT * FROM payment_checkout_sessions WHERE id=?').get(id);
}

async function createPaytrLink(db, provider, order, session, amountMinor, currencyCode, user) {
  const secrets = provider.secrets;
  if (!providerCredentialsValid(provider, secrets)) fail('PAYTR_NOT_CONFIGURED', 'PayTR merchant bilgileri eksik.', 409);
  const paytrCurrency = currencyCode === 'TRY' ? 'TL' : currencyCode;
  const name = sanitizeText(order.purpose === 'vip' ? `OnlyCoin VIP ${order.purpose_ref}` : `OnlyCoin Presale ${order.payment_reference}`, 190);
  const maxInstallment = String(clampInt(provider.config.maxInstallment ?? getSetting(db, 'payment_max_installment', 1), 1, 12, 1));
  const linkType = 'product';
  const lang = provider.config.lang === 'en' ? 'en' : 'tr';
  const minCount = '1';
  const required = `${name}${amountMinor}${paytrCurrency}${maxInstallment}${linkType}${lang}${minCount}`;
  const paytrToken = crypto.createHmac('sha256', secrets.merchantKey).update(required + secrets.merchantSalt).digest('base64');
  const callbackBase = buildCallbackBase(db);
  const callbackLink = /^https:\/\//i.test(callbackBase) && !/localhost|127\.0\.0\.1/i.test(callbackBase) ? `${callbackBase}/api/webhooks/paytr` : '';
  const body = new URLSearchParams({
    merchant_id: secrets.merchantId,
    name,
    price: String(amountMinor),
    currency: paytrCurrency,
    max_installment: maxInstallment,
    link_type: linkType,
    lang,
    get_qr: provider.config.getQr === false ? '0' : '1',
    min_count: minCount,
    email: sanitizeText(user.email || '', 100),
    expiry_date: '',
    max_count: '1',
    callback_link: callbackLink,
    callback_id: callbackLink ? session.id.replaceAll('-', '').slice(0, 64) : '',
    debug_on: provider.test_mode ? '1' : '0',
    paytr_token: paytrToken
  });
  const timeout = clampInt(getSetting(db, 'payment_provider_timeout_seconds', 20), 5, 60, 20) * 1000;
  const response = await fetch('https://www.paytr.com/odeme/api/link/create', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body, signal: AbortSignal.timeout(timeout) });
  const result = await response.json().catch(() => ({}));
  if (!response.ok || result.status !== 'success' || !result.link) {
    fail('PAYTR_LINK_CREATE_FAILED', sanitizeText(result.reason || result.err_msg || 'PayTR ödeme bağlantısı oluşturulamadı.', 240), 502);
  }
  return { providerReference: String(result.id || ''), checkoutUrl: String(result.link), qrPayload: String(result.qr || ''), instructions: { callbackEnabled: Boolean(callbackLink), currency: paytrCurrency } };
}

function createGenericHosted(provider, order, session, amountMinor, currencyCode) {
  const template = String(provider.config.checkoutUrl || '').trim();
  if (!template) fail('PROVIDER_CHECKOUT_URL_MISSING', `${provider.name} checkout URL tanımlı değil.`, 409);
  const replacements = {
    '{orderId}': encodeURIComponent(order.id),
    '{reference}': encodeURIComponent(order.payment_reference),
    '{amount}': encodeURIComponent(String(amountMinor)),
    '{currency}': encodeURIComponent(currencyCode),
    '{callbackUrl}': encodeURIComponent(`${config.appOrigin}/api/webhooks/generic/${provider.id}`),
    '{sessionId}': encodeURIComponent(session.id)
  };
  let checkoutUrl = template;
  for (const [key, value] of Object.entries(replacements)) checkoutUrl = checkoutUrl.replaceAll(key, value);
  return { providerReference: order.payment_reference, checkoutUrl, qrPayload: '', instructions: { mode: 'generic_hosted' } };
}

function createBankInstructions(db, order, currencyCode) {
  const bank = db.prepare('SELECT * FROM bank_accounts WHERE enabled=1 AND currency_code=? ORDER BY sort_order LIMIT 1').get(currencyCode);
  if (!bank) fail('BANK_ACCOUNT_NOT_CONFIGURED', `${currencyCode} için aktif banka hesabı tanımlı değil.`, 409);
  return {
    providerReference: order.payment_reference,
    checkoutUrl: '',
    qrPayload: '',
    instructions: {
      type: 'bank_transfer',
      bank: bankRow(bank),
      reference: order.payment_reference,
      reviewRequired: true
    }
  };
}

function createCryptoInstructions(db, order, assetId, purpose) {
  const settings = getSettings(db);
  if (!settings.crypto_funding_enabled) fail('CRYPTO_FUNDING_DISABLED', 'Kripto fonlama kapalıdır.', 403);
  if (!settings.crypto_payment_for_goods_enabled && purpose === 'vip') fail('CRYPTO_GOODS_PAYMENT_DISABLED', 'Bu bölgede kripto ile VIP satın alma kapalıdır.', 403);
  if (settings.crypto_payment_region_mode === 'presale_deposit_only' && !['presale','deposit'].includes(purpose)) fail('CRYPTO_PURPOSE_RESTRICTED', 'Kripto transferi yalnızca presale ve deposit için kullanılabilir.', 403);
  const asset = db.prepare('SELECT * FROM crypto_assets WHERE id=? AND enabled=1 AND purchase_enabled=1').get(normalizeProviderId(assetId));
  if (!asset) fail('CRYPTO_ASSET_NOT_AVAILABLE', 'Seçilen kripto varlık ödeme için aktif değil.', 404);
  if (!asset.deposit_address) fail('CRYPTO_DEPOSIT_ADDRESS_MISSING', `${asset.symbol} cüzdan adresi tanımlı değil.`, 409);
  const converted = convertUsdtMicrosToCryptoBase(order.amount_usdt_micros, asset);
  const formatted = formatBaseAmount(converted.amountBaseBigInt, asset.decimals);
  return {
    providerReference: order.payment_reference,
    checkoutUrl: '',
    qrPayload: `${asset.symbol.toLowerCase()}:${asset.deposit_address}?amount=${formatted}`,
    instructions: {
      type: 'crypto_transfer',
      asset: cryptoRow(asset),
      address: asset.deposit_address,
      amountBase: converted.amountBase,
      amountFormatted: formatted,
      reference: order.payment_reference,
      confirmationsRequired: asset.confirmations_required,
      reviewRequired: true
    }
  };
}

function prevalidateCheckout(db, provider, purpose, input) {
  let amountUsdtMicros = 0;
  if (purpose === 'vip') {
    const tier = clampInt(input.tier, 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);
    amountUsdtMicros = Number(plan.price_usdt_micros || 0);
    if (amountUsdtMicros <= 0) fail('VIP_USDT_PRICE_NOT_SET', 'VIP için USDT fiyatı tanımlanmamış.', 409);
  } else {
    amountUsdtMicros = clampInt(input.amountUsdtMicros, 1, Number.MAX_SAFE_INTEGER, 0);
    if (amountUsdtMicros <= 0) fail('PRESALE_AMOUNT_REQUIRED', 'Presale tutarı gereklidir.', 400);
  }

  if (provider.category === 'crypto') {
    const settings = getSettings(db);
    if (!settings.crypto_funding_enabled) fail('CRYPTO_FUNDING_DISABLED', 'Kripto fonlama kapalıdır.', 403);
    if (!settings.crypto_payment_for_goods_enabled && purpose === 'vip') fail('CRYPTO_GOODS_PAYMENT_DISABLED', 'Bu bölgede kripto ile VIP satın alma kapalıdır.', 403);
    if (settings.crypto_payment_region_mode === 'presale_deposit_only' && !['presale', 'deposit'].includes(purpose)) fail('CRYPTO_PURPOSE_RESTRICTED', 'Kripto transferi yalnızca presale ve deposit için kullanılabilir.', 403);
    const asset = db.prepare('SELECT * FROM crypto_assets WHERE id=? AND enabled=1 AND purchase_enabled=1').get(normalizeProviderId(input.cryptoAssetId));
    if (!asset) fail('CRYPTO_ASSET_NOT_AVAILABLE', 'Seçilen kripto varlık ödeme için aktif değil.', 404);
    if (!asset.deposit_address) fail('CRYPTO_DEPOSIT_ADDRESS_MISSING', `${asset.symbol} cüzdan adresi tanımlı değil.`, 409);
    convertUsdtMicrosToCryptoBase(amountUsdtMicros, asset);
    return { amountUsdtMicros, currencyCode: asset.id, asset };
  }

  if (!providerCredentialsValid(provider, provider.secrets)) {
    fail('PAYMENT_PROVIDER_NOT_CONFIGURED', `${provider.name} yapılandırması tamamlanmamış.`, 409);
  }
  const currencyCode = normalizeCode(input.currencyCode || 'TRY', 8);
  const allowed = db.prepare('SELECT * FROM payment_provider_currencies WHERE provider_id=? AND currency_code=? AND enabled=1').get(provider.id, currencyCode);
  if (!allowed) fail('PROVIDER_CURRENCY_NOT_AVAILABLE', `${provider.name} için ${currencyCode} aktif değil.`, 409);
  const fixedVipPrice = purpose === 'vip'
    ? db.prepare('SELECT amount_minor FROM vip_plan_prices WHERE tier=? AND currency_code=? AND enabled=1').get(Number(input.tier), currencyCode)
    : null;
  const converted = convertUsdtMicrosToFiatMinor(db, amountUsdtMicros, currencyCode);
  const amountMinor = fixedVipPrice?.amount_minor > 0 ? fixedVipPrice.amount_minor : converted.amountMinor;
  if (allowed.min_amount_minor && amountMinor < allowed.min_amount_minor) fail('PAYMENT_BELOW_PROVIDER_MINIMUM', 'Tutar sağlayıcı alt limitinin altında.', 400);
  if (allowed.max_amount_minor && amountMinor > allowed.max_amount_minor) fail('PAYMENT_ABOVE_PROVIDER_MAXIMUM', 'Tutar sağlayıcı üst limitini aşıyor.', 400);
  if (provider.adapter === 'manual_bank') {
    const bank = db.prepare('SELECT id FROM bank_accounts WHERE enabled=1 AND currency_code=? ORDER BY sort_order LIMIT 1').get(currencyCode);
    if (!bank) fail('BANK_ACCOUNT_NOT_CONFIGURED', `${currencyCode} için aktif banka hesabı tanımlı değil.`, 409);
  }
  return { amountUsdtMicros, currencyCode, amountMinor, converted, allowed };
}

export async function createPaymentCheckout(db, userId, input, idempotencyKey) {
  if (!idempotencyKey || String(idempotencyKey).length < 8) fail('IDEMPOTENCY_KEY_REQUIRED', 'Geçerli bir Idempotency-Key gereklidir.', 400);
  const providerId = normalizeProviderId(input.providerId);
  const providerDb = db.prepare('SELECT * FROM payment_providers WHERE id=? AND enabled=1').get(providerId);
  if (!providerDb) fail('PAYMENT_PROVIDER_NOT_AVAILABLE', 'Ödeme yöntemi aktif değil.', 404);
  const provider = providerRow(providerDb, true);
  const purpose = input.purpose === 'vip' ? 'vip' : input.purpose === 'presale' ? 'presale' : '';
  if (!purpose) fail('PAYMENT_PURPOSE_INVALID', 'Ödeme amacı geçersiz.', 400);
  const preview = prevalidateCheckout(db, provider, purpose, input);

  const created = purpose === 'vip'
    ? createVipUsdtOrder(db, userId, input.tier, `checkout-order:${idempotencyKey}`)
    : createPresaleOrder(db, userId, input.amountUsdtMicros, input.stageId, `checkout-order:${idempotencyKey}`);
  const order = created.order;
  const user = db.prepare('SELECT * FROM users WHERE id=?').get(userId);
  const currencyCode = preview.currencyCode;
  let amountMinor;
  let exchangeRateE8 = 100000000;
  if (provider.category === 'crypto') {
    amountMinor = order.amount_usdt_micros;
  } else {
    const fixedVipPrice = purpose === 'vip'
      ? db.prepare('SELECT amount_minor FROM vip_plan_prices WHERE tier=? AND currency_code=? AND enabled=1').get(Number(input.tier), currencyCode)
      : null;
    const converted = convertUsdtMicrosToFiatMinor(db, order.amount_usdt_micros, currencyCode);
    amountMinor = fixedVipPrice?.amount_minor > 0 ? fixedVipPrice.amount_minor : converted.amountMinor;
    exchangeRateE8 = converted.rateToTryE8;
  }
  if (created.duplicate) {
    const purposeConflict = order.purpose !== purpose
      || (purpose === 'vip' && String(order.purpose_ref) !== String(clampInt(input.tier, 1, 5, 0)))
      || (purpose === 'presale' && Number(order.amount_usdt_micros) !== Number(preview.amountUsdtMicros))
      || (purpose === 'presale' && input.stageId && String(order.purpose_ref) !== String(input.stageId));
    const providerConflict = Boolean(order.provider_id) && (order.provider_id !== provider.id || order.currency_code !== currencyCode);
    if (purposeConflict || providerConflict) {
      fail('IDEMPOTENCY_KEY_CONFLICT', 'Bu Idempotency-Key farklı bir ödeme isteğinde daha önce kullanılmış.', 409);
    }
  }
  const session = createCheckoutRecord(db, order, provider, currencyCode, amountMinor);
  if (session.status !== 'created') return { order, session: { ...session, instructions: parseJson(session.instructions_json) }, duplicate: true };

  let result;
  if (provider.adapter === 'paytr_link') result = await createPaytrLink(db, provider, order, session, amountMinor, currencyCode, user);
  else if (provider.adapter === 'manual_bank') result = createBankInstructions(db, order, currencyCode);
  else if (provider.adapter === 'crypto_direct') result = createCryptoInstructions(db, order, input.cryptoAssetId, purpose);
  else result = createGenericHosted(provider, order, session, amountMinor, currencyCode);

  withTransaction(db, () => {
    db.prepare(`UPDATE payment_checkout_sessions SET provider_reference=?,checkout_url=?,qr_payload=?,instructions_json=?,status='ready',updated_at=? WHERE id=?`)
      .run(result.providerReference, result.checkoutUrl, result.qrPayload, JSON.stringify(result.instructions || {}), nowIso(), session.id);
    db.prepare(`UPDATE payment_orders SET provider_id=?,currency_code=?,amount_minor=?,settlement_currency='USDT',settlement_amount_minor=?,exchange_rate_e8=?,provider_order_id=?,checkout_url=?,payment_method_type=?,updated_at=? WHERE id=?`)
      .run(provider.id, currencyCode, amountMinor, order.amount_usdt_micros, exchangeRateE8, result.providerReference, result.checkoutUrl, provider.category === 'crypto' ? 'crypto_transfer' : provider.category === 'bank' ? 'bank_transfer' : 'hosted_checkout', nowIso(), order.id);
  });
  const finalSession = db.prepare('SELECT * FROM payment_checkout_sessions WHERE id=?').get(session.id);
  const finalOrder = db.prepare('SELECT * FROM payment_orders WHERE id=?').get(order.id);
  return { ...created, order: finalOrder, session: { ...finalSession, instructions: parseJson(finalSession.instructions_json) }, provider: { id: provider.id, name: provider.name, category: provider.category }, duplicate: false };
}

function recordWebhook(db, providerId, eventId, signatureValid, payload, status = 'received', errorMessage = '') {
  const id = uuid();
  try {
    db.prepare(`INSERT INTO payment_webhook_events(id,provider_id,provider_event_id,signature_valid,status,payload_json,error_message,created_at) VALUES(?,?,?,?,?,?,?,?)`)
      .run(id, providerId, eventId, signatureValid ? 1 : 0, status, JSON.stringify(payload || {}), sanitizeText(errorMessage, 500), nowIso());
    return { id, duplicate: false };
  } catch (error) {
    if (String(error.message).includes('UNIQUE')) return { id: db.prepare('SELECT id FROM payment_webhook_events WHERE provider_id=? AND provider_event_id=?').get(providerId, eventId)?.id, duplicate: true };
    throw error;
  }
}

export function processPaytrWebhook(db, body, ip = '') {
  const row = db.prepare("SELECT * FROM payment_providers WHERE id='paytr'").get();
  if (!row) fail('PAYTR_NOT_CONFIGURED', 'PayTR sağlayıcısı bulunamadı.', 404);
  const provider = providerRow(row, true);
  const secrets = provider.secrets;
  if (!secrets.merchantKey || !secrets.merchantSalt) fail('PAYTR_NOT_CONFIGURED', 'PayTR webhook anahtarları eksik.', 503);
  const eventId = sanitizeText(body.merchant_oid || body.callback_id || body.id || crypto.randomUUID(), 120);
  const expected = crypto.createHmac('sha256', secrets.merchantKey)
    .update(`${body.id || ''}${body.merchant_oid || ''}${secrets.merchantSalt}${body.status || ''}${body.total_amount || ''}`)
    .digest('base64');
  const valid = safeEqual(expected, body.hash || '');
  const logged = recordWebhook(db, 'paytr', eventId, valid, body, valid ? 'received' : 'rejected', valid ? '' : 'Hash doğrulanamadı');
  if (logged.duplicate) return { ok: true, duplicate: true };
  if (!valid) fail('PAYTR_WEBHOOK_SIGNATURE_INVALID', 'PayTR webhook imzası doğrulanamadı.', 401);
  const callbackCompact = sanitizeText(body.callback_id || '', 80);
  let session = null;
  if (callbackCompact) {
    session = db.prepare(`SELECT * FROM payment_checkout_sessions WHERE replace(id,'-','')=?`).get(callbackCompact);
  }
  if (!session && body.id) session = db.prepare(`SELECT * FROM payment_checkout_sessions WHERE provider_id='paytr' AND provider_reference=?`).get(String(body.id));
  if (!session) fail('PAYTR_CHECKOUT_NOT_FOUND', 'PayTR ödeme oturumu bulunamadı.', 404);
  const order = db.prepare('SELECT * FROM payment_orders WHERE id=?').get(session.order_id);
  if (!order) fail('PAYMENT_ORDER_NOT_FOUND', 'Ödeme emri bulunamadı.', 404);
  if (String(body.status) === 'success') {
    const paidMinor = Number(body.payment_amount || body.total_amount || 0);
    if (paidMinor > 0 && paidMinor < order.amount_minor) fail('PAYTR_AMOUNT_MISMATCH', 'PayTR ödeme tutarı sipariş tutarından düşük.', 409);
    const result = approvePaymentOrder(db, order.id, 'webhook:paytr', ip, { provider: 'paytr', providerEventId: eventId, providerOrderId: body.merchant_oid, paidMinor, currency: body.currency });
    db.prepare(`UPDATE payment_checkout_sessions SET status='paid',updated_at=? WHERE id=?`).run(nowIso(), session.id);
    db.prepare(`UPDATE payment_webhook_events SET status='processed',processed_at=? WHERE id=?`).run(nowIso(), logged.id);
    return { ok: true, paid: true, result };
  }
  rejectPaymentOrder(db, order.id, 'webhook:paytr', ip, body.fail_message || 'PayTR ödeme başarısız');
  db.prepare(`UPDATE payment_checkout_sessions SET status='failed',updated_at=? WHERE id=?`).run(nowIso(), session.id);
  db.prepare(`UPDATE payment_webhook_events SET status='processed',processed_at=? WHERE id=?`).run(nowIso(), logged.id);
  return { ok: true, paid: false };
}

export function processGenericWebhook(db, providerIdInput, rawBody, body, signature, ip = '') {
  const providerId = normalizeProviderId(providerIdInput);
  const row = db.prepare('SELECT * FROM payment_providers WHERE id=?').get(providerId);
  if (!row) fail('PAYMENT_PROVIDER_NOT_FOUND', 'Ödeme sağlayıcısı bulunamadı.', 404);
  const provider = providerRow(row, true);
  const secret = provider.secrets.webhookSecret || provider.secrets.secretKey || '';
  if (!secret) fail('WEBHOOK_SECRET_NOT_CONFIGURED', 'Webhook secret tanımlı değil.', 503);
  const expectedHex = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  const expectedBase64 = crypto.createHmac('sha256', secret).update(rawBody).digest('base64');
  const valid = safeEqual(signature || '', expectedHex) || safeEqual(signature || '', expectedBase64);
  const eventId = sanitizeText(body.eventId || body.id || body.providerEventId || crypto.randomUUID(), 120);
  const logged = recordWebhook(db, providerId, eventId, valid, body, valid ? 'received' : 'rejected', valid ? '' : 'HMAC doğrulanamadı');
  if (logged.duplicate) return { ok: true, duplicate: true };
  if (!valid) fail('WEBHOOK_SIGNATURE_INVALID', 'Webhook imzası doğrulanamadı.', 401);
  const orderId = sanitizeText(body.orderId || body.reference || '', 80);
  const order = db.prepare('SELECT * FROM payment_orders WHERE id=? OR payment_reference=? OR provider_order_id=? LIMIT 1').get(orderId, orderId, orderId);
  if (!order) fail('PAYMENT_ORDER_NOT_FOUND', 'Webhook ödeme emriyle eşleştirilemedi.', 404);
  const status = String(body.status || '').toLowerCase();
  let result;
  if (['paid','success','completed'].includes(status)) result = approvePaymentOrder(db, order.id, `webhook:${providerId}`, ip, { provider: providerId, providerEventId: eventId, payload: body });
  else if (['failed','rejected','cancelled'].includes(status)) result = rejectPaymentOrder(db, order.id, `webhook:${providerId}`, ip, body.reason || status);
  else fail('WEBHOOK_STATUS_UNSUPPORTED', 'Webhook ödeme durumu desteklenmiyor.', 400);
  db.prepare(`UPDATE payment_webhook_events SET status='processed',processed_at=? WHERE id=?`).run(nowIso(), logged.id);
  return { ok: true, result };
}

export function getCheckoutSession(db, userId, sessionId) {
  const session = db.prepare(`SELECT s.*,o.user_id,o.payment_reference,o.purpose,o.purpose_ref,o.status AS order_status,o.amount_usdt_micros,o.currency_code,o.amount_minor,o.checkout_url
    FROM payment_checkout_sessions s JOIN payment_orders o ON o.id=s.order_id WHERE s.id=? AND o.user_id=?`).get(sessionId, userId);
  if (!session) fail('CHECKOUT_SESSION_NOT_FOUND', 'Ödeme oturumu bulunamadı.', 404);
  return { ...session, instructions: parseJson(session.instructions_json) };
}
