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

const state = persistentStore.readState();
state.trxCollateralAccounts = Array.isArray(state.trxCollateralAccounts) ? state.trxCollateralAccounts : [];
state.trxCollateralEvents = Array.isArray(state.trxCollateralEvents) ? state.trxCollateralEvents : [];
state.settings = state.settings && typeof state.settings === 'object' && !Array.isArray(state.settings) ? state.settings : {};

const BASE_COLLATERAL_CONFIG = {
  asset: 'TRX',
  network: 'TRON',
  mode: process.env.PLAI_TRX_COLLATERAL_MODE || 'mock_pending',
  minConfirmations: Number(process.env.PLAI_TRX_COLLATERAL_CONFIRMATIONS || 20),
  treasuryAddress: process.env.PLAI_TRX_TREASURY_ADDRESS || '',
  addressPrefix: process.env.PLAI_TRX_MOCK_ADDRESS_PREFIX || 'T',
  verifyDeposits: process.env.PLAI_TRX_VERIFY_DEPOSITS !== '0',
  note: 'TRX is used as temporary collateral before public PLAI collateral is available.'
};

const COLLATERAL_CONFIG = { ...BASE_COLLATERAL_CONFIG };

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

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

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

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

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

function isSameTrxAmount(left, right) {
  const a = normalizeAmount(left);
  const b = normalizeAmount(right);
  return a > 0 && b > 0 && Math.abs(a - b) <= 0.000001;
}

function normalizePositive(value, fallback) {
  const amount = normalizeAmount(value);
  return amount || normalizeAmount(fallback);
}

function ensureCollateralSettings() {
  state.settings = state.settings && typeof state.settings === 'object' && !Array.isArray(state.settings) ? state.settings : {};
  state.settings.trxCollateral = state.settings.trxCollateral && typeof state.settings.trxCollateral === 'object' && !Array.isArray(state.settings.trxCollateral)
    ? state.settings.trxCollateral
    : {};
  return state.settings.trxCollateral;
}

function refreshCollateralConfig() {
  const settings = ensureCollateralSettings();
  const defaultPerTb = normalizePositive(
    process.env.PLAI_TRX_COLLATERAL_PER_TB,
    100
  ) || 100;
  const trxPerTb = normalizePositive(settings.trxPerTb, defaultPerTb) || 100;
  const minimumCapacityTb = normalizePositive(settings.minimumCapacityTb, process.env.PLAI_TRX_MIN_NODE_CAPACITY_TB || 1) || 1;
  Object.assign(COLLATERAL_CONFIG, BASE_COLLATERAL_CONFIG, {
    policy: 'capacity_per_tb',
    unitCapacity: 'TB',
    trxPerTb,
    minimumCapacityTb,
    minNodeCollateral: normalizeAmount(trxPerTb * minimumCapacityTb),
    updatedAt: settings.updatedAt || null,
    updatedBy: settings.updatedBy || ''
  });
  return COLLATERAL_CONFIG;
}

function updateConfig({ trxPerTb, minimumCapacityTb = 1, updatedBy = 'admin' } = {}) {
  const nextTrxPerTb = normalizeAmount(trxPerTb);
  const nextMinimumCapacityTb = normalizeAmount(minimumCapacityTb);
  if (!nextTrxPerTb) throw collateralError('TRX per TB is required.', 400, 'INVALID_TRX_PER_TB');
  if (!nextMinimumCapacityTb) throw collateralError('Minimum capacity TB is required.', 400, 'INVALID_MINIMUM_CAPACITY_TB');
  const settings = ensureCollateralSettings();
  settings.trxPerTb = nextTrxPerTb;
  settings.minimumCapacityTb = nextMinimumCapacityTb;
  settings.updatedAt = nowIso();
  settings.updatedBy = String(updatedBy || 'admin').trim() || 'admin';
  save();
  return refreshCollateralConfig();
}

function calculateRequiredCollateral(capacityTb) {
  const config = refreshCollateralConfig();
  const safeCapacityTb = normalizePositive(capacityTb, config.minimumCapacityTb);
  return normalizeAmount(safeCapacityTb * config.trxPerTb);
}

function makeMockTrxAddress(ownerType, ownerId) {
  refreshCollateralConfig();
  const body = sha256(`trx-collateral:${ownerType}:${ownerId}`).slice(0, 33);
  return `${COLLATERAL_CONFIG.addressPrefix}${body}`;
}

function getDepositAddress(ownerType, ownerId) {
  refreshCollateralConfig();
  return COLLATERAL_CONFIG.treasuryAddress || makeMockTrxAddress(ownerType, ownerId);
}

