import { ethers } from 'ethers';
import { mapPaymentLeafToCanonical, usdStringToE8 } from '@dbis/checkpoint-core';
import type { PaymentLeaf } from './ingress/types';
import { effectiveValue } from './leafCodec';

const SURFACE_ABI = [
  'function notifyBatch(uint64 batchId, tuple(address participant,bytes32 chain138TxHash,bytes32 instructionId,uint8 direction,uint64 batchId,uint256 valueWei,uint64 valueUsdE8)[] items) external',
  'function notified(bytes32) view returns (bool)',
  'function notificationKey(address participant, bytes32 chain138TxHash, uint8 direction) view returns (bytes32)',
];

export const DIRECTION_CREDITED = 0;
export const DIRECTION_DEBITED = 1;

/** Matches Chain138ParticipantSurface wallet touch + top-level relayer calldata. */
export const CHAIN138_ATTESTATION_IFACE = new ethers.Interface([
  'function chain138Attestation(bytes32 chain138TxHash, uint8 direction, uint64 batchId)',
]);

/** One top-level mainnet tx per (participant, chain138TxHash, direction) — both wallets per 138 payment. */
export function buildTopLevelSurfaceTargets(
  leaves: PaymentLeaf[],
  batchId: bigint
): Array<{ participant: string; chain138TxHash: string; direction: number; batchId: bigint; blockNumber: number }> {
  const seen = new Set<string>();
  const out: Array<{
    participant: string;
    chain138TxHash: string;
    direction: number;
    batchId: bigint;
    blockNumber: number;
  }> = [];
  for (const leaf of leaves) {
    const pairs: Array<{ participant: string; direction: number }> = [
      { participant: leaf.from, direction: DIRECTION_DEBITED },
      { participant: leaf.to, direction: DIRECTION_CREDITED },
    ];
    for (const { participant, direction } of pairs) {
      if (!participant || participant === ethers.ZeroAddress) continue;
      const key = `${participant.toLowerCase()}:${leaf.txHash}:${direction}`;
      if (seen.has(key)) continue;
      seen.add(key);
      out.push({
        participant,
        chain138TxHash: leaf.txHash,
        direction,
        batchId,
        blockNumber: leaf.blockNumber,
      });
    }
  }
  return out;
}

function buildNotifications(
  leaves: PaymentLeaf[],
  batchId: bigint
): Array<{
  participant: string;
  chain138TxHash: string;
  instructionId: string;
  direction: number;
  batchId: bigint;
  valueWei: bigint;
  valueUsdE8: bigint;
}> {
  const items: Array<{
    participant: string;
    chain138TxHash: string;
    instructionId: string;
    direction: number;
    batchId: bigint;
    valueWei: bigint;
    valueUsdE8: bigint;
  }> = [];

  for (const leaf of leaves) {
    const iso = leaf.iso20022 ?? mapPaymentLeafToCanonical(leaf);
    const usd = leaf.totalTransfersUsd ?? leaf.valueUsd ?? leaf.nativeValueUsd ?? '0';
    const valueWei = effectiveValue(leaf);
    const valueUsdE8 = usdStringToE8(usd);

    items.push({
      participant: leaf.from,
      chain138TxHash: leaf.txHash,
      instructionId: iso.instructionIdBytes32,
      direction: DIRECTION_DEBITED,
      batchId,
      valueWei,
      valueUsdE8,
    });
    items.push({
      participant: leaf.to,
      chain138TxHash: leaf.txHash,
      instructionId: iso.instructionIdBytes32,
      direction: DIRECTION_CREDITED,
      batchId,
      valueWei,
      valueUsdE8,
    });
  }
  return items;
}

export async function surfaceParticipantsOnMainnet(
  wallet: ethers.Wallet,
  surfaceAddress: string,
  batchId: bigint,
  leaves: PaymentLeaf[]
): Promise<string | null> {
  if (!surfaceAddress || leaves.length === 0) return null;

  const surface = new ethers.Contract(surfaceAddress, SURFACE_ABI, wallet);
  const all = buildNotifications(leaves, batchId);
  const pending: typeof all = [];

  for (const item of all) {
    const key = await surface.notificationKey(item.participant, item.chain138TxHash, item.direction);
    try {
      if (await surface.notified(key)) continue;
    } catch {
      /* continue */
    }
    pending.push(item);
  }

  if (pending.length === 0) return null;

  const tx = await surface.notifyBatch(
    batchId,
    pending.map((p) => ({
      participant: p.participant,
      chain138TxHash: p.chain138TxHash,
      instructionId: p.instructionId,
      direction: p.direction,
      batchId: p.batchId,
      valueWei: p.valueWei,
      valueUsdE8: p.valueUsdE8,
    })),
    { gasLimit: 5_000_000n }
  );
  await tx.wait();
  return tx.hash as string;
}

/**
 * One top-level 0 ETH tx TO each participant role (sender + receiver per Chain 138 tx).
 * Shows on each wallet's Etherscan **Transactions** tab; calldata carries chain138TxHash.
 */
export async function surfaceTopLevelZeroEth(
  wallet: ethers.Wallet,
  leaves: PaymentLeaf[],
  batchId: bigint
): Promise<number> {
  const targets = buildTopLevelSurfaceTargets(leaves, batchId);
  let count = 0;
  for (const item of targets) {
    const code = await wallet.provider!.getCode(item.participant);
    if (code !== '0x') {
      console.warn(
        `  skip top-level surface for contract wallet ${item.participant} (use Internal Txns / surface events)`
      );
      continue;
    }
    const data = CHAIN138_ATTESTATION_IFACE.encodeFunctionData('chain138Attestation', [
      item.chain138TxHash,
      item.direction,
      item.batchId,
    ]);
    try {
      const tx = await wallet.sendTransaction({
        to: item.participant,
        value: 0n,
        data,
        gasLimit: 55_000n,
      });
      const receipt = await tx.wait();
      if (receipt?.status !== 1) {
        console.warn(`  top-level surface reverted for ${item.participant} tx=${tx.hash}`);
        continue;
      }
      count++;
    } catch (e) {
      console.warn(`  top-level surface failed for ${item.participant}`, e);
    }
  }
  return count;
}
