const express = require('express');
const QRCode = require('qrcode');

const { toDisplayAmount } = require('../config/coinConfig');
const {
  attachWalletUser,
  clearWalletSession,
  requireWalletUser,
  setWalletSession,
  verifyWalletToken
} = require('../middleware/walletUserAuth');
const testnetService = require('../services/testnetService');
const nodeParticipantService = require('../services/nodeParticipantService');
const trxCollateralService = require('../services/trxCollateralService');
const walletService = require('../services/walletService');

const router = express.Router();

function walletMessage(code) {
  const key = String(code || '').trim();
  const collateralMessages = {
    INVALID_TRX_AMOUNT: 'TRX 담보 금액을 다시 확인해 주세요.',
    TRX_COLLATERAL_REQUESTED: 'TRX 담보 입금 요청이 접수되었습니다.',
    TRX_COLLATERAL_AUTO_CONFIRMED: 'TRX 입금이 확인되어 담보가 자동 승인되었습니다.',
    TRX_COLLATERAL_PENDING_CONFIRMATIONS: 'TRX 입금은 확인되었지만 컨펌 수가 아직 부족합니다. 컨펌 후 다시 제출하거나 관리자 확인을 기다려 주세요.',
    TRON_ADDRESS_REQUIRED: '보낸 TRX 주소를 입력해 주세요.',
    INVALID_TRON_ADDRESS: 'TRX 주소 형식이 올바르지 않습니다.',
    TRX_DEPOSIT_NOT_FOUND: '해당 주소에서 관리자 담보 주소로 들어온 정확한 담보 금액의 입금을 아직 찾지 못했습니다.',
    TRX_TX_NOT_FOUND: 'TRON 네트워크에서 해당 TX를 찾지 못했습니다.',
    TRX_TRANSFER_REQUIRED: '해당 TX는 일반 TRX 전송 거래가 아닙니다.',
    TRX_SENDER_MISMATCH: 'TX의 보낸 주소가 입력한 TRX 주소와 다릅니다.',
    TRX_TARGET_MISMATCH: 'TX의 받는 주소가 관리자 담보 주소와 다릅니다.',
    TRX_AMOUNT_TOO_LOW: '입금된 TRX 수량이 담보 기준보다 적습니다.',
    TRX_RECEIPT_FAILED: 'TRON 거래가 성공 상태가 아닙니다.',
    DUPLICATE_TRX_TX: '이미 등록된 TRON TX Hash입니다.',
    TRON_API_FAILED: 'TRON 네트워크 조회에 실패했습니다. 잠시 후 다시 시도해 주세요.',
    TRON_API_TIMEOUT: 'TRON 네트워크 응답 시간이 초과되었습니다.'
  };
  if (collateralMessages[key]) return collateralMessages[key];
  const readableMessages = {
    PRIVATE_KEY_REQUIRED: '개인키를 입력해 주세요.',
    PRIVATE_KEY_WALLET_NOT_FOUND: '개인키와 일치하는 내 지갑을 찾지 못했습니다.'
  };
  if (readableMessages[key]) return readableMessages[key];
  const messages = {
    SOURCE_WALLET_NOT_FOUND: '보내는 지갑을 찾을 수 없습니다.',
    TARGET_WALLET_NOT_FOUND: '받는 지갑 주소를 찾을 수 없습니다.',
    INVALID_AMOUNT: '송금 금액을 다시 확인해 주세요.',
    INSUFFICIENT_BALANCE: '잔액이 부족합니다.',
    USER_ID_REQUIRED: '로그인 사용자 정보가 필요합니다.',
    TRANSFER_FAILED: '송금 처리에 실패했습니다.',
    PIN_REQUIRED: 'PIN을 입력해야 합니다.',
    PIN_MISMATCH: 'PIN 확인이 일치하지 않습니다.',
    INVALID_PIN: 'PIN이 맞지 않습니다.',
    UNAUTHORIZED_WALLET: '이 지갑에 접근할 권한이 없습니다.',
    WALLET_NOT_EMPTY: '잔액이 있는 지갑은 삭제할 수 없습니다.',
    WALLET_HAS_HISTORY: '거래 기록이 있는 지갑은 삭제할 수 없습니다.',
    WALLET_HAS_SECURITY: 'PIN 또는 복구 정보가 있는 지갑은 삭제할 수 없습니다.',
    RECOVERY_ENABLED: '복구 금고가 생성되었습니다. 복구문구와 테스트 개인키를 안전한 곳에 보관하세요.',
    RECOVERY_NOT_ENABLED: '복구 금고가 아직 없습니다.',
    RECOVERY_EXPORT_FAILED: '복구 정보 확인에 실패했습니다.',
    RECOVERY_CONFIRMED: '복구문구 확인이 완료되었습니다.',
    RECOVERY_PHRASE_REQUIRED: '복구문구를 입력해야 합니다.',
    RECOVERY_PHRASE_MISMATCH: '복구문구가 이 지갑과 일치하지 않습니다.',
    RECOVERY_WALLET_NOT_FOUND: '복구문구와 일치하는 지갑을 찾지 못했습니다.',
    RECOVERY_RESTORED: '복구문구로 지갑을 찾고 새 PIN으로 다시 잠갔습니다.'
  };
  const friendlyMessages = {
    PIN_REQUIRED: 'PIN 번호를 입력해 주세요.',
    INVALID_PIN: 'PIN 번호가 틀립니다. 다시 입력해 주세요.',
    PIN_MISMATCH: 'PIN 확인이 일치하지 않습니다. 다시 입력해 주세요.',
    INSUFFICIENT_BALANCE: '잔액이 부족합니다. 수량과 수수료를 다시 확인해 주세요.',
    INVALID_AMOUNT: '보낼 수량을 다시 확인해 주세요.',
    TARGET_WALLET_NOT_FOUND: '받는 지갑 주소를 찾을 수 없습니다. 주소를 다시 확인해 주세요.',
    SOURCE_WALLET_NOT_FOUND: '보내는 지갑을 찾을 수 없습니다.',
    UNAUTHORIZED_WALLET: '이 지갑에 접근할 권한이 없습니다.',
    RECOVERY_PHRASE_REQUIRED: '복구문구를 입력해 주세요.',
    RECOVERY_PHRASE_MISMATCH: '복구문구가 맞지 않습니다. 다시 입력해 주세요.',
    RECOVERY_WALLET_NOT_FOUND: '복구문구와 일치하는 지갑을 찾지 못했습니다.'
  };
  return friendlyMessages[key] || messages[key] || key;
}

