const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const { Wallet, getAddress, isAddress, verifyMessage } = require('ethers');
const { EVM_CANDIDATE_CONFIG } = require('../config/evmCandidateConfig');
const evmTransferService = require('./evmTransferService');

const dataPath = path.join(__dirname, '..', 'data', 'evm-wallets.json');

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

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

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

function validatePin(pin) {
  const value = String(pin || '').trim();
  if (!/^\d{6,12}$/.test(value)) {
    throw walletError('PIN must be 6 to 12 digits.', 400, 'INVALID_EVM_PIN');
  }
  return value;
}

function getVaultSecret() {
  const secret = String(process.env.PLAIS_EVM_VAULT_SECRET || '').trim();
  if (secret.length < 32) {
    throw walletError('PLAIS EVM vault secret is not configured.', 503, 'EVM_VAULT_SECRET_MISSING');
  }
  return secret;
}

function readStore() {
  if (!fs.existsSync(dataPath)) return { version: 1, wallets: [] };
  try {
    const parsed = JSON.parse(fs.readFileSync(dataPath, 'utf8'));
    return {
      version: 1,
      wallets: Array.isArray(parsed.wallets) ? parsed.wallets : []
    };
  } catch (error) {
    throw walletError('EVM wallet store could not be read.', 500, 'EVM_WALLET_STORE_INVALID');
  }
}

function writeStore(store) {
  fs.mkdirSync(path.dirname(dataPath), { recursive: true });
  const tempPath = `${dataPath}.tmp`;
  fs.writeFileSync(tempPath, `${JSON.stringify(store, null, 2)}\n`, { mode: 0o600 });
  fs.renameSync(tempPath, dataPath);
}

function deriveVaultKey(pin, salt) {
  const secretBoundPin = `${validatePin(pin)}:${getVaultSecret()}`;
  return crypto.scryptSync(secretBoundPin, salt, 32, {
    N: 16384,
    r: 8,
    p: 1,
    maxmem: 64 * 1024 * 1024
  });
}

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

function decryptKeyMaterial(vault, pin) {
  try {
    const salt = Buffer.from(vault.salt, 'hex');
    const key = deriveVaultKey(pin, 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) {
    if (error.code === 'INVALID_EVM_PIN') throw error;
    throw walletError('PIN verification failed.', 403, 'INVALID_EVM_PIN');
  }
}

function toPublicWallet(wallet) {
  if (!wallet) return null;
  return {
    id: wallet.id,
    ownerType: wallet.ownerType,
    ownerId: wallet.ownerId,
    label: wallet.label,
    address: wallet.address,
    publicKey: wallet.publicKey,
    protocol: wallet.protocol,
    networkName: wallet.networkName,
    chainId: wallet.chainId,
    status: EVM_CANDIDATE_CONFIG.externalTransfersEnabled ? 'active' : wallet.status,
    createdAt: wallet.createdAt,
    updatedAt: wallet.updatedAt
  };
}

function getWalletByOwner(ownerType, ownerId) {
  const safeType = String(ownerType || '').trim();
  const safeId = normalizeOwnerId(ownerId);
  const row = readStore().wallets.find(wallet => wallet.ownerType === safeType && wallet.ownerId === safeId);
  return toPublicWallet(row || null);
}

function getStoredWalletByOwner(ownerType, ownerId) {
  const safeType = String(ownerType || '').trim();
  const safeId = normalizeOwnerId(ownerId);
  return readStore().wallets.find(wallet => wallet.ownerType === safeType && wallet.ownerId === safeId) || null;
}

function createWalletForOwner({ ownerType = 'aisfor_user', ownerId, label, pin } = {}) {
  if (!EVM_CANDIDATE_CONFIG.enabled) {
    throw walletError('PLAIS EVM candidate wallet is disabled.', 503, 'EVM_CANDIDATE_DISABLED');
  }
  const safeType = String(ownerType || '').trim();
  const safeId = normalizeOwnerId(ownerId);
  if (!safeType || !safeId) throw walletError('Wallet owner is required.', 400, 'EVM_OWNER_REQUIRED');
  const safePin = validatePin(pin);
  const store = readStore();
  const existing = store.wallets.find(wallet => wallet.ownerType === safeType && wallet.ownerId === safeId);
  if (existing) return { created: false, wallet: toPublicWallet(existing), recovery: null };

  const generated = Wallet.createRandom();
  const createdAt = nowIso();
  const recovery = {
    address: generated.address,
    privateKey: generated.privateKey,
    mnemonic: generated.mnemonic ? generated.mnemonic.phrase : '',
    networkName: EVM_CANDIDATE_CONFIG.networkName,
    chainId: EVM_CANDIDATE_CONFIG.chainId,
    createdAt
  };
  const record = {
    id: crypto.randomUUID(),
    ownerType: safeType,
    ownerId: safeId,
    label: String(label || 'PLAIS EVM Wallet').trim().slice(0, 100),
    address: getAddress(generated.address),
    publicKey: generated.signingKey.publicKey,
    protocol: 'evm-secp256k1-v1',
    networkName: EVM_CANDIDATE_CONFIG.networkName,
    chainId: EVM_CANDIDATE_CONFIG.chainId,
    status: EVM_CANDIDATE_CONFIG.externalTransfersEnabled ? 'active' : 'candidate_locked',
    keyVault: encryptKeyMaterial(recovery, safePin),
    createdAt,
    updatedAt: createdAt
  };
  store.wallets.push(record);
  writeStore(store);
  return { created: true, wallet: toPublicWallet(record), recovery };
}

function exportRecoveryForOwner({ ownerType = 'aisfor_user', ownerId, pin } = {}) {
  const wallet = getStoredWalletByOwner(ownerType, ownerId);
  if (!wallet) throw walletError('PLAIS EVM wallet was not found.', 404, 'EVM_WALLET_NOT_FOUND');
  return decryptKeyMaterial(wallet.keyVault, pin);
}

async function signMessageForOwner({ ownerType = 'aisfor_user', ownerId, pin, message } = {}) {
  const recovery = exportRecoveryForOwner({ ownerType, ownerId, pin });
  const signer = new Wallet(recovery.privateKey);
  const payload = String(message || 'PLAIS EVM candidate wallet verification');
  const signature = await signer.signMessage(payload);
  return {
    address: signer.address,
    message: payload,
    signature,
    verified: getAddress(verifyMessage(payload, signature)) === getAddress(signer.address)
  };
}

async function sendTransferForOwner({ ownerType = 'aisfor_user', ownerId, pin, to, amount, memo, idempotencyKey } = {}) {
  const recovery = exportRecoveryForOwner({ ownerType, ownerId, pin });
  return evmTransferService.sendSignedTransfer({
    privateKey: recovery.privateKey,
    to,
    amount,
    actorType: ownerType,
    actorId: ownerId,
    memo,
    idempotencyKey
  });
}

function validateAddress(address) {
  return isAddress(String(address || '').trim());
}

module.exports = {
  createWalletForOwner,
  exportRecoveryForOwner,
  getWalletByOwner,
  sendTransferForOwner,
  signMessageForOwner,
  toPublicWallet,
  validateAddress
};
