/**
 * Bridge API: cross-chain bridge status and metrics.
 * GET /api/v1/bridge/routes — CCIP WETH9/WETH10 + Trustless (Snap / dApps).
 * GET /api/v1/bridge/status, /api/v1/bridge/metrics, /api/v1/bridge/preflight — GRU Transport readiness + cross-chain guidance.
 */

import { Router, Request, Response } from 'express';
import fs from 'fs';
import path from 'path';
import { fetchRemoteJson } from '../utils/fetch-remote-json';
import { buildDefaultBridgeRoutes } from '../utils/default-bridge-routes';
import { getActivePublicPools, getActiveTransportPairs, getGruTransportMetadata } from '../../config/gru-transport';
import { buildRelayLaneReport } from '../../indexer/relay-lane-indexer';
import { logger } from '../../utils/logger';

const router: Router = Router();

function buildGruTransportStatus() {
  const metadata = getGruTransportMetadata();
  const transportPairs = getActiveTransportPairs();
  const publicPools = getActivePublicPools();

  if (!metadata) return null;

  return {
    system: metadata.system,
    terminology: metadata.terminology,
    summary: metadata.counts,
    gasAssetFamilies: metadata.gasAssetFamilies ?? [],
    gasRedeemGroups: metadata.gasRedeemGroups ?? [],
    gasProtocolExposure: metadata.gasProtocolExposure ?? [],
    pairs: transportPairs.map((pair) => ({
      key: pair.key,
      canonicalChainId: pair.canonicalChainId,
      destinationChainId: pair.destinationChainId,
      canonicalSymbol: pair.canonicalSymbol,
      mirroredSymbol: pair.mirroredSymbol,
      assetClass: pair.assetClass ?? null,
      familyKey: pair.familyKey ?? null,
      laneGroup: pair.laneGroup ?? null,
      backingMode: pair.backingMode ?? null,
      redeemPolicy: pair.redeemPolicy ?? null,
      wrappedNativeQuoteSymbol: pair.wrappedNativeQuoteSymbol ?? null,
      stableQuoteSymbol: pair.stableQuoteSymbol ?? null,
      referenceVenue: pair.referenceVenue ?? null,
      eligible: pair.eligible === true,
      runtimeReady: pair.runtimeReady === true,
      runtimeBridgeReady: pair.runtimeBridgeReady === true,
      runtimeReserveVerifierReady: pair.runtimeReserveVerifierReady === true,
      runtimeMaxOutstandingReady: pair.runtimeMaxOutstandingReady === true,
      runtimeSupplyAccountingReady: pair.runtimeSupplyAccountingReady ?? null,
      supplyInvariantSatisfied: pair.supplyInvariantSatisfied ?? null,
      runtimeOutstandingValue: pair.runtimeOutstandingValue ?? null,
      runtimeEscrowedValue: pair.runtimeEscrowedValue ?? null,
      runtimeTreasuryBackedValue: pair.runtimeTreasuryBackedValue ?? null,
      runtimeTreasuryCapValue: pair.runtimeTreasuryCapValue ?? null,
      bridgeAvailable: pair.bridgeAvailable ?? null,
      protocolExposure: pair.protocolExposure ?? null,
      eligibilityBlockers: Array.isArray(pair.eligibilityBlockers) ? pair.eligibilityBlockers : [],
      runtimeMissingRequirements: Array.isArray(pair.runtimeMissingRequirements) ? pair.runtimeMissingRequirements : [],
      activePublicPoolKeys: Array.isArray(pair.publicPoolKeys) ? pair.publicPoolKeys : [],
    })),
    publicPools,
  };
}

function uniquePaths(paths: Array<string | undefined | null>): string[] {
  const seen = new Set<string>();
  const out: string[] = [];

  for (const candidate of paths) {
    if (typeof candidate !== 'string') continue;
    const trimmed = candidate.trim();
    if (!trimmed || seen.has(trimmed)) continue;
    seen.add(trimmed);
    out.push(trimmed);
  }

  return out;
}