function serviceInfo(testnet) {
  return {
    name: 'PLAI Wallet',
    mode: testnet.config.chainName,
    port: process.env.PORT || 4317
  };
}

function walletLoginUrl() {
  return process.env.AISFOR_WALLET_LOGIN_URL || 'https://aisfor.kr/chain-wallet';
}

function getOwnedWallets(user) {
  return walletService
    .listUserWallets()
    .filter(wallet => wallet.ownerType === 'aisfor_user' && wallet.ownerId === user.userId);
}

const BYTES_PER_GB = 1024 ** 3;
const BYTES_PER_TB = 1024 ** 4;

function normalizeId(value) {
  return String(value || '').trim();
}

function nodeBelongsToUser(node, user) {
  if (!node || !user) return false;
  const metadata = node.metadata || {};
  const endpoint = String(node.endpoint || '');
  const deviceName = String(node.deviceName || '');
  if (metadata.seeded === true || endpoint.startsWith('mock://') || /mock participant/i.test(deviceName)) {
    return false;
  }
  if (node.role && node.role !== 'STORAGE_NODE') return false;

  const userIds = [
    user.userId,
    user.mongoId,
    user.userid,
    user.username,
    user._id
  ].map(normalizeId).filter(Boolean);
  const nodeUserIds = [
    node.userId,
    metadata.ownerUserId,
    metadata.userId,
    metadata.ownerMongoId
  ].map(normalizeId).filter(Boolean);

  return nodeUserIds.some(id => userIds.includes(id));
}

