import { Router, type Request, type Response } from 'express';
import type { ExternalTransferRequest, TradeFinanceInstrument, TokenLoadRequest, TransferRequest } from '@dbis/settlement-core';
import { buildMoneySupplySnapshot, loadOmnlChainRegistry, getActiveChains } from '@dbis/settlement-core';
import { loadConfig, loadOfficeProfile, settlementOfficeId } from '../../config';
import {
  getMoneySupply,
  getSettlementStatus,
  processExternalTransfer,
} from '../../workflows/external-transfer';
import { processTokenLoad } from '../../workflows/token-load';
import { processTransfer } from '../../workflows/transfer';
import { bindOffice24 } from '../../workflows/office-scope';
import { settlementStore } from '../../store/settlement-store';
import { processSwiftInbound } from '../../workflows/swift-inbound';
import { fetchGlBalances, fineractOfficeReachable } from '../../adapters/fineract';
import { requireApiKey } from '../../middleware/auth';

async function respondPublicMoneySupply(res: Response, officeId: number): Promise<void> {
  const cfg = loadConfig();
  const allow =
    cfg.production.onlineBankMode || process.env.OMNL_PUBLIC_MONEY_SUPPLY === '1';
  if (!allow) {
    res.status(404).json({ error: 'Public money supply disabled' });
    return;
  }
  try {
    const [balances, fineractConnected] = await Promise.all([
      fetchGlBalances(officeId),
      fineractOfficeReachable(officeId),
    ]);
    const snapshot = buildMoneySupplySnapshot(officeId, balances);
    const hasActivity = Object.values(balances).some((v) => Math.abs(parseFloat(v) || 0) > 0);
    res.json({
      ...snapshot,
      fineractConnected,
      fineractLive: fineractConnected && hasActivity,
      interoffice: {
        dueFromHeadOffice: balances['1410'] ?? '0',
        gl1410: balances['1410'] ?? '0',
      },
    });
  } catch (e) {
    res.status(500).json({ error: e instanceof Error ? e.message : String(e) });
  }
}

