const crypto = require('crypto');
const { v4: uuidv4 } = require('uuid');
const persistentStore = require('./persistentStore');
const chainTransactionService = require('./chainTransactionService');
const blockService = require('./blockService');
const { COIN_CONFIG } = require('../config/coinConfig');

const state = persistentStore.readState();
state.wallets = Array.isArray(state.wallets) ? state.wallets : [];
state.walletTransactions = Array.isArray(state.walletTransactions) ? state.walletTransactions : [];

function nowIso() {
  return new Date().toISOString();
}

function sha256(value) {
  return crypto.createHash('sha256').update(String(value)).digest('hex');
}

function makeAddress(publicKey) {
  const body = sha256(publicKey).slice(0, 40);
  return `${COIN_CONFIG.addressPrefix}${body}`;
}

function normalizeAmount(value) {
  const amount = Number(value || 0);
  if (!Number.isFinite(amount) || amount <= 0) return 0;
  return Math.round(amount * 10 ** COIN_CONFIG.decimals) / 10 ** COIN_CONFIG.decimals;
}

function createWalletRecord({ label, ownerType = 'user', ownerId = null, initialBalance = 0, metadata = {} } = {}) {
  const privateKey = crypto.randomBytes(32).toString('hex');
  const publicKey = sha256(`pub:${privateKey}`);
  const address = makeAddress(publicKey);
  const createdAt = nowIso();

  return {
    id: uuidv4(),
    address,
    label: label || 'PLAI Wallet',
    ownerType,
    ownerId,
    publicKey,
    privateKeyHash: sha256(privateKey),
    balance: normalizeAmount(initialBalance),
    lockedBalance: 0,
    rewardBalance: 0,
    nonce: 0,
    status: 'active',
    metadata,
    createdAt,
    updatedAt: createdAt
  };
}

function save() {
  persistentStore.writeState(state);
}

function shortHash(value) {
  return sha256(value).slice(0, 24);
}

function attachChainAnchor(target, chainTx) {
  if (!target || !chainTx) return;
  target.chainTransactionId = chainTx.id;
  target.chainTxHash = chainTx.txHash;
  target.chainNetworkId = chainTx.networkId;
  target.chainMockBlockHeight = chainTx.blockHeight || null;
  target.chainConfirmedAt = chainTx.confirmedAt || null;
  target.chainStatus = chainTx.status || null;
}

function syncWalletLedgerBlock(target, chainTx) {
  if (!target || !chainTx) return;
  target.ledgerBlockHeight = chainTx.ledgerBlockHeight || target.ledgerBlockHeight || null;
  target.ledgerBlockHash = chainTx.ledgerBlockHash || target.ledgerBlockHash || null;
  target.ledgerMinedAt = chainTx.minedAt || target.ledgerMinedAt || null;
}

function anchorWalletEvent(actionType, { wallet = null, walletTx = null, targetWallet = null, amount = 0, metadata = {} } = {}) {
  const actorAddress = wallet?.address || walletTx?.fromAddress || walletTx?.toAddress || null;
  const targetAddress = targetWallet?.address || walletTx?.toAddress || null;
  const safeMetadata = {
    source: 'wallet-service',
    walletAddressHash: actorAddress ? shortHash(actorAddress) : null,
    targetAddressHash: targetAddress ? shortHash(targetAddress) : null,
    walletTxHash: walletTx?.txHash || null,
    walletTxId: walletTx?.id || null,
    memoHash: walletTx?.memo ? shortHash(walletTx.memo) : null,
    ...metadata
  };

  chainTransactionService.submitTransaction({
    sourceService: 'AISFOR_WALLET',
    actionType,
    actorId: actorAddress ? shortHash(actorAddress) : null,
    targetId: targetAddress ? shortHash(targetAddress) : null,
    amount: normalizeAmount(amount || walletTx?.amount || 0),
    metadata: safeMetadata
  }).then((chainTx) => {
    attachChainAnchor(walletTx, chainTx);
    if (wallet) {
      wallet.metadata = wallet.metadata || {};
      wallet.metadata.lastChainTxHash = chainTx.txHash;
      wallet.metadata.lastChainActionType = actionType;
    }
    const mined = blockService.minePendingProofs({ limit: 1, reason: 'wallet-event' });
    const anchoredChainTx = chainTransactionService.getTransaction(chainTx.id || chainTx.txHash) || chainTx;
    attachChainAnchor(walletTx, anchoredChainTx);
    if (mined && mined.created) syncWalletLedgerBlock(walletTx, anchoredChainTx);
    save();
  }).catch((error) => {
    if (walletTx) walletTx.chainAnchorError = error.message;
    if (wallet) {
      wallet.metadata = wallet.metadata || {};
      wallet.metadata.lastChainAnchorError = error.message;
    }
    save();
  });
}


