import crypto from 'node:crypto';
import { getSetting, withTransaction } from './db.js';
import { queueEmail } from './email.js';
import { config } from './config.js';
import { keccak256 } from './keccak.js';
import { nowIso, nowSeconds, sanitizeText, sha256, uuid } from './utils.js';

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

function userRow(db, userId) {
  const user = db.prepare('SELECT id,email,email_verified_at,display_name,login_id FROM users WHERE id=? AND status<>?').get(userId, 'deleted');
  if (!user) fail('USER_NOT_FOUND', 'Kullanıcı bulunamadı.', 404);
  return user;
}

export function normalizeEvmAddress(input) {
  const address = String(input || '').trim().toLowerCase();
  return /^0x[0-9a-f]{40}$/.test(address) ? address : '';
}

function generateEvmKeypair() {
  const ecdh = crypto.createECDH('secp256k1');
  ecdh.generateKeys();
  const privateKey = ecdh.getPrivateKey();
  const publicKey = ecdh.getPublicKey(null, 'uncompressed');
  const address = `0x${keccak256(publicKey.subarray(1)).subarray(-20).toString('hex')}`;
  return { address, privateKey: `0x${privateKey.toString('hex')}`, publicKey: `0x${publicKey.toString('hex')}` };
}

function derivePasswordKey(password, salt, params = {}) {
  const N = Number(params.N || 16384);
  const r = Number(params.r || 8);
  const p = Number(params.p || 1);
  return crypto.scryptSync(String(password), salt, 32, { N, r, p, maxmem: 64 * 1024 * 1024 });
}

function encryptPrivateKey(privateKey, password) {
  const salt = crypto.randomBytes(16);
  const iv = crypto.randomBytes(12);
  const params = { N: 16384, r: 8, p: 1, dkLen: 32 };
  const key = derivePasswordKey(password, salt, params);
  const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
  const encrypted = Buffer.concat([cipher.update(privateKey, 'utf8'), cipher.final()]);
  const tag = cipher.getAuthTag();
  const verifier = crypto.createHash('sha256').update(key).update(':onlycoin-wallet-verifier:v1').digest('hex');
  key.fill(0);
  return {
    encryptedKeyJson: JSON.stringify({
      version: 1,
      cipher: 'aes-256-gcm',
      kdf: 'scrypt',
      kdfParams: params,
      salt: salt.toString('base64url'),
      iv: iv.toString('base64url'),
      tag: tag.toString('base64url'),
      ciphertext: encrypted.toString('base64url')
    }),
    verifier
  };
}

function decryptPrivateKey(encryptedKeyJson, password) {
  let payload;
  try { payload = JSON.parse(encryptedKeyJson); } catch { fail('WALLET_DATA_INVALID', 'Cüzdan şifreli verisi geçersiz.', 500); }
  if (payload.version !== 1 || payload.cipher !== 'aes-256-gcm' || payload.kdf !== 'scrypt') fail('WALLET_DATA_UNSUPPORTED', 'Cüzdan şifreleme biçimi desteklenmiyor.', 500);
  try {
    const key = derivePasswordKey(password, Buffer.from(payload.salt, 'base64url'), payload.kdfParams);
    const decipher = crypto.createDecipheriv('aes-256-gcm', key, Buffer.from(payload.iv, 'base64url'));
    decipher.setAuthTag(Buffer.from(payload.tag, 'base64url'));
    const plain = Buffer.concat([
      decipher.update(Buffer.from(payload.ciphertext, 'base64url')),
      decipher.final()
    ]).toString('utf8');
    key.fill(0);
    return plain;
  } catch {
    fail('WALLET_PASSWORD_INVALID', 'Cüzdan şifresi yanlış.', 401);
  }
}

function publicWallet(row) {
  return {
    id: row.id,
    walletType: row.wallet_type,
    network: row.network,
    chainId: row.chain_id,
    address: row.address,
    label: row.label,
    verificationStatus: row.verification_status,
    status: row.status,
    passwordProtected: Boolean(row.password_set_at),
    passwordSetAt: row.password_set_at,
    availableAt: row.available_at,
    emailNotifiedAt: row.email_notified_at,
    createdAt: row.created_at,
    updatedAt: row.updated_at
  };
}