export function createSettlementRouter(): Router {
  const router = Router();
  const officeId = settlementOfficeId();
  const profile = loadOfficeProfile();

  router.get('/health/ledger', (_req, res) => respondPublicMoneySupply(res, officeId));

  router.get('/health', async (req, res) => {
    let chainStats = { total: 0, active: 0 };
    try {
      const reg = loadOmnlChainRegistry();
      chainStats = { total: reg.chains.length, active: getActiveChains().length };
    } catch {
      /* optional */
    }
    const payload: Record<string, unknown> = {
      service: 'omnl-settlement-middleware',
      status: 'ok',
      mode: process.env.SETTLEMENT_MIDDLEWARE_CONFIG?.includes('production') ? 'production' : 'standard',
      role: 'OMNL central-bank settlement orchestration',
      chainSupport: { max: 128, ...chainStats },
      settlementOffice: {
        officeId: profile.officeId,
        externalId: profile.externalId,
        name: profile.name,
      },
      enforceSingleOffice: loadConfig().enforceSingleOffice ?? false,
    };
    const includeLedger =
      req.query.ledger === '1' ||
      req.query.include === 'money-supply' ||
      req.query.include === 'ledger';
    if (includeLedger) {
      const cfg = loadConfig();
      const allow =
        cfg.production.onlineBankMode || process.env.OMNL_PUBLIC_MONEY_SUPPLY === '1';
      if (allow) {
        try {
          const [balances, fineractConnected] = await Promise.all([
            fetchGlBalances(officeId),
            fineractOfficeReachable(officeId),
          ]);
          const snapshot = buildMoneySupplySnapshot(officeId, balances);
          const hasActivity = Object.values(balances).some((v) => Math.abs(parseFloat(v) || 0) > 0);
          payload.moneySupply = {
            ...snapshot,
            fineractConnected,
            fineractLive: fineractConnected && hasActivity,
            interoffice: {
              dueFromHeadOffice: balances['1410'] ?? '0',
              gl1410: balances['1410'] ?? '0',
            },
          };
        } catch (e) {
          payload.moneySupplyError = e instanceof Error ? e.message : String(e);
        }
      }
    }
    res.json(payload);
  });

  router.get('/office', (_req, res) => {
    res.json({
      ...profile,
      locked: true,
      storePath: `data/settlement-store/office-${officeId}`,
    });
  });

  router.get('/public/money-supply', (_req, res) => respondPublicMoneySupply(res, officeId));

  router.get('/money-supply/public', (_req, res) => respondPublicMoneySupply(res, officeId));

  router.get('/money-supply', async (req, res) => {
    if (!requireApiKey(req, res)) return;
    try {
      res.json(await getMoneySupply(officeId));
    } catch (e) {
      res.status(500).json({ error: e instanceof Error ? e.message : String(e) });
    }
  });

  router.get('/money-supply/:officeIdParam', async (req, res) => {
    if (!requireApiKey(req, res)) return;
    const requested = parseInt(req.params.officeIdParam, 10);
    if (!Number.isFinite(requested)) {
      res.status(404).json({ error: 'Not found' });
      return;
    }
    try {
      res.json(await getMoneySupply(settlementOfficeId(requested)));
    } catch (e) {
      res.status(400).json({ error: e instanceof Error ? e.message : String(e) });
    }
  });

  router.get('/settlements', (req, res) => {
    if (!requireApiKey(req, res)) return;
    res.json({
      officeId,
      items: settlementStore.list(Number(req.query.limit ?? 50)),
    });
  });

  router.get('/settlements/:id', (req, res) => {
    if (!requireApiKey(req, res)) return;
    const rec = getSettlementStatus(req.params.id);
    if (!rec) {
      res.status(404).json({ error: 'Not found' });
      return;
    }
    res.json(rec);
  });

  router.post('/external-transfer', async (req, res) => {
    if (!requireApiKey(req, res)) return;
    try {
      const merged = bindOffice24(req.body as ExternalTransferRequest);
      const record = await processExternalTransfer(merged);
      res.status(record.phase === 'FAILED' ? 422 : 200).json(record);
    } catch (e) {
      res.status(e instanceof Error && e.message.includes('locked') ? 403 : 500).json({
        error: e instanceof Error ? e.message : String(e),
      });
    }
  });

  router.post('/swift/inbound', async (req, res) => {
    if (!requireApiKey(req, res)) return;
    try {
      const raw = typeof req.body === 'string' ? req.body : req.body?.raw;
      if (!raw) {
        res.status(400).json({ error: 'raw SWIFT payload required' });
        return;
      }
      const record = await processSwiftInbound(String(raw));
      res.json(record);
    } catch (e) {
      res.status(500).json({ error: e instanceof Error ? e.message : String(e) });
    }
  });

  router.post('/trade-finance/:instrument', async (req, res) => {
    if (!requireApiKey(req, res)) return;
    const instrument = req.params.instrument.toUpperCase() as TradeFinanceInstrument;
    if (!['SBLC', 'DLC', 'BG', 'LS'].includes(instrument)) {
      res.status(400).json({ error: 'Invalid instrument' });
      return;
    }
    try {
      const body = req.body as ExternalTransferRequest;
      const merged = bindOffice24({
        ...body,
        tradeFinance: {
          instrument,
          reference: body.tradeFinance?.reference ?? body.idempotencyKey,
          expiryDate: body.tradeFinance?.expiryDate,
        },
        rail: 'SWIFT',
        moneyLayers: body.moneyLayers ?? ['M1', 'M2'],
      });
      const record = await processExternalTransfer(merged);
      res.status(record.phase === 'FAILED' ? 422 : 200).json(record);
    } catch (e) {
      res.status(500).json({ error: e instanceof Error ? e.message : String(e) });
    }
  });

  router.post('/convert/fiat-to-crypto', async (req, res) => {
    if (!requireApiKey(req, res)) return;
    try {
      const body = req.body as ExternalTransferRequest;
      const merged = bindOffice24({
        ...body,
        rail: body.rail ?? 'INTERNAL',
        convertToCrypto: {
          enabled: true,
          targetChainId: body.convertToCrypto?.targetChainId ?? 138,
          tokenLineId: body.convertToCrypto?.tokenLineId ?? `${body.currency ?? 'USD'}-M2`,
          recipientAddress: body.convertToCrypto?.recipientAddress ?? '',
        },
        moneyLayers: ['M2'],
      });
      const record = await processExternalTransfer(merged);
      res.status(record.phase === 'FAILED' ? 422 : 200).json(record);
    } catch (e) {
      res.status(500).json({ error: e instanceof Error ? e.message : String(e) });
    }
  });

  router.post('/token-load', async (req, res) => {
    if (!requireApiKey(req, res)) return;
    try {
      const body = req.body as TokenLoadRequest;
      if (!body.idempotencyKey || !body.lineId || !body.amount || !body.recipientAddress) {
        res.status(400).json({ error: 'idempotencyKey, lineId, amount, recipientAddress required' });
        return;
      }
      const record = await processTokenLoad({
        ...body,
        officeId: settlementOfficeId(body.officeId),
      });
      res.status(record.phase === 'FAILED' ? 422 : 200).json(record);
    } catch (e) {
      res.status(500).json({ error: e instanceof Error ? e.message : String(e) });
    }
  });

  router.post('/transfer/internal', async (req, res) => {
    if (!requireApiKey(req, res)) return;
    try {
      const body = req.body as TransferRequest;
      if (!body.idempotencyKey || !body.tokenAddress || !body.amount || !body.recipientAddress) {
        res.status(400).json({ error: 'idempotencyKey, tokenAddress, amount, recipientAddress required' });
        return;
      }
      const record = await processTransfer({
        ...body,
        officeId: settlementOfficeId(body.officeId),
        rail: 'INTERNAL',
        moneyLayers: body.moneyLayers ?? ['M2'],
      });
      res.status(record.phase === 'FAILED' ? 422 : 200).json(record);
    } catch (e) {
      res.status(500).json({ error: e instanceof Error ? e.message : String(e) });
    }
  });

  router.post('/transfer/external', async (req, res) => {
    if (!requireApiKey(req, res)) return;
    try {
      const body = req.body as TransferRequest;
      if (!body.creditorIban) {
        res.status(400).json({ error: 'creditorIban required for external transfer' });
        return;
      }
      const record = await processTransfer({
        ...body,
        officeId: settlementOfficeId(body.officeId),
        rail: body.rail === 'RTGS' ? 'RTGS' : 'SWIFT',
        moneyLayers: body.moneyLayers ?? ['M2'],
      });
      res.status(record.phase === 'FAILED' ? 422 : 200).json(record);
    } catch (e) {
      res.status(500).json({ error: e instanceof Error ? e.message : String(e) });
    }
  });

  router.get('/tokens/m2', (_req, res) => {
    try {
      const fs = require('fs') as typeof import('fs');
      const path = require('path') as typeof import('path');
      const p =
        process.env.OMNL_M2_TOKEN_REGISTRY ||
        path.resolve(__dirname, '../../../../../config/omnl-m2-token-registry.v1.json');
      const registry = JSON.parse(fs.readFileSync(p, 'utf8'));
      res.json(registry);
    } catch (e) {
      res.status(500).json({ error: e instanceof Error ? e.message : String(e) });
    }
  });

  router.get('/chains', (_req, res) => {
    try {
      const registry = loadOmnlChainRegistry();
      res.json({
        brand: registry.brand,
        maxCapacity: registry.maxCapacity,
        primaryChainId: registry.primaryChainId,
        mirrorChainId: registry.mirrorChainId,
        settlementOfficeId: registry.settlementOfficeId,
        stats: registry.stats,
        chains: registry.chains,
      });
    } catch (e) {
      res.status(500).json({ error: e instanceof Error ? e.message : String(e) });
    }
  });

  router.get('/chains/active', (_req, res) => {
    try {
      res.json({ count: getActiveChains().length, chains: getActiveChains() });
    } catch (e) {
      res.status(500).json({ error: e instanceof Error ? e.message : String(e) });
    }
  });

  return router;
}