function hydrateWalletTransactionChain(tx) {
  if (!tx || (!tx.chainTransactionId && !tx.chainTxHash)) return false;
  const before = `${tx.chainTxHash || ''}:${tx.ledgerBlockHeight || ''}:${tx.ledgerBlockHash || ''}`;
  const chainTx = chainTransactionService.getTransaction(tx.chainTransactionId || tx.chainTxHash);
  if (!chainTx) return false;
  attachChainAnchor(tx, chainTx);
  syncWalletLedgerBlock(tx, chainTx);
  const after = `${tx.chainTxHash || ''}:${tx.ledgerBlockHeight || ''}:${tx.ledgerBlockHash || ''}`;
  return before !== after;
}

function hydrateWalletTransactionChains(rows = state.walletTransactions) {
  let changed = false;
  for (const tx of rows || []) {
    if (hydrateWalletTransactionChain(tx)) changed = true;
  }
  if (changed) save();
  return rows;
}
function reconcileFoundationSupply(wallet) {
  if (!wallet) return wallet;
  const totalWalletBalance = state.wallets.reduce((sum, row) => sum + Number(row.balance || 0), 0);
  const delta = normalizeAmount(Number(COIN_CONFIG.totalSupply || 0) - totalWalletBalance);
  if (delta <= 0) return wallet;

  wallet.balance = normalizeAmount(Number(wallet.balance || 0) + delta);
  wallet.updatedAt = nowIso();
  wallet.metadata = wallet.metadata || {};
  wallet.metadata.supplyPolicyAdjustedAt = wallet.updatedAt;
  wallet.metadata.supplyPolicyTarget = COIN_CONFIG.totalSupply;
  state.walletTransactions.unshift({
    id: uuidv4(),
    txHash: `0x${sha256(`supply-policy:${wallet.address}:${COIN_CONFIG.totalSupply}:${wallet.updatedAt}`)}`,
    type: 'SUPPLY_POLICY_ADJUSTMENT',
    status: 'CONFIRMED_MOCK',
    fromAddress: null,
    toAddress: wallet.address,
    amount: delta,
    fee: 0,
    symbol: COIN_CONFIG.symbol,
    memo: `Adjust mock treasury supply to ${COIN_CONFIG.totalSupply} ${COIN_CONFIG.symbol}`,
    createdAt: wallet.updatedAt,
    confirmedAt: wallet.updatedAt,
    blockHeight: 0,
    metadata: {
      previousTotalWalletBalance: totalWalletBalance,
      targetTotalSupply: COIN_CONFIG.totalSupply,
      reason: 'tokenomics-v0.1-max-supply-alignment'
    }
  });
  save();
  return wallet;
}
function ensureFoundationWallet() {
  let wallet = state.wallets.find(row => row.ownerType === 'foundation' || row.address === 'aisfoundation000000000000000000000000000000');
  if (wallet) return reconcileFoundationSupply(wallet);

  wallet = createWalletRecord({
    label: COIN_CONFIG.genesisLabel,
    ownerType: 'foundation',
    ownerId: 'AISFOR_FOUNDATION',
    initialBalance: COIN_CONFIG.totalSupply,
    metadata: {
      genesis: true,
      allocation: 'total mock supply treasury'
    }
  });

  state.wallets.unshift(wallet);
  state.walletTransactions.unshift({
    id: uuidv4(),
    txHash: `0x${sha256(`genesis:${wallet.address}:${wallet.createdAt}`)}`,
    type: 'GENESIS_MINT',
    status: 'CONFIRMED_MOCK',
    fromAddress: null,
    toAddress: wallet.address,
    amount: wallet.balance,
    fee: 0,
    symbol: COIN_CONFIG.symbol,
    memo: 'Genesis mock supply allocation',
    createdAt: wallet.createdAt,
    confirmedAt: wallet.createdAt,
    blockHeight: 0
  });
  save();
  return wallet;
}

function listWallets() {
  ensureFoundationWallet();
  return state.wallets;
}

function listWalletTransactions() {
  ensureFoundationWallet();
  return hydrateWalletTransactionChains(state.walletTransactions);
}

function getWallet(address) {
  const key = String(address || '').trim();
  if (!key) return null;
  return listWallets().find(wallet => wallet.address === key || wallet.id === key) || null;
}

function getWalletTransactions(address, limit = 100) {
  const key = String(address || '').trim();
  return listWalletTransactions()
    .filter(tx => tx.fromAddress === key || tx.toAddress === key || tx.txHash === key || tx.id === key)
    .slice(0, Math.max(1, Number(limit || 100)));
}