/** Non-empty built-in CCIP / trustless lane counts for /bridge/metrics (telemetry-friendly). */
function summarizeBuiltInBridgeLanes() {
  const payload = buildDefaultBridgeRoutes();
  const trustlessKeys = payload.routes.trustless ? Object.keys(payload.routes.trustless) : [];
  const chain138 = payload.chain138Bridges as Record<string, string | undefined>;
  return {
    weth9Destinations: Object.keys(payload.routes.weth9).length,
    weth10Destinations: Object.keys(payload.routes.weth10).length,
    trustlessDestinations: trustlessKeys.length,
    chain138ConfiguredBridges: Object.keys(chain138).filter((k) => {
      const v = chain138[k];
      return typeof v === 'string' && v.startsWith('0x');
    }),
  };
}

function resolveBridgeRoutesPath(): string | null {
  const candidates = uniquePaths([
    process.env.BRIDGE_LIST_JSON_PATH,
    process.env.BRIDGE_ROUTES_JSON_PATH,
    path.resolve(process.cwd(), 'config/bridge-routes-chain138-default.json'),
    path.resolve(process.cwd(), '../config/bridge-routes-chain138-default.json'),
    path.resolve(process.cwd(), '../../config/bridge-routes-chain138-default.json'),
    path.resolve(__dirname, '../../../../../config/bridge-routes-chain138-default.json'),
  ]);

  for (const candidate of candidates) {
    if (fs.existsSync(candidate)) return candidate;
  }
  return null;
}

function loadRuntimeBridgeRoutes(): { payload: Record<string, unknown>; lastModified?: string } | null {
  const filePath = resolveBridgeRoutesPath();
  if (!filePath) return null;

  try {
    const raw = fs.readFileSync(filePath, 'utf8');
    const stat = fs.statSync(filePath);
    return {
      payload: JSON.parse(raw) as Record<string, unknown>,
      lastModified: stat.mtime.toISOString(),
    };
  } catch {
    return null;
  }
}

/**
 * GET /api/v1/bridge/routes
 * Optional BRIDGE_LIST_JSON_URL — remote JSON replaces entire payload (5m cache).
 */
router.get('/routes', async (_req: Request, res: Response) => {
  const gruTransportMetadata = getGruTransportMetadata();
  res.set('Cache-Control', 'public, max-age=0, must-revalidate');
  const url = process.env.BRIDGE_LIST_JSON_URL?.trim();
  if (url) {
    try {
      const data = await fetchRemoteJson(url);
      const basePayload = data && typeof data === 'object' ? data : { data };
      res.json({
        source: 'remote-url',
        ...basePayload,
        gruTransport: gruTransportMetadata
          ? {
              system: gruTransportMetadata.system,
              summary: gruTransportMetadata.counts,
              activeTransportPairs: getActiveTransportPairs(),
              activePublicPools: getActivePublicPools(),
            }
          : undefined,
      });
      return;
    } catch (e) {
      logger.error('BRIDGE_LIST_JSON_URL fetch failed, trying runtime file/built-in routes:', e);
    }
  }

  const runtimePayload = loadRuntimeBridgeRoutes();
  if (runtimePayload) {
    res.json({
      source: 'runtime-file',
      lastModified: runtimePayload.lastModified,
      ...runtimePayload.payload,
      gruTransport: gruTransportMetadata
        ? {
            system: gruTransportMetadata.system,
            summary: gruTransportMetadata.counts,
            activeTransportPairs: getActiveTransportPairs(),
            activePublicPools: getActivePublicPools(),
          }
        : undefined,
    });
    return;
  }

  res.json({
    source: 'built-in',
    ...buildDefaultBridgeRoutes(),
  });
});

router.get('/status', (_req: Request, res: Response) => {
  const gruTransport = buildGruTransportStatus();
  res.json({
    ok: true,
    bridges: [],
    gruTransport,
    message: 'Bridge status includes GRU Transport runtime readiness. Use /api/v1/bridge/preflight for missing refs and /api/v1/report/cross-chain for volume/lanes.',
  });
});