function getDepositMemo(ownerId) {
  refreshCollateralConfig();
  return COLLATERAL_CONFIG.treasuryAddress ? `PLAI-${ownerId}` : '';
}

function collateralError(message, status, code) {
  const err = new Error(message);
  err.status = status;
  err.code = code;
  return err;
}

function anchorCollateralEvent(actionType, event) {
  chainTransactionService.submitTransaction({
    sourceService: 'PLAI_COLLATERAL',
    actionType,
    actorId: event.ownerId ? sha256(event.ownerId).slice(0, 24) : null,
    targetId: event.depositAddress ? sha256(event.depositAddress).slice(0, 24) : null,
    amount: normalizeAmount(event.amount || 0),
    metadata: {
      collateralEventId: event.id,
      asset: event.asset,
      network: event.network,
      status: event.status,
      externalTxHash: event.externalTxHash || '',
      depositAddressHash: event.depositAddress ? sha256(event.depositAddress).slice(0, 24) : null
    }
  }).then((chainTx) => {
    event.chainTransactionId = chainTx.id;
    event.chainTxHash = chainTx.txHash;
    event.chainStatus = chainTx.status;
    event.chainConfirmedAt = chainTx.confirmedAt || null;
    const mined = blockService.minePendingProofs({ limit: 1, reason: 'trx-collateral-event' });
    const anchored = chainTransactionService.getTransaction(chainTx.id || chainTx.txHash) || chainTx;
    event.ledgerBlockHeight = anchored.ledgerBlockHeight || event.ledgerBlockHeight || null;
    event.ledgerBlockHash = anchored.ledgerBlockHash || event.ledgerBlockHash || null;
    event.ledgerMinedAt = anchored.minedAt || event.ledgerMinedAt || null;
    if (mined && mined.created) event.ledgerAnchored = true;
    save();
  }).catch((error) => {
    event.chainAnchorError = error.message;
    save();
  });
}

function getRequiredCollateralForAccount(account) {
  refreshCollateralConfig();
  return normalizePositive(
    account && account.metadata ? account.metadata.requiredCollateral : 0,
    COLLATERAL_CONFIG.minNodeCollateral
  ) || COLLATERAL_CONFIG.minNodeCollateral;
}

function ensureAccount({ ownerType = 'aisfor_user', ownerId, label = '', metadata = {} } = {}) {
  refreshCollateralConfig();
  const safeOwnerType = String(ownerType || '').trim() || 'aisfor_user';
  const safeOwnerId = normalizeOwnerId(ownerId);
  if (!safeOwnerId) throw collateralError('Owner id is required.', 400, 'OWNER_ID_REQUIRED');
  const safeMetadata = metadata && typeof metadata === 'object' && !Array.isArray(metadata) ? metadata : {};

  let account = state.trxCollateralAccounts.find(row => row.ownerType === safeOwnerType && row.ownerId === safeOwnerId);
  if (account) {
    const expectedDepositAddress = getDepositAddress(safeOwnerType, safeOwnerId);
    const expectedDepositMemo = getDepositMemo(safeOwnerId);
    const expectedMockAddress = !COLLATERAL_CONFIG.treasuryAddress;
    const nextMetadata = {
      ...(account.metadata || {}),
      ...safeMetadata,
      mockAddress: expectedMockAddress,
      minConfirmations: COLLATERAL_CONFIG.minConfirmations,
      minNodeCollateral: COLLATERAL_CONFIG.minNodeCollateral
    };
    if (
      account.depositAddress !== expectedDepositAddress
      || account.depositMemo !== expectedDepositMemo
      || !account.metadata
      || account.metadata.mockAddress !== expectedMockAddress
      || account.metadata.minNodeCollateral !== COLLATERAL_CONFIG.minNodeCollateral
      || account.metadata.minConfirmations !== COLLATERAL_CONFIG.minConfirmations
      || JSON.stringify(account.metadata || {}) !== JSON.stringify(nextMetadata)
    ) {
      account.depositAddress = expectedDepositAddress;
      account.depositMemo = expectedDepositMemo;
      account.mode = COLLATERAL_CONFIG.mode;
      account.metadata = nextMetadata;
      account.updatedAt = nowIso();
      save();
    }
    return account;
  }

  const createdAt = nowIso();
  account = {
    id: uuidv4(),
    ownerType: safeOwnerType,
    ownerId: safeOwnerId,
    label: label || `${safeOwnerId} TRX Collateral`,
    asset: COLLATERAL_CONFIG.asset,
    network: COLLATERAL_CONFIG.network,
    depositAddress: getDepositAddress(safeOwnerType, safeOwnerId),
    depositMemo: getDepositMemo(safeOwnerId),
    confirmedAmount: 0,
    pendingAmount: 0,
    lockedAmount: 0,
    status: 'waiting_deposit',
    mode: COLLATERAL_CONFIG.mode,
    metadata: {
      ...safeMetadata,
      mockAddress: !COLLATERAL_CONFIG.treasuryAddress,
      minConfirmations: COLLATERAL_CONFIG.minConfirmations,
      minNodeCollateral: COLLATERAL_CONFIG.minNodeCollateral
    },
    createdAt,
    updatedAt: createdAt
  };
  state.trxCollateralAccounts.unshift(account);
  save();
  return account;
}