function deleteEmptyOwnedWallet({ address, ownerType, ownerId } = {}) {
  const safeAddress = String(address || '').trim();
  const safeOwnerType = String(ownerType || '').trim();
  const safeOwnerId = normalizeOwnerId(ownerId);
  const walletIndex = state.wallets.findIndex(wallet => wallet.address === safeAddress);
  const wallet = walletIndex >= 0 ? state.wallets[walletIndex] : null;
  if (!wallet) {
    const err = new Error('Wallet not found');
    err.status = 404;
    err.code = 'WALLET_NOT_FOUND';
    throw err;
  }
  if (wallet.ownerType !== safeOwnerType || wallet.ownerId !== safeOwnerId) {
    const err = new Error('Wallet access denied.');
    err.status = 403;
    err.code = 'UNAUTHORIZED_WALLET';
    throw err;
  }
  if (Number(wallet.balance || 0) !== 0 || Number(wallet.lockedBalance || 0) !== 0) {
    const err = new Error('Only empty wallets can be deleted.');
    err.status = 400;
    err.code = 'WALLET_NOT_EMPTY';
    throw err;
  }
  if (getWalletTransactions(wallet.address, 1).length > 0) {
    const err = new Error('Wallet has transaction history.');
    err.status = 400;
    err.code = 'WALLET_HAS_HISTORY';
    throw err;
  }
  if (walletHasPin(wallet.address) || wallet.recoveryPhraseHash || wallet.keyVault) {
    const err = new Error('Wallet has security or recovery material.');
    err.status = 400;
    err.code = 'WALLET_HAS_SECURITY';
    throw err;
  }

  const [deleted] = state.wallets.splice(walletIndex, 1);
  save();
  return deleted;
}

function getWalletTransaction(txId) {
  const key = String(txId || '').trim();
  if (!key) return null;
  const tx = listWalletTransactions().find(row => row.txHash === key || row.id === key) || null;
  hydrateWalletTransactionChain(tx);
  return tx;
}

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

function getWalletByOwner(ownerType, ownerId) {
  const type = String(ownerType || '').trim();
  const id = normalizeOwnerId(ownerId);
  if (!type || !id) return null;
  return listWallets().find(wallet => wallet.ownerType === type && wallet.ownerId === id) || null;
}

function ensureUserWallet({ userId, label, metadata = {}, faucetAmount = 0 } = {}) {
  const safeUserId = normalizeOwnerId(userId);
  if (!safeUserId) {
    const err = new Error('userId is required');
    err.status = 400;
    err.code = 'USER_ID_REQUIRED';
    throw err;
  }

  const existing = getWalletByOwner('aisfor_user', safeUserId);
  if (existing) return { wallet: existing, created: false, faucetTransaction: null };

  const wallet = createWalletRecord({
    label: label || `PLAI User ${safeUserId}`,
    ownerType: 'aisfor_user',
    ownerId: safeUserId,
    initialBalance: 0,
    metadata: {
      ...metadata,
      aisforUserId: safeUserId,
      purpose: 'canonical-user-wallet'
    }
  });
  state.wallets.unshift(wallet);
  anchorWalletEvent('WALLET_CREATE', {
    wallet,
    metadata: { ownerType: wallet.ownerType, createdFrom: 'ensure-user-wallet' }
  });

  let faucetTransaction = null;
  const amount = normalizeAmount(faucetAmount || 0);
  if (amount > 0) {
    faucetTransaction = transfer({
      fromAddress: ensureFoundationWallet().address,
      toAddress: wallet.address,
      amount,
      memo: 'PLAI user wallet initial faucet'
    });
  } else {
    save();
  }

  return { wallet, created: true, faucetTransaction };
}

function listUserWallets() {
  return listWallets().filter(wallet => wallet.ownerType === 'aisfor_user');
}
function createWallet(payload = {}) {
  const wallet = createWalletRecord({
    label: payload.label || 'PLAI Test Wallet',
    ownerType: payload.ownerType || 'user',
    ownerId: payload.ownerId || null,
    initialBalance: 0,
    metadata: payload.metadata || {}
  });
  state.wallets.unshift(wallet);
  anchorWalletEvent('WALLET_CREATE', {
    wallet,
    metadata: { ownerType: wallet.ownerType, createdFrom: 'create-wallet' }
  });

  const faucetAmount = normalizeAmount(payload.faucetAmount || 0);
  if (faucetAmount > 0) {
    transfer({
      fromAddress: ensureFoundationWallet().address,
      toAddress: wallet.address,
      amount: faucetAmount,
      memo: payload.memo || 'Admin test wallet faucet'
    });
  } else {
    save();
  }

  return wallet;
}