function getNodeCapacityTb(node, config) {
  const bytes = Number(node && (node.storageLimitBytes || node.maxStorageBytes || node.availableStorageBytes || 0));
  if (Number.isFinite(bytes) && bytes > 0) {
    const gib = bytes / BYTES_PER_GB;
    const capacityTb = gib < 1024 ? Math.round(gib) / 1000 : gib / 1024;
    return Math.round(capacityTb * 1_000_000) / 1_000_000;
  }
  return Number(config.minimumCapacityTb || 1);
}

function formatNodeCapacity(capacityTb) {
  const value = Number(capacityTb || 0);
  if (!Number.isFinite(value) || value <= 0) return '0 GB';
  if (value < 1) return `${Math.round(value * 1000).toLocaleString('en-US')} GB`;
  if (Number.isInteger(value)) return `${value.toLocaleString('en-US')} TB`;
  return `${value.toLocaleString('en-US', { maximumFractionDigits: 2 })} TB`;
}

function getWalletNodes(user) {
  return nodeParticipantService
    .listNodes()
    .filter(node => nodeBelongsToUser(node, user));
}

function buildNodeCollateralRows(user) {
  const config = trxCollateralService.refreshCollateralConfig();
  return getWalletNodes(user).map((node) => {
    const capacityTb = getNodeCapacityTb(node, config);
    const requiredCollateral = trxCollateralService.calculateRequiredCollateral(capacityTb);
    const account = trxCollateralService.ensureAccount({
      ownerType: 'node',
      ownerId: node.nodeId,
      label: `${node.deviceName || node.serviceName || node.nodeId} TRX Collateral`,
      metadata: {
        userId: user.userId,
        nodeId: node.nodeId,
        capacityTb,
        requiredCollateral
      }
    });
    const events = trxCollateralService.listEvents({
      ownerType: 'node',
      ownerId: node.nodeId,
      limit: 5
    });
    return {
      node,
      capacityTb,
      capacityLabel: formatNodeCapacity(capacityTb),
      requiredCollateral,
      account,
      events
    };
  });
}

function getOwnedNodeOrThrow(user, nodeId) {
  const safeNodeId = String(nodeId || '').trim();
  const node = safeNodeId ? nodeParticipantService.getNode(safeNodeId) : null;
  if (!node || !nodeBelongsToUser(node, user)) {
    const err = new Error('Node access denied.');
    err.status = 403;
    err.code = 'UNAUTHORIZED_NODE';
    throw err;
  }
  return node;
}

function pickOwnedWallet(req, forcedAddress = '') {
  const wallets = getOwnedWallets(req.walletUser);
  const requestedAddress = String(forcedAddress || req.query.address || '').trim();
  if (requestedAddress) {
    const found = wallets.find(wallet => wallet.address === requestedAddress);
    if (found) return found;
  }
  return wallets[0] || null;
}

function assertWalletOwner(req, address) {
  const wallet = walletService.getWallet(address);
  if (!wallet || wallet.ownerType !== 'aisfor_user' || wallet.ownerId !== req.walletUser.userId) {
    const err = new Error('Wallet access denied.');
    err.status = 403;
    err.code = 'UNAUTHORIZED_WALLET';
    throw err;
  }
  return wallet;
}

