import test from 'node:test';
import assert from 'node:assert/strict';
import crypto from 'node:crypto';
import { closeDb, getDb, resetDbFile, setSetting } from '../src/db.js';
import { getOrCreateUser } from '../src/game.js';
import {
  adminPaymentSettings,
  adminCreateBankAccount,
  adminUpdateBankAccount,
  adminUpdateCryptoAsset,
  adminUpdateCurrency,
  adminUpdatePaymentProvider,
  createPaymentCheckout,
  getPublicPaymentCatalog,
  processGenericWebhook,
  syncTcmbRates
} from '../src/payments.js';

function expectCode(fn, code) {
  assert.throws(fn, (error) => error?.code === code);
}

async function expectCodeAsync(fn, code) {
  await assert.rejects(fn, (error) => error?.code === code);
}

test('payment settings support multi-currency, bank, crypto and signed webhooks', async () => {
  resetDbFile();
  const db = getDb();
  const actor = 'test-admin';
  const ip = '127.0.0.1';

  const initial = adminPaymentSettings(db);
  setSetting(db, 'purchases_enabled', true);
  setSetting(db, 'fiat_payments_enabled', true);
  setSetting(db, 'bank_transfer_enabled', true);
  setSetting(db, 'presale_enabled', true);
  adminUpdatePaymentProvider(db, 'bank_transfer', { enabled: true }, actor, ip);
  assert.ok(initial.currencies.some((row) => row.code === 'USD'));
  assert.ok(initial.currencies.some((row) => row.code === 'EUR'));
  assert.ok(initial.providers.some((row) => row.id === 'paytr'));
  assert.ok(initial.providers.some((row) => row.id === 'iyzico'));
  assert.ok(initial.cryptoAssets.some((row) => row.symbol === 'BTC'));
  assert.ok(initial.cryptoAssets.some((row) => row.id === 'usdt-bep20'));

  const savedPaytr = adminUpdatePaymentProvider(db, 'paytr', {
    enabled: false,
    test_mode: true,
    config: { maxInstallment: 3, lang: 'tr', getQr: true },
    secrets: { merchantId: '123456', merchantKey: 'merchant-key-test', merchantSalt: 'merchant-salt-test' }
  }, actor, ip);
  assert.equal(savedPaytr.status, 'disabled');
  const masked = adminPaymentSettings(db).providers.find((row) => row.id === 'paytr');
  assert.match(masked.secrets.merchantKey, /^••••••••/);
  assert.equal(masked.config.maxInstallment, 3);

  const usd = adminUpdateCurrency(db, 'USD', {
    name: 'ABD Doları', symbol: '$', decimals: 2, enabled: true, is_base: false, sort_order: 20,
    rate_to_try_e8: 3250000000, source: 'manual-test'
  }, actor, ip);
  assert.equal(usd.rate_to_try_e8, 3250000000);

  const mockXml = `<?xml version="1.0" encoding="UTF-8"?>
  <Tarih_Date Tarih="29.07.2026">
    <Currency CrossOrder="0" Kod="USD" CurrencyCode="USD"><Unit>1</Unit><ForexBuying>40.1000</ForexBuying><ForexSelling>40.2000</ForexSelling></Currency>
    <Currency CrossOrder="1" Kod="EUR" CurrencyCode="EUR"><Unit>1</Unit><ForexBuying>46.5000</ForexBuying><ForexSelling>46.6000</ForexSelling></Currency>
  </Tarih_Date>`;
  const sync = await syncTcmbRates(db, actor, ip, async () => ({ ok: true, text: async () => mockXml }));
  assert.equal(sync.updated, 2);
  assert.equal(db.prepare("SELECT source FROM fx_rates WHERE currency_code='USD'").get().source, 'tcmb');

  adminCreateBankAccount(db, {
    id: 'bank-try', bank_name: 'OnlyCoin Test Bankası', account_holder: 'OnlyCoin Test', iban: 'TR000000000000000000000000',
    currency_code: 'TRY', instructions: 'Referansı açıklamaya yazın.', enabled: true
  }, actor, ip);

  const user = getOrCreateUser(db, { externalId: 'test:payment-user', username: 'PaymentUser', displayName: 'Payment User' });
  const orderCountBeforeMissingBank = db.prepare('SELECT COUNT(*) AS count FROM payment_orders').get().count;
  adminUpdateBankAccount(db, 'bank-try', {
    bank_name: 'OnlyCoin Test Bankası', account_holder: 'OnlyCoin Test', iban: '',
    currency_code: 'TRY', instructions: 'Referansı açıklamaya yazın.', enabled: false
  }, actor, ip);
  await expectCodeAsync(() => createPaymentCheckout(db, user.id, {
    purpose: 'vip', tier: 1, providerId: 'bank_transfer', currencyCode: 'TRY'
  }, 'checkout-bank-missing-0001'), 'BANK_ACCOUNT_NOT_CONFIGURED');
  assert.equal(db.prepare('SELECT COUNT(*) AS count FROM payment_orders').get().count, orderCountBeforeMissingBank, 'Geçersiz checkout denemesi ödeme emri oluşturmamalı.');
  adminUpdateBankAccount(db, 'bank-try', {
    bank_name: 'OnlyCoin Test Bankası', account_holder: 'OnlyCoin Test', iban: 'TR000000000000000000000000',
    currency_code: 'TRY', instructions: 'Referansı açıklamaya yazın.', enabled: true
  }, actor, ip);
  const bankCheckout = await createPaymentCheckout(db, user.id, {
    purpose: 'vip', tier: 1, providerId: 'bank_transfer', currencyCode: 'TRY'
  }, 'checkout-bank-vip-0001');
  assert.equal(bankCheckout.session.status, 'ready');
  assert.equal(bankCheckout.session.instructions.type, 'bank_transfer');
  assert.equal(bankCheckout.session.instructions.bank.currency_code, 'TRY');
  await expectCodeAsync(() => createPaymentCheckout(db, user.id, {
    purpose: 'vip', tier: 2, providerId: 'bank_transfer', currencyCode: 'TRY'
  }, 'checkout-bank-vip-0001'), 'IDEMPOTENCY_KEY_CONFLICT');

  setSetting(db, 'crypto_funding_enabled', true);
  setSetting(db, 'crypto_payment_for_goods_enabled', false);
  setSetting(db, 'crypto_payment_region_mode', 'presale_deposit_only');
  adminUpdatePaymentProvider(db, 'crypto_direct', { enabled: true, test_mode: true }, actor, ip);
  adminUpdateCryptoAsset(db, 'usdt-bep20', {
    enabled: true, purchase_enabled: true, deposit_enabled: true, withdrawal_enabled: false,
    deposit_address: '0x1111111111111111111111111111111111111111',
    contract_address: '0x2222222222222222222222222222222222222222',
    decimals: 18, manual_price_usd_e8: 100000000, confirmations_required: 15
  }, actor, ip);

  const cryptoCheckout = await createPaymentCheckout(db, user.id, {
    purpose: 'presale', amountUsdtMicros: 25_000_000, providerId: 'crypto_direct', cryptoAssetId: 'usdt-bep20'
  }, 'checkout-crypto-presale-0001');
  assert.equal(cryptoCheckout.session.instructions.type, 'crypto_transfer');
  assert.equal(cryptoCheckout.session.instructions.asset.symbol, 'USDT');
  assert.equal(cryptoCheckout.session.instructions.address, '0x1111111111111111111111111111111111111111');

  await expectCodeAsync(() => createPaymentCheckout(db, user.id, {
    purpose: 'vip', tier: 2, providerId: 'crypto_direct', cryptoAssetId: 'usdt-bep20'
  }, 'checkout-crypto-vip-blocked-0001'), 'CRYPTO_GOODS_PAYMENT_DISABLED');

  adminUpdatePaymentProvider(db, 'param', {
    enabled: true,
    test_mode: true,
    config: { checkoutUrl: 'https://payments.example/checkout?order={orderId}&amount={amount}&currency={currency}' },
    secrets: { apiKey: 'param-api-key', secretKey: 'param-secret', webhookSecret: 'param-webhook-secret' },
    currencies: [{ currency_code: 'USD', enabled: true, min_amount_minor: 100, max_amount_minor: 100000000, fee_bps: 0, fixed_fee_minor: 0 }]
  }, actor, ip);

  const hosted = await createPaymentCheckout(db, user.id, {
    purpose: 'vip', tier: 2, providerId: 'param', currencyCode: 'USD'
  }, 'checkout-param-vip-0001');
  assert.match(hosted.session.checkout_url, /^https:\/\/payments\.example\/checkout/);
  const webhookBody = { eventId: 'param-event-1', orderId: hosted.order.id, status: 'paid' };
  const raw = Buffer.from(JSON.stringify(webhookBody));
  const signature = crypto.createHmac('sha256', 'param-webhook-secret').update(raw).digest('hex');
  const paid = processGenericWebhook(db, 'param', raw, webhookBody, signature, ip);
  assert.equal(paid.ok, true);
  assert.equal(db.prepare('SELECT status FROM payment_orders WHERE id=?').get(hosted.order.id).status, 'paid');
  assert.equal(db.prepare('SELECT vip_tier FROM user_game_state WHERE user_id=?').get(user.id).vip_tier, 2);
  const duplicate = processGenericWebhook(db, 'param', raw, webhookBody, signature, ip);
  assert.equal(duplicate.duplicate, true);

  const publicCatalog = getPublicPaymentCatalog(db, 'TR');
  assert.equal(publicCatalog.country, 'TR');
  assert.ok(publicCatalog.vipPrices.some((row) => row.currency_code === 'USD'));
  assert.ok(publicCatalog.cryptoAssets.some((row) => row.id === 'usdt-bep20'));

  const invalidRaw = Buffer.from(JSON.stringify({ eventId: 'bad-event', orderId: hosted.order.id, status: 'paid' }));
  expectCode(() => processGenericWebhook(db, 'param', invalidRaw, JSON.parse(invalidRaw), 'wrong', ip), 'WEBHOOK_SIGNATURE_INVALID');

  closeDb();
});