function ensureOwnedWalletWithRecoveryExport({
  ownerType,
  ownerId,
  label,
  metadata = {},
  faucetAmount = 0
} = {}) {
  const safeOwnerType = String(ownerType || '').trim();
  const safeOwnerId = normalizeOwnerId(ownerId);
  if (!safeOwnerType || !safeOwnerId) {
    throw walletError('ownerType and ownerId are required.', 400, 'OWNER_REQUIRED');
  }

  let wallet = getWalletByOwner(safeOwnerType, safeOwnerId);
  let created = false;
  if (!wallet) {
    wallet = createWalletRecord({
      label: label || `${safeOwnerType} ${safeOwnerId} PLAI Wallet`,
      ownerType: safeOwnerType,
      ownerId: safeOwnerId,
      initialBalance: 0,
      metadata: {
        ...metadata,
        purpose: metadata.purpose || 'owned-wallet'
      }
    });
    state.wallets.unshift(wallet);
    created = true;
    anchorWalletEvent('WALLET_CREATE', {
      wallet,
      metadata: { ownerType: wallet.ownerType, createdFrom: 'ensure-owned-wallet' }
    });
  } else {
    wallet.metadata = {
      ...(wallet.metadata || {}),
      ...metadata
    };
  }

  let recoveryExport = null;
  if (!wallet.recoveryPhraseHash) {
    const recoveryPhrase = randomRecoveryPhrase();
    recoveryExport = recoveryPayloadForWallet(wallet, recoveryPhrase);
    wallet.recoveryPhraseHash = sha256(recoveryExport.recoveryPhrase);
    wallet.privateKeyHash = sha256(recoveryExport.privateKey);
    wallet.metadata = {
      ...(wallet.metadata || {}),
      recoveryMaterialIssuedAt: nowIso(),
      recoveryMode: 'one-time-private-key'
    };
    wallet.updatedAt = nowIso();
    anchorWalletEvent('WALLET_RECOVERY_ENABLE', {
      wallet,
      metadata: { recovery: 'one-time-export', ownerType: wallet.ownerType }
    });
  }

  const amount = normalizeAmount(faucetAmount || 0);
  let faucetTransaction = null;
  if (amount > 0 && created) {
    faucetTransaction = transfer({
      fromAddress: ensureFoundationWallet().address,
      toAddress: wallet.address,
      amount,
      memo: `${safeOwnerType} wallet initial faucet`
    });
  } else {
    save();
  }

  return {
    wallet,
    created,
    recoveryExport: recoveryExport ? normalizeRecoveryExport(recoveryExport) : null,
    faucetTransaction
  };
}

function reissueOwnedWalletRecoveryExport({
  ownerType,
  ownerId,
  metadata = {}
} = {}) {
  const safeOwnerType = String(ownerType || '').trim();
  const safeOwnerId = normalizeOwnerId(ownerId);
  if (!safeOwnerType || !safeOwnerId) {
    throw walletError('ownerType and ownerId are required.', 400, 'OWNER_REQUIRED');
  }

  const wallet = getWalletByOwner(safeOwnerType, safeOwnerId);
  if (!wallet) {
    throw walletError('Owned wallet not found.', 404, 'WALLET_NOT_FOUND');
  }

  const recoveryPhrase = randomRecoveryPhrase();
  const recoveryExport = recoveryPayloadForWallet(wallet, recoveryPhrase);
  const issuedAt = nowIso();
  wallet.recoveryPhraseHash = sha256(recoveryExport.recoveryPhrase);
  wallet.privateKeyHash = sha256(recoveryExport.privateKey);
  wallet.metadata = {
    ...(wallet.metadata || {}),
    ...metadata,
    recoveryMaterialReissuedAt: issuedAt,
    recoveryMode: 'one-time-private-key'
  };
  wallet.updatedAt = issuedAt;

  anchorWalletEvent('WALLET_RECOVERY_REISSUE', {
    wallet,
    metadata: { recovery: 'one-time-export-reissue', ownerType: wallet.ownerType }
  });
  save();

  return {
    wallet,
    recoveryExport: normalizeRecoveryExport(recoveryExport)
  };
}

function transfer({ fromAddress, toAddress, amount, memo = '', fee = 0, metadata = {} } = {}) {
  const from = getWallet(fromAddress);
  const to = getWallet(toAddress);
  const sendAmount = normalizeAmount(amount);
  const sendFee = normalizeAmount(fee);
  const totalDebit = normalizeAmount(sendAmount + sendFee);

  if (!from) {
    const err = new Error('Source wallet not found');
    err.status = 404;
    err.code = 'SOURCE_WALLET_NOT_FOUND';
    throw err;
  }
  if (!to) {
    const err = new Error('Target wallet not found');
    err.status = 404;
    err.code = 'TARGET_WALLET_NOT_FOUND';
    throw err;
  }
  if (sendAmount <= 0) {
    const err = new Error('Amount must be greater than zero');
    err.status = 400;
    err.code = 'INVALID_AMOUNT';
    throw err;
  }
  if (Number(from.balance || 0) < totalDebit) {
    const err = new Error('Insufficient wallet balance');
    err.status = 400;
    err.code = 'INSUFFICIENT_BALANCE';
    throw err;
  }

  const createdAt = nowIso();
  from.balance = normalizeAmount(Number(from.balance || 0) - totalDebit);
  from.nonce = Number(from.nonce || 0) + 1;
  from.updatedAt = createdAt;
  to.balance = normalizeAmount(Number(to.balance || 0) + sendAmount);
  to.updatedAt = createdAt;

  const txPayload = `${from.address}:${to.address}:${sendAmount}:${sendFee}:${from.nonce}:${createdAt}`;
  const tx = {
    id: uuidv4(),
    txHash: `0x${sha256(txPayload)}`,
    type: 'TRANSFER',
    status: 'CONFIRMED_MOCK',
    fromAddress: from.address,
    toAddress: to.address,
    amount: sendAmount,
    fee: sendFee,
    symbol: COIN_CONFIG.symbol,
    memo,
    nonce: from.nonce,
    metadata: metadata || {},
    createdAt,
    confirmedAt: createdAt,
    blockHeight: null
  };
  state.walletTransactions.unshift(tx);
  anchorWalletEvent('WALLET_TRANSFER', {
    wallet: from,
    targetWallet: to,
    walletTx: tx,
    amount: sendAmount,
    metadata: { symbol: COIN_CONFIG.symbol, ...(metadata || {}) }
  });
  save();
  return tx;
}