function renderWallet(req, res, extra = {}) {
  const testnet = testnetService.getStatus();
  const wallet = pickOwnedWallet(req, extra.walletAddress);
  const wallets = getOwnedWallets(req.walletUser);
  const transactions = wallet ? walletService.getWalletTransactions(wallet.address, 50) : [];
  const trxCollateral = trxCollateralService.ensureAccount({
    ownerType: 'aisfor_user',
    ownerId: req.walletUser.userId,
    label: `${req.walletUser.displayName || req.walletUser.userId} TRX Collateral`
  });
  const trxCollateralEvents = trxCollateralService.listEvents({
    ownerType: 'aisfor_user',
    ownerId: req.walletUser.userId,
    limit: 20
  });
  const nodeCollateralRows = buildNodeCollateralRows(req.walletUser);
  const selectedCollateralNodeId = String(extra.nodeId || req.query.nodeId || '').trim()
    || (nodeCollateralRows[0] ? nodeCollateralRows[0].node.nodeId : '');
  const selectedNodeCollateral = nodeCollateralRows.find(row => row.node.nodeId === selectedCollateralNodeId) || nodeCollateralRows[0] || null;
  const selectedCollateralEvents = selectedNodeCollateral ? selectedNodeCollateral.events : trxCollateralEvents;
  const errorCode = String(extra.error || req.query.error || '').trim();
  const errorScope = String(extra.errorScope || req.query.errorScope || '').trim();

  res.render('wallet', {
    service: serviceInfo(testnet),
    testnet,
    wallet,
    wallets,
    walletUser: req.walletUser,
    walletStats: walletService.getStats(),
    canAccessAdmin: false,
    transactions,
    pinEnabled: wallet ? walletService.walletHasPin(wallet.address) : false,
    recoveryStatus: wallet ? walletService.getWalletRecoveryStatus(wallet) : null,
    trxCollateral,
    trxCollateralEvents,
    trxCollateralStats: trxCollateralService.getStats(),
    nodeCollateralRows,
    selectedNodeCollateral,
    selectedCollateralNodeId,
    selectedCollateralEvents,
    recoveryExport: extra.recoveryExport || null,
    recoveryNotice: extra.recoveryNotice || '',
    toDisplayAmount,
    txHash: extra.txHash || req.query.tx || '',
    errorCode,
    errorScope,
    error: walletMessage(errorCode)
  });
}

router.use(attachWalletUser);

router.get('/login-required', (req, res) => {
  res.status(401).render('wallet_login_required', {
    loginUrl: walletLoginUrl(),
    service: serviceInfo(testnetService.getStatus())
  });
});

router.get('/login', (req, res) => {
  try {
    const user = verifyWalletToken(req.query.token);
    setWalletSession(res, user);
    const nextPath = String(req.query.next || '/wallet');
    const allowedNext = nextPath.startsWith('/wallet') || nextPath.startsWith('/explorer');
    res.redirect(allowedNext ? nextPath : '/wallet');
  } catch (error) {
    res.redirect('/wallet/login-required');
  }
});

router.get('/logout', (req, res) => {
  clearWalletSession(res);
  res.redirect('/wallet/login-required');
});

router.get('/qr/:address', requireWalletUser, async (req, res, next) => {
  try {
    const wallet = assertWalletOwner(req, req.params.address);
    const svg = await QRCode.toString(wallet.address, {
      type: 'svg',
      errorCorrectionLevel: 'M',
      margin: 1,
      width: 220,
      color: {
        dark: '#0f7a66',
        light: '#ffffff'
      }
    });
    res.type('image/svg+xml').send(svg);
  } catch (err) {
    next(err);
  }
});

router.use(requireWalletUser);

router.get('/', (req, res) => {
  renderWallet(req, res);
});

router.post('/create', (req, res, next) => {
  try {
    const existingWallet = getOwnedWallets(req.walletUser)[0] || null;
    if (existingWallet) {
      return res.redirect(`/wallet?address=${encodeURIComponent(existingWallet.address)}`);
    }

    const pin = String(req.body.pin || '').trim();
    const pinConfirm = String(req.body.pinConfirm || '').trim();
    if (!pin) {
      const err = new Error('PIN is required.');
      err.status = 400;
      err.code = 'PIN_REQUIRED';
      throw err;
    }
    if (pin !== pinConfirm) {
      const err = new Error('PIN confirmation does not match.');
      err.status = 400;
      err.code = 'PIN_MISMATCH';
      throw err;
    }

    const result = walletService.ensureUserWallet({
      userId: req.walletUser.userId,
      label: req.body.label || `${req.walletUser.displayName || req.walletUser.userId} PLAI Wallet`,
      faucetAmount: 0,
      metadata: { createdFrom: 'wallet-page', ownerSource: 'aisfor-session' }
    });

    walletService.setWalletPin(result.wallet.address, pin);
    const recovery = walletService.enableWalletRecovery(result.wallet.address, pin);
    return renderWallet(req, res, {
      walletAddress: result.wallet.address,
      recoveryNotice: walletMessage('RECOVERY_ENABLED'),
      recoveryExport: recovery.export
    });
  } catch (err) {
    const code = err.code || 'TRANSFER_FAILED';
    res.redirect(`/wallet?error=${encodeURIComponent(code)}&errorScope=create`);
  }
});

