/**
 * ZEDXION relay: monitor LockForZedxion (138) and LockFor138 (83872), complete release on destination.
 * See docs/03-deployment/CHAIN138_ZEDXION_83872_BRIDGE_RUNBOOK.md
 */
import { ethers } from 'ethers';
import { config } from './config.js';
import { ZedxionCustomBridgeABI } from './abis.js';

export class ZedxionRelayService {
  constructor(logger) {
    this.logger = logger;
    this.outboundQueue = [];
    this.inboundQueue = [];
    this.inFlight = 0;
    this.processed = new Set();
  }

  async start() {
    if (!config.zedxionCustomBridgeHub || !config.zedxionCustomBridgeDest) {
      throw new Error('ZEDXION_CUSTOM_BRIDGE_138 and ZEDXION_CUSTOM_BRIDGE_83872 required');
    }
    if (!config.relayPrivateKey) {
      throw new Error('ZEDXION_RELAY_PRIVATE_KEY or PRIVATE_KEY required');
    }

    this.hubProvider = new ethers.JsonRpcProvider(config.hubRpcUrl);
    this.zedxProvider = new ethers.JsonRpcProvider(config.zedxionRpcUrl);
    this.hubSigner = new ethers.Wallet(config.relayPrivateKey, this.hubProvider);
    this.zedxSigner = new ethers.Wallet(config.relayPrivateKey, this.zedxProvider);

    this.hubBridge = new ethers.Contract(
      config.zedxionCustomBridgeHub,
      ZedxionCustomBridgeABI,
      this.hubProvider,
    );
    this.zedxBridge = new ethers.Contract(
      config.zedxionCustomBridgeDest,
      ZedxionCustomBridgeABI,
      this.zedxSigner,
    );
    this.hubBridgeWrite = new ethers.Contract(
      config.zedxionCustomBridgeHub,
      ZedxionCustomBridgeABI,
      this.hubSigner,
    );

    this.hubBridge.on(
      'LockForZedxion',
      (requestId, sender, token, amount, recipient, sourceChainId, event) => {
        this.enqueueOutbound({ requestId, token, amount, recipient, txHash: event.log.transactionHash });
      },
    );

    this.zedxBridge.on(
      'LockFor138',
      (requestId, sender, token, amount, recipient, sourceChainId, event) => {
        this.enqueueInbound({ requestId, token, amount, recipient, txHash: event.log.transactionHash });
      },
    );

    this.logger.info('Zedxion relay started', {
      hubBridge: config.zedxionCustomBridgeHub,
      zedxBridge: config.zedxionCustomBridgeDest,
    });

    setInterval(() => this.processQueues(), config.pollIntervalMs);
  }

  enqueueOutbound(msg) {
    const id = msg.requestId;
    if (this.processed.has(id)) return;
    if (this.outboundQueue.length + this.inboundQueue.length + this.inFlight >= config.queueDepthLimit) {
      this.logger.warn('Queue depth limit reached');
      return;
    }
    this.outboundQueue.push(msg);
  }

  enqueueInbound(msg) {
    const id = msg.requestId;
    if (this.processed.has(id)) return;
    if (this.outboundQueue.length + this.inboundQueue.length + this.inFlight >= config.queueDepthLimit) {
      this.logger.warn('Queue depth limit reached');
      return;
    }
    this.inboundQueue.push(msg);
  }

  async processQueues() {
    while (this.outboundQueue.length > 0 && this.inFlight < config.maxConcurrent) {
      const msg = this.outboundQueue.shift();
      this.inFlight++;
      this.completeOutbound(msg).finally(() => { this.inFlight--; });
    }
    while (this.inboundQueue.length > 0 && this.inFlight < config.maxConcurrent) {
      const msg = this.inboundQueue.shift();
      this.inFlight++;
      this.completeInbound(msg).finally(() => { this.inFlight--; });
    }
  }

  async resolveMirroredToken(canonicalToken) {
    try {
      const mirrored = await this.hubBridge.canonicalToMirrored(canonicalToken);
      if (mirrored && mirrored !== ethers.ZeroAddress) return mirrored;
    } catch (_) { /* fall through */ }
    return canonicalToken;
  }

  async resolveCanonicalToken(mirroredToken) {
    try {
      const canonical = await this.zedxBridge.mirroredToCanonical(mirroredToken);
      if (canonical && canonical !== ethers.ZeroAddress) return canonical;
    } catch (_) { /* fall through */ }
    return mirroredToken;
  }

  async completeOutbound({ requestId, token, amount, recipient }) {
    if (this.processed.has(requestId)) return;
    try {
      const mirrored = await this.resolveMirroredToken(token);
      const tx = await this.zedxBridge.releaseOnZedxion(requestId, mirrored, amount, recipient);
      await tx.wait();
      this.processed.add(requestId);
      this.logger.info('releaseOnZedxion ok', { requestId, mirrored, amount: amount.toString() });
    } catch (err) {
      this.logger.error('releaseOnZedxion failed', { requestId, error: err.message });
    }
  }

  async completeInbound({ requestId, token, amount, recipient }) {
    if (this.processed.has(requestId)) return;
    try {
      const canonical = await this.resolveCanonicalToken(token);
      const tx = await this.hubBridgeWrite.releaseOn138(requestId, canonical, amount, recipient);
      await tx.wait();
      this.processed.add(requestId);
      this.logger.info('releaseOn138 ok', { requestId, canonical, amount: amount.toString() });
    } catch (err) {
      this.logger.error('releaseOn138 failed', { requestId, error: err.message });
    }
  }
}