function findWalletTransactionByMetadata(key, value) {
  const safeKey = String(key || '').trim();
  const safeValue = String(value || '').trim();
  if (!safeKey || !safeValue) return null;
  return listWalletTransactions().find(tx => String(tx.metadata?.[safeKey] || '') === safeValue) || null;
}

function creditPointConversion({ userId, pointAmount, coinAmount, conversionRate = 1000, requestId = '', idempotencyKey = '', label = '' } = {}) {
  const safeUserId = normalizeOwnerId(userId);
  const safePointAmount = normalizeAmount(pointAmount);
  const safeCoinAmount = normalizeAmount(coinAmount);
  const safeKey = String(idempotencyKey || requestId || '').trim();

  if (!safeUserId) {
    const err = new Error('userId is required');
    err.status = 400;
    err.code = 'USER_ID_REQUIRED';
    throw err;
  }
  if (safePointAmount <= 0 || safeCoinAmount <= 0) {
    const err = new Error('Point conversion amount must be greater than zero');
    err.status = 400;
    err.code = 'INVALID_CONVERSION_AMOUNT';
    throw err;
  }

  const existing = findWalletTransactionByMetadata('pointConversionKey', safeKey);
  if (existing) {
    const wallet = getWallet(existing.toAddress);
    return { wallet, transaction: existing, duplicate: true };
  }

  const { wallet } = ensureUserWallet({
    userId: safeUserId,
    label: label || `PLAI User ${safeUserId}`,
    metadata: {
      source: 'point-conversion'
    },
    faucetAmount: 0
  });

  const transaction = transfer({
    fromAddress: ensureFoundationWallet().address,
    toAddress: wallet.address,
    amount: safeCoinAmount,
    memo: `PLAI Point conversion ${safePointAmount}P -> ${safeCoinAmount} ${COIN_CONFIG.symbol}`,
    metadata: {
      source: 'plai-point-conversion',
      pointConversionKey: safeKey,
      requestId: String(requestId || ''),
      aisforUserId: safeUserId,
      pointAmount: safePointAmount,
      coinAmount: safeCoinAmount,
      conversionRate: Number(conversionRate || 1000)
    }
  });

  return { wallet, transaction, duplicate: false };
}

const RECOVERY_WORDS = [
  'ais', 'for', 'node', 'proof', 'chain', 'backup', 'cloud', 'memo', 'voice', 'photo',
  'secure', 'vault', 'restore', 'ledger', 'signal', 'orbit', 'trust', 'anchor', 'block', 'pulse',
  'garden', 'river', 'silver', 'future', 'memory', 'daily', 'keeper', 'bridge', 'light', 'stone',
  'dream', 'token', 'point', 'space', 'clear', 'quiet', 'green', 'blue', 'alpha', 'delta'
];

function walletError(message, status = 400, code = 'WALLET_ERROR') {
  const error = new Error(message);
  error.status = status;
  error.code = code;
  return error;
}

function randomRecoveryPhrase() {
  const words = [];
  for (let i = 0; i < 12; i += 1) {
    const index = crypto.randomInt(0, RECOVERY_WORDS.length);
    words.push(RECOVERY_WORDS[index]);
  }
  return words.join(' ');
}

function deriveVaultKey(pin, saltHex) {
  pinHash(pin);
  return crypto.scryptSync(String(pin).trim(), Buffer.from(saltHex, 'hex'), 32);
}

function encryptVaultPayload(payload, pin) {
  const salt = crypto.randomBytes(16);
  const iv = crypto.randomBytes(12);
  const key = deriveVaultKey(pin, salt.toString('hex'));
  const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
  const plaintext = Buffer.from(JSON.stringify(payload), 'utf8');
  const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
  return {
    version: 1,
    algorithm: 'aes-256-gcm',
    kdf: 'scrypt-sha256',
    salt: salt.toString('hex'),
    iv: iv.toString('hex'),
    authTag: cipher.getAuthTag().toString('hex'),
    ciphertext: ciphertext.toString('hex')
  };
}

function decryptVaultPayload(vault, pin) {
  if (!vault || !vault.ciphertext) {
    throw walletError('Recovery vault is not enabled.', 404, 'RECOVERY_NOT_ENABLED');
  }
  try {
    const key = deriveVaultKey(pin, vault.salt);
    const decipher = crypto.createDecipheriv('aes-256-gcm', key, Buffer.from(vault.iv, 'hex'));
    decipher.setAuthTag(Buffer.from(vault.authTag, 'hex'));
    const plaintext = Buffer.concat([
      decipher.update(Buffer.from(vault.ciphertext, 'hex')),
      decipher.final()
    ]).toString('utf8');
    return JSON.parse(plaintext);
  } catch (error) {
    throw walletError('PIN verification failed for recovery vault.', 403, 'INVALID_PIN');
  }
}