function refreshCooldowns(db, userId) {
  db.prepare("UPDATE user_wallets SET status='active',updated_at=? WHERE user_id=? AND status='cooldown' AND available_at<=?")
    .run(nowIso(), userId, nowSeconds());
}

export function listUserWallets(db, userId) {
  userRow(db, userId);
  refreshCooldowns(db, userId);
  return db.prepare('SELECT * FROM user_wallets WHERE user_id=? ORDER BY created_at DESC').all(userId).map(publicWallet);
}

export function createTestnetWallet(db, userId, input = {}) {
  if (!Boolean(getSetting(db, 'wallet_testnet_enabled', getSetting(db, 'testnet_wallets_enabled', true)))) fail('TESTNET_WALLETS_DISABLED', 'Testnet cüzdan oluşturma kapalıdır.', 403);
  const user = userRow(db, userId);
  const password = String(input.password || '');
  const confirmPassword = String(input.confirmPassword || '');
  const minLength = Math.max(10, Number(getSetting(db, 'wallet_password_min_length', 12)));
  if (password.length < minLength) fail('WALLET_PASSWORD_WEAK', `Cüzdan şifresi en az ${minLength} karakter olmalıdır.`, 400);
  if (password !== confirmPassword) fail('WALLET_PASSWORD_MISMATCH', 'Cüzdan şifreleri eşleşmiyor.', 400);
  if (!/[A-ZÇĞİÖŞÜ]/u.test(password) || !/[a-zçğıöşü]/u.test(password) || !/\d/.test(password)) {
    fail('WALLET_PASSWORD_WEAK', 'Cüzdan şifresi büyük harf, küçük harf ve rakam içermelidir.', 400);
  }
  const network = sanitizeText(getSetting(db, 'wallet_testnet_network_name', getSetting(db, 'testnet_chain_name', 'BSC Testnet')), 80);
  const chainId = Number(getSetting(db, 'wallet_testnet_chain_id', getSetting(db, 'testnet_chain_id', 97)));
  const existing = db.prepare("SELECT * FROM user_wallets WHERE user_id=? AND wallet_type='generated_testnet' AND network=? AND status<>'disabled' ORDER BY created_at DESC LIMIT 1").get(userId, network);
  if (existing) fail('TESTNET_WALLET_EXISTS', 'Bu ağ için testnet cüzdanınız zaten bulunuyor.', 409, { wallet: publicWallet(existing) });

  const pair = generateEvmKeypair();
  const protectedKey = encryptPrivateKey(pair.privateKey, password);
  pair.privateKey = '';
  const walletId = uuid();
  const recoveryToken = crypto.randomBytes(32).toString('base64url');
  const now = nowIso();
  withTransaction(db, () => {
    db.prepare(`INSERT INTO user_wallets(id,user_id,wallet_type,network,chain_id,address,label,encrypted_key_json,password_verifier,verification_status,status,password_set_at,available_at,created_at,updated_at)
      VALUES(?,?, 'generated_testnet',?,?,?,?,?,?,'admin_verified','active',?,?,?,?)`)
      .run(walletId, userId, network, chainId, pair.address, sanitizeText(input.label || 'OnlyCoin Testnet Cüzdanı', 80), protectedKey.encryptedKeyJson, protectedKey.verifier, now, nowSeconds(), now, now);
    const recoveryTtl = Math.max(5, Number(getSetting(db, 'wallet_recovery_link_ttl_minutes', 30)) || 30);
    db.prepare(`INSERT INTO wallet_recovery_exports(id,wallet_id,token_hash,expires_at,created_at) VALUES(?,?,?,?,?)`)
      .run(uuid(), walletId, sha256(recoveryToken), nowSeconds() + recoveryTtl * 60, now);
  });

  if (Boolean(getSetting(db, 'wallet_recovery_email_enabled', true)) && user.email && user.email_verified_at) {
    const queued = queueEmail(db, {
      to: user.email,
      subject: 'OnlyCoin testnet cüzdanınız oluşturuldu',
      template: 'wallet-created',
      text: [
        `Merhaba ${user.display_name || user.login_id},`,
        '',
        `Testnet cüzdan adresiniz: ${pair.address}`,
        `Ağ: ${network} (${chainId})`,
        '',
        'Güvenlik nedeniyle cüzdan şifreniz ve private key e-posta ile gönderilmez ve OnlyCoin tarafından saklanmaz.',
        'Şifrenizi güvenli bir parola yöneticisinde saklayın ve tek kullanımlık kurtarma paketini uygulamadan indirin.'
      ].join('\n'),
      html: `<p>Merhaba ${sanitizeText(user.display_name || user.login_id, 80)},</p><p>Testnet cüzdan adresiniz: <code>${pair.address}</code></p><p>Ağ: ${network} (${chainId})</p><p><strong>Güvenlik nedeniyle cüzdan şifreniz ve private key e-posta ile gönderilmez.</strong> Şifrenizi güvenli bir parola yöneticisinde saklayın ve tek kullanımlık kurtarma paketini uygulamadan indirin.</p>`,
      metadata: { userId, walletId, address: pair.address }
    });
    if (queued) db.prepare('UPDATE user_wallets SET email_notified_at=?,updated_at=? WHERE id=?').run(nowIso(), nowIso(), walletId);
  }

  return {
    wallet: publicWallet(db.prepare('SELECT * FROM user_wallets WHERE id=?').get(walletId)),
    recoveryToken,
    recoveryExpiresAt: nowSeconds() + Math.max(5, Number(getSetting(db, 'wallet_recovery_link_ttl_minutes', 30)) || 30) * 60,
    securityNotice: 'Şifreniz sunucuda düz metin olarak saklanmaz ve e-posta ile gönderilmez. Kurtarma paketini şimdi indirin ve şifrenizi ayrı bir parola yöneticisinde saklayın.'
  };
}

