import { v4 as uuidv4 } from 'uuid';
import {
  buildMoneySupplySnapshot,
  isIbanValid,
  m2FiatToTokenLoadAmount,
  resolveInstrumentGl,
  rollExternalTransferVerbiage,
  swiftKindForRequest,
  type ExternalTransferRequest,
  type SettlementRecord,
} from '@dbis/settlement-core';
import { loadConfig, loadOfficeProfile, settlementOfficeId } from '../config';
import { fetchGlBalances, postJournalEntry, resolveGlAccountId } from '../adapters/fineract';
import { HybxProductionRail } from '../adapters/hybx-production';
import { archiveIso20022, requestTokenMint } from '../adapters/omnl';
import { buildMt102Stub, buildMt103Stub, forwardSwiftToListener } from '../adapters/swift';
import { settlementStore } from '../store/settlement-store';

export async function processExternalTransfer(
  input: ExternalTransferRequest,
): Promise<SettlementRecord> {
  const cfg = loadConfig();
  const profile = loadOfficeProfile();
  const officeId = settlementOfficeId(input.officeId);
  const req: ExternalTransferRequest = { ...input, officeId };

  const existing = settlementStore.getByIdempotencyKey(req.idempotencyKey);
  if (existing?.phase === 'SETTLED') return existing;

  const now = new Date().toISOString();
  let record: SettlementRecord = existing ?? {
    settlementId: uuidv4(),
    idempotencyKey: req.idempotencyKey,
    phase: 'RECEIVED',
    request: req,
    errors: [],
    createdAt: now,
    updatedAt: now,
  };

  const advance = (phase: SettlementRecord['phase'], patch: Partial<SettlementRecord> = {}) => {
    record = { ...record, phase, updatedAt: new Date().toISOString(), ...patch };
    settlementStore.save(record);
  };

  try {
    if (!isIbanValid(req.creditorIban)) {
      throw new Error(`Invalid creditor IBAN: ${req.creditorIban}`);
    }
    advance('VALIDATED');

    const verbiage = rollExternalTransferVerbiage(req);
    advance('VERBIAGE_ROLLED', { verbiageDocument: verbiage });

    const amount = parseFloat(req.amount) || 0;
    const tf = req.tradeFinance;
    const gl = tf ? resolveInstrumentGl(tf.instrument) : null;
    const debitGl = gl?.debitGl ?? profile.settlement.glSettlement ?? cfg.moneySupply.glSettlement;
    const creditGl = gl?.creditGl ?? profile.settlement.glM1 ?? cfg.moneySupply.glM1;

    if (amount > 0) {
      const [debitGlId, creditGlId] = await Promise.all([
        resolveGlAccountId(debitGl),
        resolveGlAccountId(creditGl),
      ]);
      const je = await postJournalEntry({
        officeId: req.officeId,
        transactionDate: req.valueDate,
        referenceNumber: req.idempotencyKey,
        comments: verbiage.slice(0, 500),
        debits: [{ glAccountId: debitGlId, amount }],
        credits: [{ glAccountId: creditGlId, amount }],
      });
      advance('FINERACT_POSTED', { fineractJournalRef: String(je.resourceId) });
    } else {
      advance('FINERACT_POSTED');
    }

    if (cfg.rails.hybx.enabled && req.rail !== 'INTERNAL') {
      const hybx = new HybxProductionRail();
      const pay = await hybx.dispatchPayment({
        idempotencyKey: req.idempotencyKey,
        amount: req.amount,
        currency: req.currency,
        creditorIban: req.creditorIban,
        creditorBic: req.creditorBic,
        beneficiaryName: req.beneficiaryName,
        remittanceInfo: req.remittanceInfo,
        rail: req.rail,
      });
      advance('HYBX_RAIL_DISPATCHED', { hybxPaymentId: pay.paymentId });
    } else {
      advance('HYBX_RAIL_DISPATCHED');
    }

    const swiftKind = swiftKindForRequest(req);
    const swiftRaw =
      swiftKind === 'MT102'
        ? buildMt102Stub({
            senderRef: req.idempotencyKey,
            currency: req.currency,
            amount: req.amount,
            valueDate: req.valueDate,
            orderingInstitution: req.orderingName ?? cfg.omnlLegalName,
            beneficiary: req.beneficiaryName,
            iban: req.creditorIban,
            remittance: req.remittanceInfo,
          })
        : buildMt103Stub({
            senderRef: req.idempotencyKey,
            currency: req.currency,
            amount: req.amount,
            valueDate: req.valueDate,
            orderingCustomer: req.orderingName ?? cfg.omnlLegalName,
            beneficiary: req.beneficiaryName,
            iban: req.creditorIban,
            bic: req.creditorBic,
            remittance: req.remittanceInfo,
          });

    if (cfg.rails.swift.enabled) {
      await forwardSwiftToListener(swiftRaw);
    }
    const iso = await archiveIso20022({
      messageType: swiftKind === 'MT102' ? 'pacs.008' : 'pacs.008',
      direction: 'OUTBOUND',
      raw: swiftRaw,
      settlementRef: record.settlementId,
    });
    advance('ISO20022_ARCHIVED', { iso20022MessageId: iso.messageId });

    if (req.convertToCrypto?.enabled) {
      const balances = await fetchGlBalances(req.officeId);
      const snap = buildMoneySupplySnapshot(req.officeId, balances);
      const { loadAmount, fullyBacked } = m2FiatToTokenLoadAmount(
        req.amount,
        snap.M2.broadMoney,
      );
      if (!fullyBacked) {
        record.errors.push(`M2 backing insufficient for token load (${loadAmount}/${req.amount})`);
      }
      const mint = await requestTokenMint({
        lineId: req.convertToCrypto.tokenLineId,
        amount: loadAmount,
        recipient: req.convertToCrypto.recipientAddress,
        settlementRef: record.settlementId,
        dryRun: !cfg.production.allowChainMintExecute,
      });
      advance('CHAIN_MINT_REQUESTED', { chainTxHash: mint.txHash });
    } else {
      advance('CHAIN_MINT_REQUESTED');
    }

    advance('SETTLED');
    return record;
  } catch (err) {
    const msg = err instanceof Error ? err.message : String(err);
    record.errors.push(msg);
    advance('FAILED');
    return record;
  }
}

export async function getMoneySupply(officeId: number) {
  const balances = await fetchGlBalances(officeId);
  return buildMoneySupplySnapshot(officeId, balances);
}

export function getSettlementStatus(id: string): SettlementRecord | null {
  return settlementStore.getById(id);
}