function buildMockPrivateKey(wallet, recoveryPhrase) {
  const seed = wallet.address + ':' + recoveryPhrase + ':' + wallet.publicKey;
  return 'plai_mock_private_' + sha256(seed).slice(0, 64);
}

function normalizeRecoveryExport(payload) {
  if (!payload || typeof payload !== 'object') return payload;
  return {
    ...payload,
    privateKey: String(payload.privateKey || '').replace(/^ais_mock_private_/, 'plai_mock_private_')
  };
}

function recoveryPayloadForWallet(wallet, recoveryPhrase) {
  const privateKey = buildMockPrivateKey(wallet, recoveryPhrase);
  return {
    chainName: COIN_CONFIG.chainName,
    symbol: COIN_CONFIG.symbol,
    address: wallet.address,
    publicKey: wallet.publicKey,
    recoveryPhrase,
    privateKey,
    createdAt: nowIso(),
    warning: 'Server-managed testnet wallet recovery material. Keep it private.'
  };
}

function getWalletRecoveryStatus(addressOrWallet) {
  const wallet = typeof addressOrWallet === 'string' ? getWallet(addressOrWallet) : addressOrWallet;
  if (!wallet) return null;
  const vault = wallet.keyVault || null;
  return {
    enabled: Boolean(vault && vault.ciphertext),
    version: vault?.version || null,
    algorithm: vault?.algorithm || null,
    kdf: vault?.kdf || null,
    recoveryPhraseHash: wallet.recoveryPhraseHash || null,
    privateKeyHash: wallet.privateKeyHash || null,
    encryptedPrivateKeyStored: Boolean(vault && vault.ciphertext),
    recoveryConfirmedAt: wallet.metadata?.recoveryConfirmedAt || null,
    restoredAt: wallet.metadata?.recoveryRestoredAt || null,
    createdAt: vault?.createdAt || null,
    updatedAt: vault?.updatedAt || null
  };
}

function enableWalletRecovery(address, pin) {
  const wallet = getWallet(address);
  if (!wallet) throw walletError('Wallet not found.', 404, 'WALLET_NOT_FOUND');
  if (!walletHasPin(wallet.address)) {
    throw walletError('Enable PIN before creating recovery vault.', 400, 'PIN_REQUIRED');
  }
  if (!verifyWalletPin(wallet.address, pin)) {
    throw walletError('Invalid wallet PIN.', 403, 'INVALID_PIN');
  }
  if (wallet.keyVault && wallet.keyVault.ciphertext) {
    return {
      status: getWalletRecoveryStatus(wallet),
      export: normalizeRecoveryExport(decryptVaultPayload(wallet.keyVault, pin))
    };
  }
  const recoveryPhrase = randomRecoveryPhrase();
  const payload = recoveryPayloadForWallet(wallet, recoveryPhrase);
  wallet.keyVault = {
    ...encryptVaultPayload(payload, pin),
    createdAt: nowIso(),
    updatedAt: nowIso()
  };
  wallet.recoveryPhraseHash = sha256(payload.recoveryPhrase);
  wallet.privateKeyHash = sha256(payload.privateKey);
  wallet.updatedAt = nowIso();
  anchorWalletEvent('WALLET_RECOVERY_ENABLE', {
    wallet,
    metadata: { recovery: 'enabled' }
  });
  persistentStore.writeState(state);
  return {
    status: getWalletRecoveryStatus(wallet),
    export: payload
  };
}

function exportWalletRecovery(address, pin) {
  const wallet = getWallet(address);
  if (!wallet) throw walletError('Wallet not found.', 404, 'WALLET_NOT_FOUND');
  if (!walletHasPin(wallet.address)) {
    throw walletError('PIN is required.', 400, 'PIN_REQUIRED');
  }
  if (!verifyWalletPin(wallet.address, pin)) {
    throw walletError('Invalid wallet PIN.', 403, 'INVALID_PIN');
  }
  return normalizeRecoveryExport(decryptVaultPayload(wallet.keyVault, pin));
}

function confirmWalletRecovery(address, pin, recoveryPhrase) {
  const wallet = getWallet(address);
  if (!wallet) throw walletError('Wallet not found.', 404, 'WALLET_NOT_FOUND');
  const phrase = String(recoveryPhrase || '').trim().replace(/\s+/g, ' ');
  if (!phrase) throw walletError('Recovery phrase is required.', 400, 'RECOVERY_PHRASE_REQUIRED');
  const exported = exportWalletRecovery(wallet.address, pin);
  if (sha256(phrase) !== wallet.recoveryPhraseHash || phrase !== exported.recoveryPhrase) {
    throw walletError('Recovery phrase does not match this wallet.', 403, 'RECOVERY_PHRASE_MISMATCH');
  }
  wallet.metadata = wallet.metadata || {};
  wallet.metadata.recoveryConfirmedAt = nowIso();
  wallet.updatedAt = nowIso();
  anchorWalletEvent('WALLET_RECOVERY_CONFIRM', {
    wallet,
    metadata: { recovery: 'confirmed' }
  });
  save();
  return getWalletRecoveryStatus(wallet);
}