export function verifyWalletPassword(db, userId, walletId, password) {
  const wallet = db.prepare("SELECT * FROM user_wallets WHERE id=? AND user_id=? AND wallet_type='generated_testnet'").get(walletId, userId);
  if (!wallet) fail('WALLET_NOT_FOUND', 'Cüzdan bulunamadı.', 404);
  const privateKey = decryptPrivateKey(wallet.encrypted_key_json, String(password || ''));
  const ok = /^0x[0-9a-f]{64}$/i.test(privateKey);
  return { ok, address: wallet.address };
}

export function downloadRecoveryPackage(db, userId, recoveryToken) {
  const tokenHash = sha256(String(recoveryToken || ''));
  return withTransaction(db, () => {
    const row = db.prepare(`SELECT e.*,w.user_id,w.network,w.chain_id,w.address,w.label,w.encrypted_key_json,w.created_at AS wallet_created_at
      FROM wallet_recovery_exports e JOIN user_wallets w ON w.id=e.wallet_id
      WHERE e.token_hash=? AND w.user_id=?`).get(tokenHash, userId);
    if (!row) fail('RECOVERY_EXPORT_NOT_FOUND', 'Kurtarma paketi bulunamadı.', 404);
    if (row.downloaded_at) fail('RECOVERY_EXPORT_USED', 'Kurtarma paketi daha önce indirilmiş.', 409);
    if (row.expires_at <= nowSeconds()) fail('RECOVERY_EXPORT_EXPIRED', 'Kurtarma paketi indirme süresi dolmuş.', 410);
    db.prepare('UPDATE wallet_recovery_exports SET downloaded_at=? WHERE id=?').run(nowIso(), row.id);
    return {
      format: 'onlycoin-encrypted-wallet-v1',
      walletId: row.wallet_id,
      address: row.address,
      network: row.network,
      chainId: row.chain_id,
      label: row.label,
      encryptedPrivateKey: JSON.parse(row.encrypted_key_json),
      createdAt: row.wallet_created_at,
      instructions: [
        'Bu dosya yalnızca oluştururken belirlediğiniz şifreyle açılabilir.',
        'OnlyCoin şifrenizi saklamaz ve kurtaramaz.',
        'Dosyayı çevrimdışı, şifreli bir konumda; şifreyi ise ayrı bir parola yöneticisinde saklayın.',
        'Bu testnet cüzdanıdır; mainnet varlıklarını bu adrese göndermeyin.'
      ]
    };
  });
}