function listAccounts() {
  return state.trxCollateralAccounts;
}

function listEvents({ ownerType, ownerId, limit = 50 } = {}) {
  const safeOwnerType = ownerType ? String(ownerType).trim() : '';
  const safeOwnerId = ownerId ? normalizeOwnerId(ownerId) : '';
  return state.trxCollateralEvents
    .filter(event => (!safeOwnerType || event.ownerType === safeOwnerType) && (!safeOwnerId || event.ownerId === safeOwnerId))
    .slice(0, Math.max(1, Number(limit || 50)));
}

function getAccount({ ownerType = 'aisfor_user', ownerId } = {}) {
  const safeOwnerType = String(ownerType || '').trim() || 'aisfor_user';
  const safeOwnerId = normalizeOwnerId(ownerId);
  if (!safeOwnerId) return null;
  return state.trxCollateralAccounts.find(row => row.ownerType === safeOwnerType && row.ownerId === safeOwnerId) || null;
}

function findDuplicateEvent(externalTxHash) {
  const safeHash = String(externalTxHash || '').trim();
  if (!safeHash) return null;
  return state.trxCollateralEvents.find(event => String(event.externalTxHash || '').trim() === safeHash) || null;
}

function listUsedExternalTxHashes() {
  return state.trxCollateralEvents
    .map(event => String(event.externalTxHash || '').trim())
    .filter(Boolean);
}

function findRecheckableEvent({ ownerType, ownerId, amount, senderTrxAddress } = {}) {
  const safeOwnerType = String(ownerType || '').trim() || 'aisfor_user';
  const safeOwnerId = normalizeOwnerId(ownerId);
  const safeAmount = normalizeAmount(amount);
  const safeSender = String(senderTrxAddress || '').trim();
  if (!safeOwnerId || !safeAmount || !safeSender) return null;
  return state.trxCollateralEvents.find(event => (
    event.ownerType === safeOwnerType
    && event.ownerId === safeOwnerId
    && event.status !== 'CONFIRMED'
    && event.status !== 'REJECTED'
    && isSameTrxAmount(event.amount, safeAmount)
    && String(event.senderTrxAddress || '').trim() === safeSender
    && String(event.externalTxHash || '').trim()
  )) || null;
}

function requestDeposit({ ownerType = 'aisfor_user', ownerId, amount, externalTxHash = '', note = '', senderTrxAddress = '' } = {}) {
  refreshCollateralConfig();
  const account = ensureAccount({ ownerType, ownerId });
  const requiredCollateral = getRequiredCollateralForAccount(account);
  const safeAmount = normalizeAmount(amount);
  if (!safeAmount) throw collateralError('TRX amount is required.', 400, 'INVALID_TRX_AMOUNT');
  const safeExternalTxHash = String(externalTxHash || '').trim();
  const duplicate = findDuplicateEvent(safeExternalTxHash);
  if (duplicate) throw collateralError('TRX transaction hash already exists.', 409, 'DUPLICATE_TRX_TX');

  const createdAt = nowIso();
  const event = {
    id: uuidv4(),
    type: 'DEPOSIT_REQUEST',
    status: 'PENDING_REVIEW',
    ownerType: account.ownerType,
    ownerId: account.ownerId,
    accountId: account.id,
    asset: COLLATERAL_CONFIG.asset,
    network: COLLATERAL_CONFIG.network,
    depositAddress: account.depositAddress,
    senderTrxAddress: String(senderTrxAddress || '').trim(),
    amount: safeAmount,
    externalTxHash: safeExternalTxHash,
    note: String(note || '').trim(),
    confirmations: 0,
    createdAt,
    updatedAt: createdAt
  };

  account.pendingAmount = normalizeAmount(Number(account.pendingAmount || 0) + safeAmount);
  account.status = Number(account.confirmedAmount || 0) >= requiredCollateral ? 'active' : 'pending_review';
  account.updatedAt = createdAt;
  state.trxCollateralEvents.unshift(event);
  anchorCollateralEvent('TRX_COLLATERAL_DEPOSIT_REQUEST', event);
  save();
  return { account, event };
}