function restoreWalletWithRecoveryPhrase(recoveryPhrase, newPin) {
  const phrase = String(recoveryPhrase || '').trim().replace(/\s+/g, ' ');
  if (!phrase) throw walletError('Recovery phrase is required.', 400, 'RECOVERY_PHRASE_REQUIRED');
  pinHash(newPin);
  const phraseHash = sha256(phrase);
  const wallet = listWallets().find(row => row.recoveryPhraseHash === phraseHash);
  if (!wallet) throw walletError('No wallet matched this recovery phrase.', 404, 'RECOVERY_WALLET_NOT_FOUND');

  const payload = recoveryPayloadForWallet(wallet, phrase);
  wallet.keyVault = {
    ...encryptVaultPayload(payload, newPin),
    createdAt: wallet.keyVault?.createdAt || nowIso(),
    updatedAt: nowIso()
  };
  wallet.privateKeyHash = sha256(payload.privateKey);
  wallet.metadata = wallet.metadata || {};
  wallet.metadata.pinHash = pinHash(newPin);
  wallet.metadata.pinEnabledAt = wallet.metadata.pinEnabledAt || nowIso();
  wallet.metadata.recoveryRestoredAt = nowIso();
  wallet.updatedAt = nowIso();
  anchorWalletEvent('WALLET_RECOVERY_RESTORE', {
    wallet,
    metadata: { recovery: 'restored' }
  });
  save();
  return { wallet, recovery: getWalletRecoveryStatus(wallet) };
}

function restoreOwnedWalletWithRecoveryPhrase({ ownerType, ownerId, recoveryPhrase, newPin } = {}) {
  const safeOwnerType = String(ownerType || '').trim();
  const safeOwnerId = normalizeOwnerId(ownerId);
  const phrase = String(recoveryPhrase || '').trim().replace(/\s+/g, ' ');
  if (!phrase) throw walletError('Recovery phrase is required.', 400, 'RECOVERY_PHRASE_REQUIRED');
  pinHash(newPin);

  const phraseHash = sha256(phrase);
  const wallet = listWallets().find(row =>
    row.recoveryPhraseHash === phraseHash &&
    row.ownerType === safeOwnerType &&
    row.ownerId === safeOwnerId
  );
  if (!wallet) throw walletError('No wallet matched this recovery phrase.', 404, 'RECOVERY_WALLET_NOT_FOUND');

  const payload = recoveryPayloadForWallet(wallet, phrase);
  wallet.keyVault = {
    ...encryptVaultPayload(payload, newPin),
    createdAt: wallet.keyVault?.createdAt || nowIso(),
    updatedAt: nowIso()
  };
  wallet.privateKeyHash = sha256(payload.privateKey);
  wallet.metadata = wallet.metadata || {};
  wallet.metadata.pinHash = pinHash(newPin);
  wallet.metadata.pinEnabledAt = wallet.metadata.pinEnabledAt || nowIso();
  wallet.metadata.recoveryRestoredAt = nowIso();
  wallet.updatedAt = nowIso();
  anchorWalletEvent('WALLET_RECOVERY_RESTORE', {
    wallet,
    metadata: { recovery: 'restored', ownerChecked: true }
  });
  save();
  return { wallet, recovery: getWalletRecoveryStatus(wallet) };
}

function restoreOwnedWalletWithPrivateKey({ ownerType, ownerId, privateKey, newPin } = {}) {
  const safeOwnerType = String(ownerType || '').trim();
  const safeOwnerId = normalizeOwnerId(ownerId);
  const key = String(privateKey || '').trim().replace(/^ais_mock_private_/, 'plai_mock_private_');
  if (!key) throw walletError('Private key is required.', 400, 'PRIVATE_KEY_REQUIRED');
  pinHash(newPin);

  const keyHash = sha256(key);
  const wallet = listWallets().find(row =>
    row.privateKeyHash === keyHash &&
    row.ownerType === safeOwnerType &&
    row.ownerId === safeOwnerId
  );
  if (!wallet) throw walletError('No wallet matched this private key.', 404, 'PRIVATE_KEY_WALLET_NOT_FOUND');

  const payload = {
    chainName: COIN_CONFIG.chainName,
    symbol: COIN_CONFIG.symbol,
    address: wallet.address,
    publicKey: wallet.publicKey,
    recoveryPhrase: '',
    privateKey: key,
    createdAt: nowIso(),
    warning: 'Server-managed testnet wallet recovery material. Keep it private.'
  };

  wallet.keyVault = {
    ...encryptVaultPayload(payload, newPin),
    createdAt: wallet.keyVault?.createdAt || nowIso(),
    updatedAt: nowIso()
  };
  wallet.privateKeyHash = keyHash;
  wallet.metadata = wallet.metadata || {};
  wallet.metadata.pinHash = pinHash(newPin);
  wallet.metadata.pinEnabledAt = wallet.metadata.pinEnabledAt || nowIso();
  wallet.metadata.recoveryRestoredAt = nowIso();
  wallet.metadata.recoveryRestoreMethod = 'private-key';
  wallet.updatedAt = nowIso();
  anchorWalletEvent('WALLET_PRIVATE_KEY_RESTORE', {
    wallet,
    metadata: { recovery: 'restored', ownerChecked: true, method: 'private-key' }
  });
  save();
  return { wallet, recovery: getWalletRecoveryStatus(wallet) };
}