function queueWalletConfirmationEmail(db, user, wallet, code) {
  if (!user.email || !user.email_verified_at) fail('VERIFIED_EMAIL_REQUIRED', 'Ana ağ cüzdanı eklemek için doğrulanmış e-posta gereklidir.', 409);
  queueEmail(db, {
    to: user.email,
    subject: 'OnlyCoin ana ağ cüzdan adresi doğrulama kodu',
    template: 'wallet-address-confirmation',
    text: `OnlyCoin cüzdan doğrulama kodunuz: ${code}\n\nAdres: ${wallet.address}\nAğ: ${wallet.network}\n\nBu işlemi siz yapmadıysanız hesabınızın güvenliğini kontrol edin.`,
    html: `<p>OnlyCoin cüzdan doğrulama kodunuz:</p><p style="font-size:28px;font-weight:bold;letter-spacing:4px">${code}</p><p>Adres: <code>${wallet.address}</code><br>Ağ: ${wallet.network}</p><p>Bu işlemi siz yapmadıysanız hesabınızın güvenliğini kontrol edin.</p>`,
    metadata: { userId: user.id, walletId: wallet.id, address: wallet.address }
  });
}

export function addExternalMainnetWallet(db, userId, input = {}) {
  if (!Boolean(getSetting(db, 'wallet_mainnet_enabled', getSetting(db, 'mainnet_wallet_entry_enabled', true)))) fail('MAINNET_WALLET_ENTRY_DISABLED', 'Ana ağ cüzdanı ekleme kapalıdır.', 403);
  const user = userRow(db, userId);
  const address = normalizeEvmAddress(input.address);
  const confirmAddress = normalizeEvmAddress(input.confirmAddress);
  if (!address) fail('INVALID_WALLET_ADDRESS', 'Geçerli bir EVM cüzdan adresi girin.', 400);
  if (address !== confirmAddress) fail('WALLET_ADDRESS_MISMATCH', 'Cüzdan adresleri eşleşmiyor.', 400);
  const network = sanitizeText(input.network || getSetting(db, 'wallet_mainnet_network_name', getSetting(db, 'usdt_network_name', 'EVM Mainnet')), 80);
  const chainId = Number(input.chainId || getSetting(db, 'wallet_mainnet_chain_id', getSetting(db, 'usdt_chain_id', 56)));
  const duplicate = db.prepare("SELECT * FROM user_wallets WHERE user_id=? AND wallet_type='external_mainnet' AND network=? AND address=? AND status<>'disabled'").get(userId, network, address);
  if (duplicate) fail('WALLET_ALREADY_EXISTS', 'Bu cüzdan adresi zaten eklenmiş.', 409);
  const cooldownHours = Math.max(0, Number(getSetting(db, 'wallet_address_cooldown_hours', getSetting(db, 'mainnet_wallet_cooldown_hours', 24))));
  const now = nowIso();
  const walletId = uuid();
  const code = String(crypto.randomInt(100000, 1000000));
  const initialStatus = cooldownHours > 0 ? 'cooldown' : 'active';
  withTransaction(db, () => {
    db.prepare(`INSERT INTO user_wallets(id,user_id,wallet_type,network,chain_id,address,label,encrypted_key_json,password_verifier,verification_status,status,available_at,created_at,updated_at)
      VALUES(?,?, 'external_mainnet',?,?,?,?, '','', 'unverified',?,?,?,?)`)
      .run(walletId, userId, network, chainId, address, sanitizeText(input.label || 'Ana Ağ Cüzdanım', 80), initialStatus, nowSeconds() + cooldownHours * 3600, now, now);
    db.prepare(`INSERT INTO wallet_address_confirmations(id,wallet_id,user_id,code_hash,expires_at,created_at) VALUES(?,?,?,?,?,?)`)
      .run(uuid(), walletId, userId, sha256(code), nowSeconds() + 15 * 60, now);
  });
  const wallet = db.prepare('SELECT * FROM user_wallets WHERE id=?').get(walletId);
  if (Boolean(getSetting(db, 'wallet_email_confirmation_required', true))) queueWalletConfirmationEmail(db, user, wallet, code);
  else db.prepare("UPDATE user_wallets SET verification_status='email_confirmed',updated_at=? WHERE id=?").run(nowIso(), walletId);
  return { wallet: publicWallet(db.prepare('SELECT * FROM user_wallets WHERE id=?').get(walletId)), confirmationRequired: Boolean(getSetting(db, 'wallet_email_confirmation_required', true)), ...(config.isProduction ? {} : { debugConfirmationCode: code }) };
}