router.post('/pin', (req, res) => {
  const address = String(req.body.address || '').trim();
  try {
    assertWalletOwner(req, address);
    walletService.setWalletPin(address, req.body.pin);
    res.redirect(`/wallet?address=${encodeURIComponent(address)}`);
  } catch (err) {
    const code = err.code || 'INVALID_PIN';
    res.redirect(`/wallet?address=${encodeURIComponent(address)}&error=${encodeURIComponent(code)}&errorScope=pin`);
  }
});

router.post('/delete', (req, res) => {
  const address = String(req.body.address || '').trim();
  try {
    walletService.deleteEmptyOwnedWallet({
      address,
      ownerType: 'aisfor_user',
      ownerId: req.walletUser.userId
    });
    res.redirect('/wallet');
  } catch (err) {
    const code = err.code || 'TRANSFER_FAILED';
    res.redirect(`/wallet?address=${encodeURIComponent(address)}&error=${encodeURIComponent(code)}&errorScope=wallet`);
  }
});

router.post('/recovery/enable', (req, res) => {
  const address = String(req.body.address || req.query.address || '').trim();
  const pin = req.body.pin;
  try {
    assertWalletOwner(req, address);
    const result = walletService.enableWalletRecovery(address, pin);
    return renderWallet(req, res, {
      walletAddress: address,
      recoveryNotice: walletMessage('RECOVERY_ENABLED'),
      recoveryExport: result.export
    });
  } catch (error) {
    return renderWallet(req, res, {
      walletAddress: address,
      error: error.code || 'RECOVERY_EXPORT_FAILED',
      errorScope: 'recovery'
    });
  }
});

router.post('/recovery/export', (req, res) => {
  const address = String(req.body.address || req.query.address || '').trim();
  const pin = req.body.pin;
  try {
    assertWalletOwner(req, address);
    const exported = walletService.exportWalletRecovery(address, pin);
    return renderWallet(req, res, {
      walletAddress: address,
      recoveryNotice: '복구 정보가 현재 화면에만 복호화되어 표시됩니다.',
      recoveryExport: exported
    });
  } catch (error) {
    return renderWallet(req, res, {
      walletAddress: address,
      error: error.code || 'RECOVERY_EXPORT_FAILED',
      errorScope: 'recovery'
    });
  }
});

router.post('/recovery/confirm', (req, res) => {
  const address = String(req.body.address || req.query.address || '').trim();
  try {
    assertWalletOwner(req, address);
    walletService.confirmWalletRecovery(address, req.body.pin, req.body.recoveryPhrase);
    return renderWallet(req, res, {
      walletAddress: address,
      recoveryNotice: walletMessage('RECOVERY_CONFIRMED')
    });
  } catch (error) {
    return renderWallet(req, res, {
      walletAddress: address,
      error: error.code || 'RECOVERY_EXPORT_FAILED',
      errorScope: 'recoveryConfirm'
    });
  }
});

router.post('/recovery/restore', (req, res) => {
  try {
    const result = walletService.restoreOwnedWalletWithRecoveryPhrase({
      ownerType: 'aisfor_user',
      ownerId: req.walletUser.userId,
      recoveryPhrase: req.body.recoveryPhrase,
      newPin: req.body.newPin
    });
    return renderWallet(req, res, {
      walletAddress: result.wallet.address,
      recoveryNotice: walletMessage('RECOVERY_RESTORED')
    });
  } catch (error) {
    return renderWallet(req, res, {
      error: error.code || 'RECOVERY_EXPORT_FAILED',
      errorScope: 'recoveryRestore'
    });
  }
});

