import { Router, Request, Response } from 'express';
import { existsSync, readFileSync, readdirSync } from 'fs';
import { join, resolve } from 'path';
import { logger } from '../../utils/logger';

const router: Router = Router();

function indexerBase(): string {
  const port = process.env.CHECKPOINT_INDEXER_PORT || '3100';
  return (
    process.env.CHECKPOINT_INDEXER_URL || `http://127.0.0.1:${port}`
  ).replace(/\/$/, '');
}

type MainnetAttestationLink = {
  layer: string;
  label: string;
  mainnetTxHash: string;
  mainnetExplorerUrl: string;
  contractAddress?: string;
  logIndex?: number;
  meta?: Record<string, string>;
};

function batchDir(): string {
  const custom = process.env.CHECKPOINT_BATCH_DIR?.trim();
  if (custom) return custom;
  const root =
    process.env.PROXMOX_ROOT?.trim() ||
    process.env.PHOENIX_REPO_ROOT?.trim() ||
    resolve(__dirname, '../../../../../..');
  return join(root, 'reports/checkpoint-indexer/batches');
}

type CheckpointLeaf = {
  txHash?: string;
  valueUsd?: string;
  nativeValueUsd?: string;
  tokenValueUsd?: string;
  totalTransfersUsd?: string;
  priceSource?: string;
    transfers?: Array<{
    token?: string;
    tokenAddress?: string;
    tokenSymbol?: string;
    symbol?: string;
    amountRaw?: string;
    decimals?: number;
    valueUsd?: string;
    priceSource?: string;
  }>;
};

function findLeaf(payload: { leaves?: CheckpointLeaf[] } | null, txHash: string): CheckpointLeaf | null {
  if (!payload?.leaves?.length) return null;
  const want = txHash.toLowerCase();
  return payload.leaves.find((l) => (l.txHash || '').toLowerCase() === want) ?? null;
}

function loadLocalAttestation(txHash: string): {
  included: boolean;
  batchId: string;
  batchTotalUsd?: string;
  leaf: CheckpointLeaf | null;
  batchMeta: {
    mainnetAttestation?: {
      hubTx?: string;
      mirrorTx?: string;
      activityRegistryV1Tx?: string;
      activityRegistryV2Tx?: string;
      participantSurfaceTx?: string;
      participantTopLevelTxs?: string[];
    };
  } | null;
} | null {
  const dir = batchDir();
  if (!existsSync(dir)) return null;
  const want = txHash.toLowerCase();
  const files = readdirSync(dir).filter((f) => /^batch-\d+\.json$/i.test(f));
  for (const file of files.sort((a, b) => {
    const na = parseInt(a.replace(/\D/g, ''), 10);
    const nb = parseInt(b.replace(/\D/g, ''), 10);
    return nb - na;
  })) {
    try {
      const raw = JSON.parse(readFileSync(join(dir, file), 'utf8')) as {
        batchId?: string;
        batchTotalUsd?: string;
        leaves?: CheckpointLeaf[];
        mainnetAttestation?: {
          hubTx?: string;
          mirrorTx?: string;
          activityRegistryV1Tx?: string;
          activityRegistryV2Tx?: string;
          participantSurfaceTx?: string;
          participantTopLevelTxs?: string[];
        };
      };
      const leaf = findLeaf(raw, want);
      if (!leaf) continue;
      const batchId = String(raw.batchId ?? file.replace(/^batch-|\.json$/gi, ''));
      return {
        included: true,
        batchId,
        batchTotalUsd: raw.batchTotalUsd,
        leaf,
        batchMeta: raw,
      };
    } catch {
      continue;
    }
  }
  return null;
}

function linksFromBatchMeta(raw: {
  mainnetAttestation?: {
    hubTx?: string;
    mirrorTx?: string;
    activityRegistryV1Tx?: string;
    activityRegistryV2Tx?: string;
    participantSurfaceTx?: string;
    participantTopLevelTxs?: string[];
  };
} | null): MainnetAttestationLink[] {
  const meta = raw?.mainnetAttestation;
  if (!meta) return [];
  const rows: Array<{ key: keyof NonNullable<typeof meta>; layer: string; label: string }> = [
    { key: 'hubTx', layer: 'checkpointHub', label: 'Checkpoint batch (hub)' },
    { key: 'mirrorTx', layer: 'transactionMirror', label: 'Transaction mirror' },
    { key: 'activityRegistryV1Tx', layer: 'activityRegistryV1', label: 'Activity registry V1' },
    { key: 'activityRegistryV2Tx', layer: 'activityRegistryV2', label: 'ISO-20022 PaymentAttested (V2)' },
    { key: 'participantSurfaceTx', layer: 'participantSurface', label: 'Participant surface' },
  ];
  const links: MainnetAttestationLink[] = [];
  for (const row of rows) {
    const hash = meta[row.key];
    if (typeof hash === 'string' && /^0x[a-fA-F0-9]{64}$/.test(hash)) {
      links.push({
        layer: row.layer,
        label: row.label,
        mainnetTxHash: hash,
        mainnetExplorerUrl: `https://etherscan.io/tx/${hash}`,
      });
    }
  }
  if (Array.isArray(meta.participantTopLevelTxs)) {
    for (const hash of meta.participantTopLevelTxs) {
      if (!/^0x[a-fA-F0-9]{64}$/.test(hash)) continue;
      links.push({
        layer: 'participantWallet',
        label: 'Participant wallet (0 ETH attestation tx)',
        mainnetTxHash: hash,
        mainnetExplorerUrl: `https://etherscan.io/tx/${hash}`,
      });
    }
  }
  return links;
}