export function confirmExternalWallet(db, userId, walletId, code) {
  return withTransaction(db, () => {
    const wallet = db.prepare("SELECT * FROM user_wallets WHERE id=? AND user_id=? AND wallet_type='external_mainnet'").get(walletId, userId);
    if (!wallet) fail('WALLET_NOT_FOUND', 'Cüzdan bulunamadı.', 404);
    const confirmation = db.prepare('SELECT * FROM wallet_address_confirmations WHERE wallet_id=? AND user_id=? AND consumed_at IS NULL ORDER BY created_at DESC LIMIT 1').get(walletId, userId);
    if (!confirmation) fail('CONFIRMATION_NOT_FOUND', 'Doğrulama isteği bulunamadı.', 404);
    if (confirmation.expires_at <= nowSeconds()) fail('CONFIRMATION_EXPIRED', 'Doğrulama kodunun süresi dolmuş.', 410);
    const maxAttempts = Math.max(3, Number(getSetting(db, 'email_verification_max_attempts', 5)) || 5);
    if (confirmation.attempts >= maxAttempts) fail('CONFIRMATION_ATTEMPTS_EXCEEDED', 'Cüzdan doğrulama deneme sınırı aşıldı.', 429);
    if (sha256(String(code || '').trim()) !== confirmation.code_hash) {
      db.prepare('UPDATE wallet_address_confirmations SET attempts=attempts+1 WHERE id=?').run(confirmation.id);
      fail('CONFIRMATION_CODE_INVALID', 'Doğrulama kodu geçersiz.', 400, { remainingAttempts: Math.max(0, maxAttempts - confirmation.attempts - 1) });
    }
    db.prepare('UPDATE wallet_address_confirmations SET consumed_at=? WHERE id=?').run(nowIso(), confirmation.id);
    const status = wallet.available_at <= nowSeconds() ? 'active' : 'cooldown';
    db.prepare("UPDATE user_wallets SET verification_status='email_confirmed',status=?,updated_at=? WHERE id=?").run(status, nowIso(), wallet.id);
    db.prepare(`INSERT OR IGNORE INTO withdrawal_addresses(id,user_id,asset,network,address,label,status,created_at) VALUES(?,?, 'OC_TOKEN',?,?,?,'active',?)`)
      .run(uuid(), userId, wallet.network, wallet.address, wallet.label || 'Onaylı Ana Ağ Cüzdanı', nowIso());
    return publicWallet(db.prepare('SELECT * FROM user_wallets WHERE id=?').get(wallet.id));
  });
}

export function adminWallets(db, limit = 500) {
  return db.prepare(`SELECT w.*,u.email,u.username,u.display_name,u.login_id
    FROM user_wallets w JOIN users u ON u.id=w.user_id
    ORDER BY w.created_at DESC LIMIT ?`).all(Math.max(1, Math.min(2000, Number(limit) || 500))).map((row) => ({ ...publicWallet(row), userId: row.user_id, email: row.email, username: row.username, displayName: row.display_name, loginId: row.login_id }));
}

export function decryptGeneratedWalletForTest(db, userId, walletId, password) {
  const wallet = db.prepare("SELECT * FROM user_wallets WHERE id=? AND user_id=? AND wallet_type='generated_testnet'").get(walletId, userId);
  if (!wallet) fail('WALLET_NOT_FOUND', 'Cüzdan bulunamadı.', 404);
  return { address: wallet.address, privateKey: decryptPrivateKey(wallet.encrypted_key_json, password) };
}