router.post('/recovery/restore-private-key', (req, res) => {
  try {
    const result = walletService.restoreOwnedWalletWithPrivateKey({
      ownerType: 'aisfor_user',
      ownerId: req.walletUser.userId,
      privateKey: req.body.privateKey,
      newPin: req.body.newPin
    });
    return renderWallet(req, res, {
      walletAddress: result.wallet.address,
      recoveryNotice: '개인키로 지갑을 복구하고 새 PIN으로 다시 잠갔습니다.'
    });
  } catch (error) {
    return renderWallet(req, res, {
      error: error.code || 'RECOVERY_EXPORT_FAILED',
      errorScope: 'privateKeyRestore'
    });
  }
});

router.post('/transfer', (req, res) => {
  const fromAddress = String(req.body.fromAddress || '').trim();
  try {
    assertWalletOwner(req, fromAddress);
    walletService.verifyWalletPin(fromAddress, req.body.pin);
    const tx = walletService.transfer({
      fromAddress,
      toAddress: req.body.toAddress,
      amount: req.body.amount,
      fee: req.body.fee || 0,
      memo: req.body.memo || 'PLAI Wallet transfer'
    });
    res.redirect(`/wallet?address=${encodeURIComponent(fromAddress)}&tx=${encodeURIComponent(tx.txHash)}`);
  } catch (err) {
    const code = err.code || 'TRANSFER_FAILED';
    res.redirect(`/wallet?address=${encodeURIComponent(fromAddress)}&error=${encodeURIComponent(code)}&errorScope=transfer`);
  }
});

router.post('/collateral/trx/request', async (req, res) => {
  const walletAddress = String(req.body.walletAddress || '').trim();
  const nodeId = String(req.body.nodeId || '').trim();
  try {
    if (walletAddress) assertWalletOwner(req, walletAddress);
    let ownerType = 'aisfor_user';
    let ownerId = req.walletUser.userId;
    if (nodeId) {
      const node = getOwnedNodeOrThrow(req.walletUser, nodeId);
      const config = trxCollateralService.refreshCollateralConfig();
      const capacityTb = getNodeCapacityTb(node, config);
      const requiredCollateral = trxCollateralService.calculateRequiredCollateral(capacityTb);
      trxCollateralService.ensureAccount({
        ownerType: 'node',
        ownerId: node.nodeId,
        label: `${node.deviceName || node.serviceName || node.nodeId} TRX Collateral`,
        metadata: {
          userId: req.walletUser.userId,
          nodeId: node.nodeId,
          capacityTb,
          requiredCollateral
        }
      });
      ownerType = 'node';
      ownerId = node.nodeId;
    }
    const collateralConfig = trxCollateralService.refreshCollateralConfig();
    if (collateralConfig.verifyDeposits) {
      const result = await trxCollateralService.requestVerifiedDeposit({
        ownerType,
        ownerId,
        amount: req.body.amount,
        senderTrxAddress: req.body.senderTrxAddress,
        note: req.body.note
      });
      const code = result.autoConfirmed ? 'TRX_COLLATERAL_AUTO_CONFIRMED' : 'TRX_COLLATERAL_PENDING_CONFIRMATIONS';
      return res.redirect(`/wallet?address=${encodeURIComponent(walletAddress)}&nodeId=${encodeURIComponent(nodeId)}&error=${code}&errorScope=collateral`);
    } else {
      trxCollateralService.requestDeposit({
        ownerType,
        ownerId,
        amount: req.body.amount,
        senderTrxAddress: req.body.senderTrxAddress,
        note: req.body.note
      });
    }
    res.redirect(`/wallet?address=${encodeURIComponent(walletAddress)}&nodeId=${encodeURIComponent(nodeId)}&error=TRX_COLLATERAL_REQUESTED&errorScope=collateral`);
  } catch (err) {
    const code = err.code || 'INVALID_TRX_AMOUNT';
    res.redirect(`/wallet?address=${encodeURIComponent(walletAddress)}&nodeId=${encodeURIComponent(nodeId)}&error=${encodeURIComponent(code)}&errorScope=collateral`);
  }
});

module.exports = router;