async function requestVerifiedDeposit({ ownerType = 'aisfor_user', ownerId, amount, externalTxHash = '', note = '', senderTrxAddress = '' } = {}) {
  refreshCollateralConfig();
  if (!COLLATERAL_CONFIG.treasuryAddress) {
    throw collateralError('Real TRX treasury address is not configured.', 400, 'TRX_TREASURY_REQUIRED');
  }
  const safeOwnerType = String(ownerType || '').trim() || 'aisfor_user';
  const safeOwnerId = normalizeOwnerId(ownerId);
  const safeAmount = normalizeAmount(amount);
  if (!safeAmount) throw collateralError('TRX amount is required.', 400, 'INVALID_TRX_AMOUNT');
  const safeExternalTxHash = String(externalTxHash || '').trim();
  let duplicate = findDuplicateEvent(safeExternalTxHash);
  if (safeExternalTxHash && duplicate && (duplicate.ownerType !== safeOwnerType || duplicate.ownerId !== safeOwnerId)) {
    throw collateralError('TRX transaction hash already exists.', 409, 'DUPLICATE_TRX_TX');
  }

  const recheckableEvent = safeExternalTxHash
    ? null
    : findRecheckableEvent({
      ownerType: safeOwnerType,
      ownerId: safeOwnerId,
      amount: safeAmount,
      senderTrxAddress
    });

  const verification = recheckableEvent
    ? await tronNetworkService.verifyTrxTransfer({
      txHash: recheckableEvent.externalTxHash,
      fromAddress: senderTrxAddress,
      toAddress: COLLATERAL_CONFIG.treasuryAddress,
      minAmount: safeAmount
    })
    : safeExternalTxHash
    ? await tronNetworkService.verifyTrxTransfer({
      txHash: safeExternalTxHash,
      fromAddress: senderTrxAddress,
      toAddress: COLLATERAL_CONFIG.treasuryAddress,
      minAmount: safeAmount
    })
    : await tronNetworkService.findIncomingTrxTransfer({
      fromAddress: senderTrxAddress,
      toAddress: COLLATERAL_CONFIG.treasuryAddress,
      minAmount: safeAmount,
      ignoredTxHashes: listUsedExternalTxHashes()
    });
  duplicate = findDuplicateEvent(verification.txHash);
  if (duplicate && (duplicate.ownerType !== safeOwnerType || duplicate.ownerId !== safeOwnerId)) {
    throw collateralError('TRX transaction hash already exists.', 409, 'DUPLICATE_TRX_TX');
  }
  if (!isSameTrxAmount(verification.amountTrx, safeAmount)) {
    throw collateralError('TRON transfer amount does not match requested amount.', 409, 'TRX_AMOUNT_MISMATCH');
  }
  if (duplicate) {
    duplicate.senderTrxAddress = String(senderTrxAddress || '').trim();
    duplicate.verification = {
      ...verification,
      minConfirmations: COLLATERAL_CONFIG.minConfirmations
    };
    duplicate.confirmations = verification.confirmations;
    duplicate.verifiedAt = nowIso();
    duplicate.updatedAt = duplicate.verifiedAt;
    let autoConfirmed = false;
    if (duplicate.status !== 'CONFIRMED' && verification.confirmations >= COLLATERAL_CONFIG.minConfirmations) {
      confirmDeposit(duplicate.id, {
        confirmedBy: 'tron-auto',
        externalTxHash: verification.txHash
      });
      autoConfirmed = true;
    } else {
      duplicate.status = duplicate.status === 'CONFIRMED' ? 'CONFIRMED' : 'PENDING_CONFIRMATIONS';
      save();
    }
    return {
      account: state.trxCollateralAccounts.find(row => row.id === duplicate.accountId) || null,
      event: duplicate,
      autoConfirmed
    };
  }
  const result = requestDeposit({
    ownerType: safeOwnerType,
    ownerId: safeOwnerId,
    amount: verification.amountTrx,
    externalTxHash: verification.txHash,
    note,
    senderTrxAddress
  });
  result.event.verification = {
    ...verification,
    minConfirmations: COLLATERAL_CONFIG.minConfirmations
  };
  result.event.confirmations = verification.confirmations;
  result.event.verifiedAt = nowIso();
  result.event.updatedAt = result.event.verifiedAt;

  if (verification.confirmations >= COLLATERAL_CONFIG.minConfirmations) {
    confirmDeposit(result.event.id, {
      confirmedBy: 'tron-auto',
      externalTxHash: verification.txHash
    });
    result.autoConfirmed = true;
  } else {
    result.event.status = 'PENDING_CONFIRMATIONS';
    save();
  }
  return result;
}