function ensureServiceWallets(serviceNames = []) {
  const names = Array.from(new Set((serviceNames || []).map(name => String(name || '').trim().toUpperCase()).filter(Boolean)));
  const created = [];

  for (const serviceName of names) {
    const existing = state.wallets.find(wallet => wallet.ownerType === 'service' && wallet.ownerId === serviceName);
    if (existing) continue;

    const wallet = createWalletRecord({
      label: `${serviceName} Service Wallet`,
      ownerType: 'service',
      ownerId: serviceName,
      initialBalance: 0,
      metadata: {
        serviceName,
        purpose: 'service-account-settlement'
      }
    });
    state.wallets.unshift(wallet);
    created.push(wallet);
  }

  if (created.length) save();
  return names.map(serviceName => state.wallets.find(wallet => wallet.ownerType === 'service' && wallet.ownerId === serviceName)).filter(Boolean);
}

function getFoundationWallet() {
  return ensureFoundationWallet();
}

function pinHash(pin) {
  const value = String(pin || '').trim();
  if (!/^\d{4,12}$/.test(value)) {
    const err = new Error('PIN must be 4 to 12 digits');
    err.status = 400;
    err.code = 'INVALID_PIN';
    throw err;
  }
  return sha256(`wallet-pin:${value}`);
}

function walletHasPin(address) {
  const wallet = getWallet(address);
  return !!(wallet && wallet.metadata && wallet.metadata.pinHash);
}

function setWalletPin(address, pin) {
  const wallet = getWallet(address);
  if (!wallet) {
    const err = new Error('Wallet not found');
    err.status = 404;
    err.code = 'WALLET_NOT_FOUND';
    throw err;
  }
  wallet.metadata = wallet.metadata || {};
  wallet.metadata.pinHash = pinHash(pin);
  wallet.metadata.pinEnabledAt = nowIso();
  wallet.updatedAt = nowIso();
  anchorWalletEvent('WALLET_PIN_SET', {
    wallet,
    metadata: { pin: 'enabled' }
  });
  save();
  return wallet;
}

function verifyWalletPin(address, pin) {
  const wallet = getWallet(address);
  if (!wallet) {
    const err = new Error('Wallet not found');
    err.status = 404;
    err.code = 'WALLET_NOT_FOUND';
    throw err;
  }
  const expected = wallet.metadata && wallet.metadata.pinHash;
  if (!expected) return true;
  let actual;
  try {
    actual = pinHash(pin);
  } catch (err) {
    const pinErr = new Error('PIN is required.');
    pinErr.status = 400;
    pinErr.code = 'PIN_REQUIRED';
    throw pinErr;
  }
  if (actual !== expected) {
    const err = new Error('Invalid wallet PIN');
    err.status = 403;
    err.code = 'INVALID_PIN';
    throw err;
  }
  return true;
}
function getStats() {
  const wallets = listWallets();
  const tx = listWalletTransactions();
  const circulatingSupply = wallets
    .filter(wallet => wallet.ownerType !== 'foundation')
    .reduce((sum, wallet) => sum + Number(wallet.balance || 0), 0);
  const totalWalletBalance = wallets.reduce((sum, wallet) => sum + Number(wallet.balance || 0), 0);

  return {
    coin: COIN_CONFIG,
    walletCount: wallets.length,
    transactionCount: tx.length,
    totalSupply: COIN_CONFIG.totalSupply,
    circulatingSupply,
    treasuryBalance: Number(ensureFoundationWallet().balance || 0),
    totalWalletBalance
  };
}

module.exports = {
  COIN_CONFIG,
  createWallet,
  ensureOwnedWalletWithRecoveryExport,
  reissueOwnedWalletRecoveryExport,
  ensureFoundationWallet,
  ensureServiceWallets,
  getFoundationWallet,
  getWalletByOwner,
  ensureUserWallet,
  listUserWallets,
  getStats,
  deleteEmptyOwnedWallet,
  setWalletPin,
  verifyWalletPin,
  walletHasPin,
  getWalletRecoveryStatus,
  enableWalletRecovery,
  exportWalletRecovery,
  confirmWalletRecovery,
  restoreWalletWithRecoveryPhrase,
  restoreOwnedWalletWithRecoveryPhrase,
  restoreOwnedWalletWithPrivateKey,
  getWallet,
  getWalletTransaction,
  getWalletTransactions,
  listWalletTransactions,
  listWallets,
  creditPointConversion,
  transfer
};
