import { ethers } from 'ethers';
import type { CanonicalPaymentMessage, PaymentLeafIsoInput } from './types';
import { MSG_TYPE } from './types';
import {
  hashShortUtf8,
  instructionIdFromTxHash,
  uetrBytes32FromTxHash,
  uetrFromTxHash,
} from './hashes';

/** Map Chain 138 payment leaf → MT-103 / pacs.008 equivalent canonical message. */
export function mapPaymentLeafToCanonical(leaf: PaymentLeafIsoInput): CanonicalPaymentMessage {
  const txHash = leaf.txHash;
  const instrBytes32 = instructionIdFromTxHash(txHash);
  const instrDisplay = `CHAIN138-${txHash.slice(2, 34)}`;
  const e2e = `E2E-${txHash.slice(2, 18)}`;
  const uetr = uetrFromTxHash(txHash);
  const usd = leaf.totalTransfersUsd ?? leaf.valueUsd ?? leaf.nativeValueUsd ?? '0';
  const currencyCode = leaf.tokenSymbol === 'ETH' || !leaf.token ? 'ETH' : 'USD';
  const purpose = `Chain138 settlement batch attestation; native value ${ethers.formatEther(leaf.value)} ETH; USD ref ${usd}`;

  const canonical: Omit<CanonicalPaymentMessage, 'payloadHash'> = {
    msgType: 'chain138.synthetic',
    msgTypeCode: MSG_TYPE.CHAIN138_SYNTH,
    instructionId: instrDisplay,
    instructionIdBytes32: instrBytes32,
    endToEndId: e2e,
    endToEndIdHash: hashShortUtf8(e2e),
    msgId: `MSG-${txHash.slice(2, 14)}`,
    uetr,
    uetrBytes32: uetrBytes32FromTxHash(txHash),
    accountRefId: leaf.from,
    counterpartyRefId: leaf.to,
    debtorId: leaf.from,
    creditorId: leaf.to,
    purpose,
    settlementMethod: 'CLRG',
    categoryPurpose: 'CBFF',
    currencyCode,
    amountRaw: ethers.formatEther(leaf.value),
    amountSmallestUnit: leaf.value.toString(),
    tokenAddress: leaf.token,
    debtorRefHash: hashShortUtf8(`${leaf.from}|${leaf.from}`),
    creditorRefHash: hashShortUtf8(`${leaf.to}|${leaf.to}`),
    purposeHash: hashShortUtf8(purpose),
    chain138TxHash: txHash,
    valueDateIso: new Date(leaf.blockTimestamp * 1000).toISOString().slice(0, 10),
  };

  const payloadHash = ethers.keccak256(
    ethers.toUtf8Bytes(
      JSON.stringify({
        ...canonical,
        blockNumber: leaf.blockNumber,
        blockTimestamp: leaf.blockTimestamp,
        receiptHash: leaf.receiptHash ?? null,
        logCount: leaf.logCount ?? 0,
      })
    )
  );

  return { ...canonical, payloadHash };
}

/** Minimal pacs.008.001 XML for OMNL store / audit (not full MX validation). */
export function canonicalToPacs008Xml(c: CanonicalPaymentMessage): string {
  const cre = new Date().toISOString();
  return `<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pacs.008.001.08">
  <FIToFICstmrCdtTrf>
    <GrpHdr>
      <MsgId>${escapeXml(c.msgId)}</MsgId>
      <CreDtTm>${escapeXml(cre)}</CreDtTm>
      <NbOfTxs>1</NbOfTxs>
    </GrpHdr>
    <CdtTrfTxInf>
      <PmtId>
        <InstrId>${escapeXml(c.instructionId)}</InstrId>
        <EndToEndId>${escapeXml(c.endToEndId)}</EndToEndId>
        <TxId>${escapeXml(c.chain138TxHash)}</TxId>
      </PmtId>
      <IntrBkSttlmAmt Ccy="${escapeXml(c.currencyCode)}">${escapeXml(c.amountRaw)}</IntrBkSttlmAmt>
      <Dbtr><Nm>${escapeXml(c.debtorId)}</Nm></Dbtr>
      <DbtrAcct><Id><Othr><Id>${escapeXml(c.accountRefId)}</Id></Othr></Id></DbtrAcct>
      <Cdtr><Nm>${escapeXml(c.creditorId)}</Nm></Cdtr>
      <CdtrAcct><Id><Othr><Id>${escapeXml(c.counterpartyRefId)}</Id></Othr></Id></CdtrAcct>
      <RmtInf><Ustrd>${escapeXml(c.purpose)}</Ustrd></RmtInf>
      <SplmtryData>
        <PlcAndNm>Chain138Attestation</PlcAndNm>
        <Envlp>
          <UETR>${escapeXml(c.uetr)}</UETR>
          <PayloadHash>${escapeXml(c.payloadHash)}</PayloadHash>
          <MsgTypeCode>${c.msgTypeCode}</MsgTypeCode>
        </Envlp>
      </SplmtryData>
    </CdtTrfTxInf>
  </FIToFICstmrCdtTrf>
</Document>`;
}

function escapeXml(s: string): string {
  return s
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;');
}