router.get('/metrics', (_req: Request, res: Response) => {
  const gruTransport = buildGruTransportStatus();
  const builtIn = summarizeBuiltInBridgeLanes();
  res.json({
    ok: true,
    lanes: [
      {
        kind: 'ccip-weth9',
        label: 'WETH9 bridge destinations (built-in catalog)',
        count: builtIn.weth9Destinations,
      },
      {
        kind: 'ccip-weth10',
        label: 'WETH10 bridge destinations (built-in catalog)',
        count: builtIn.weth10Destinations,
      },
      {
        kind: 'trustless',
        label: 'Trustless / Lockbox destinations (env-backed)',
        count: builtIn.trustlessDestinations,
      },
    ],
    chain138Bridges: builtIn.chain138ConfiguredBridges,
    gruTransport: gruTransport
      ? {
          system: gruTransport.system,
          summary: gruTransport.summary,
        }
      : null,
    message:
      'Lane counts reflect the built-in CCIP route catalog plus GRU Transport summary. Use /api/v1/bridge/routes for full JSON and /api/v1/report/cross-chain for volumes.',
  });
});

/**
 * GET /api/v1/bridge/messages?lane=138-monad
 * Custom relay lane tracker (Chain 138 ↔ Monad). Not indexed by ccip.chain.link.
 */
router.get('/messages', async (req: Request, res: Response) => {
  const lane = String(req.query.lane ?? '138-monad').trim().toLowerCase();
  if (lane !== '138-monad' && lane !== 'monad-138' && lane !== '138-monad-relay') {
    return res.status(400).json({
      ok: false,
      error: 'Unsupported lane',
      supported: ['138-monad', 'monad-138', '138-monad-relay'],
    });
  }

  const limit = Math.min(500, Math.max(1, Number(req.query.limit ?? 100) || 100));
  const fromBlockDays = Math.min(90, Math.max(1, Number(req.query.days ?? 30) || 30));
  const skipRpc = req.query.skipRpc === '1' || req.query.skipRpc === 'true';

  try {
    const report = await buildRelayLaneReport({ fromBlockDays, limit, skipRpc });
    res.set('Cache-Control', 'public, max-age=30, stale-while-revalidate=120');
    return res.json({
      ok: true,
      lane,
      ...report,
    });
  } catch (err) {
    logger.error('bridge/messages failed:', err);
    return res.status(503).json({
      ok: false,
      error: 'Relay lane indexer unavailable',
    });
  }
});

router.get('/preflight', (_req: Request, res: Response) => {
  const gruTransport = buildGruTransportStatus();
  if (!gruTransport) {
    return res.status(503).json({
      ok: false,
      error: 'GRU transport config not available',
    });
  }

  const blockedPairs = gruTransport.pairs.filter(
    (pair) => pair.eligible !== true || pair.runtimeReady !== true
  );
  const readyPairs = gruTransport.pairs.filter(
    (pair) => pair.eligible === true && pair.runtimeReady === true
  );

  return res.json({
    ok: blockedPairs.length === 0,
    generatedAt: new Date().toISOString(),
    gruTransport: {
      system: gruTransport.system,
      summary: gruTransport.summary,
      blockedPairs,
      readyPairs: readyPairs.map((pair) => ({
        key: pair.key,
        canonicalSymbol: pair.canonicalSymbol,
        mirroredSymbol: pair.mirroredSymbol,
        destinationChainId: pair.destinationChainId,
        assetClass: pair.assetClass ?? null,
        familyKey: pair.familyKey ?? null,
        backingMode: pair.backingMode ?? null,
        redeemPolicy: pair.redeemPolicy ?? null,
        supplyInvariantSatisfied: pair.supplyInvariantSatisfied ?? null,
      })),
    },
  });
});

export default router;