function confirmDeposit(eventId, { confirmedBy = 'admin', externalTxHash = '' } = {}) {
  refreshCollateralConfig();
  const event = state.trxCollateralEvents.find(row => row.id === eventId);
  if (!event) throw collateralError('Collateral event not found.', 404, 'COLLATERAL_EVENT_NOT_FOUND');
  if (event.status === 'CONFIRMED') return event;

  const account = state.trxCollateralAccounts.find(row => row.id === event.accountId);
  if (!account) throw collateralError('Collateral account not found.', 404, 'COLLATERAL_ACCOUNT_NOT_FOUND');

  const confirmedAt = nowIso();
  const amount = normalizeAmount(event.amount);
  const requiredCollateral = getRequiredCollateralForAccount(account);
  account.pendingAmount = normalizeAmount(Math.max(0, Number(account.pendingAmount || 0) - amount));
  account.confirmedAmount = normalizeAmount(Number(account.confirmedAmount || 0) + amount);
  account.status = account.confirmedAmount >= requiredCollateral ? 'active' : 'below_minimum';
  account.updatedAt = confirmedAt;

  event.status = 'CONFIRMED';
  event.confirmations = COLLATERAL_CONFIG.minConfirmations;
  event.confirmedBy = confirmedBy;
  event.confirmedAt = confirmedAt;
  event.updatedAt = confirmedAt;
  if (externalTxHash) event.externalTxHash = String(externalTxHash).trim();
  anchorCollateralEvent('TRX_COLLATERAL_DEPOSIT_CONFIRMED', event);
  save();
  return event;
}

function rejectDeposit(eventId, { rejectedBy = 'admin', reason = '' } = {}) {
  refreshCollateralConfig();
  const event = state.trxCollateralEvents.find(row => row.id === eventId);
  if (!event) throw collateralError('Collateral event not found.', 404, 'COLLATERAL_EVENT_NOT_FOUND');
  if (event.status === 'REJECTED') return event;
  const account = state.trxCollateralAccounts.find(row => row.id === event.accountId);
  const rejectedAt = nowIso();
  const amount = normalizeAmount(event.amount);
  if (account) {
    const requiredCollateral = getRequiredCollateralForAccount(account);
    account.pendingAmount = normalizeAmount(Math.max(0, Number(account.pendingAmount || 0) - amount));
    account.status = Number(account.confirmedAmount || 0) >= requiredCollateral ? 'active' : 'waiting_deposit';
    account.updatedAt = rejectedAt;
  }
  event.status = 'REJECTED';
  event.rejectedBy = rejectedBy;
  event.rejectReason = String(reason || '').trim();
  event.updatedAt = rejectedAt;
  anchorCollateralEvent('TRX_COLLATERAL_DEPOSIT_REJECTED', event);
  save();
  return event;
}

function getStats() {
  refreshCollateralConfig();
  const accounts = listAccounts();
  const events = state.trxCollateralEvents;
  return {
    config: { ...COLLATERAL_CONFIG },
    accountCount: accounts.length,
    eventCount: events.length,
    pendingEvents: events.filter(event => event.status === 'PENDING_REVIEW').length,
    confirmedAmount: normalizeAmount(accounts.reduce((sum, row) => sum + Number(row.confirmedAmount || 0), 0)),
    pendingAmount: normalizeAmount(accounts.reduce((sum, row) => sum + Number(row.pendingAmount || 0), 0)),
    activeAccounts: accounts.filter(row => row.status === 'active').length,
    latestEvents: events.slice(0, 10)
  };
}

refreshCollateralConfig();

module.exports = {
  COLLATERAL_CONFIG,
  refreshCollateralConfig,
  updateConfig,
  calculateRequiredCollateral,
  getRequiredCollateralForAccount,
  ensureAccount,
  getAccount,
  listAccounts,
  listEvents,
  requestDeposit,
  requestVerifiedDeposit,
  confirmDeposit,
  rejectDeposit,
  getStats
};