function leafResponse(leaf: CheckpointLeaf | null) {
  if (!leaf) return null;
  return {
    valueUsd: leaf.valueUsd,
    nativeValueUsd: leaf.nativeValueUsd,
    tokenValueUsd: leaf.tokenValueUsd,
    totalTransfersUsd: leaf.totalTransfersUsd,
    priceSource: leaf.priceSource,
        transfers: leaf.transfers?.map((t) => ({
          ...t,
          tokenAddress: t.tokenAddress || t.token,
          symbol: t.symbol || t.tokenSymbol,
        })),
  };
}

function mergeLinks(...groups: MainnetAttestationLink[][]): MainnetAttestationLink[] {
  const out: MainnetAttestationLink[] = [];
  const seen = new Set<string>();
  for (const group of groups) {
    for (const link of group) {
      const key = `${link.layer}:${link.mainnetTxHash}`;
      if (seen.has(key)) continue;
      seen.add(key);
      out.push(link);
    }
  }
  return out;
}

async function fetchIndexerAttestation(
  txHash: string,
  timeoutMs: number,
): Promise<{
  txHash?: string;
  included?: boolean;
  batchId?: string;
  payload?: { leaves?: CheckpointLeaf[]; batchTotalUsd?: string };
  etherscanLinks?: MainnetAttestationLink[];
  etherscan?: { base?: string; txUrlTemplate?: string };
} | null> {
  const url = `${indexerBase()}/v1/tx/${txHash}/attestation`;
  try {
    const ctrl = new AbortController();
    const timer = setTimeout(() => ctrl.abort(), timeoutMs);
    const upstream = await fetch(url, { signal: ctrl.signal });
    clearTimeout(timer);
    if (!upstream.ok) return null;
    return (await upstream.json()) as {
      txHash?: string;
      included?: boolean;
      batchId?: string;
      payload?: { leaves?: CheckpointLeaf[]; batchTotalUsd?: string };
      etherscanLinks?: MainnetAttestationLink[];
      etherscan?: { base?: string; txUrlTemplate?: string };
    };
  } catch (e) {
    logger.debug('checkpoint indexer proxy miss', { txHash, error: String(e) });
    return null;
  }
}

/**
 * GET /checkpoint/tx/:txHash/attestation — USD-enriched attestation (local batch first, indexer enrich).
 */
router.get('/checkpoint/tx/:txHash/attestation', async (req: Request, res: Response) => {
  const txHash = String(req.params.txHash || '').trim();
  if (!/^0x[a-fA-F0-9]{64}$/.test(txHash)) {
    return res.status(400).json({ error: 'invalid tx hash' });
  }

  const etherscanMeta = {
    base: 'https://etherscan.io',
    txUrlTemplate: 'https://etherscan.io/tx/{txHash}',
  };

  const local = loadLocalAttestation(txHash);
  const metaLinks = local ? linksFromBatchMeta(local.batchMeta) : [];

  // Fast path: synced batch files on the explorer CT already carry mainnet tx hashes.
  if (local && metaLinks.length > 0) {
    return res.json({
      txHash,
      included: true,
      batchId: local.batchId,
      batchTotalUsd: local.batchTotalUsd,
      leaf: leafResponse(local.leaf),
      etherscanLinks: metaLinks,
      etherscan: etherscanMeta,
      source: 'checkpoint-batch-dir',
    });
  }

  const indexerTimeoutMs = Number(process.env.CHECKPOINT_INDEXER_FETCH_TIMEOUT_MS || 2500);
  const remote = await fetchIndexerAttestation(txHash, indexerTimeoutMs);

  if (remote?.included) {
    const leaf = findLeaf(remote.payload ?? null, txHash) ?? local?.leaf ?? null;
    return res.json({
      txHash: remote.txHash ?? txHash,
      included: true,
      batchId: remote.batchId ?? local?.batchId ?? '0',
      batchTotalUsd: remote.payload?.batchTotalUsd ?? local?.batchTotalUsd,
      leaf: leafResponse(leaf),
      etherscanLinks: mergeLinks(metaLinks, remote.etherscanLinks ?? []),
      etherscan: remote.etherscan ?? etherscanMeta,
      source: local ? 'checkpoint-batch-dir+indexer' : 'checkpoint-indexer',
    });
  }

  if (local) {
    return res.json({
      txHash,
      included: true,
      batchId: local.batchId,
      batchTotalUsd: local.batchTotalUsd,
      leaf: leafResponse(local.leaf),
      etherscanLinks: metaLinks,
      etherscan: etherscanMeta,
      source: 'checkpoint-batch-dir',
    });
  }

  res.json({
    txHash,
    included: false,
    batchId: '0',
    leaf: null,
    etherscanLinks: [],
    etherscan: etherscanMeta,
    source: 'none',
  });
});

export default router;
