/**
 * CMC and CoinGecko reporting API: all tokens, liquidity, volume, and reportable data.
 * Use for listing submissions and external aggregator sync.
 */

import { Router, Request, Response } from 'express';
import { TokenRepository } from '../../database/repositories/token-repo';
import { MarketDataRepository } from '../../database/repositories/market-data-repo';
import { PoolRepository } from '../../database/repositories/pool-repo';
import {
  CANONICAL_TOKENS,
  getCanonicalTokensByChain,
  getLogoUriForSpec,
} from '../../config/canonical-tokens';
import { resolvePoolTokenDisplays } from '../../services/token-display';
import { getSupportedChainIds } from '../../config/chains';
import { cacheMiddleware } from '../middleware/cache';
import { fetchRemoteJson } from '../utils/fetch-remote-json';
import { buildCrossChainReport } from '../../indexer/cross-chain-indexer';
import { logger } from '../../utils/logger';

const router: Router = Router();
const tokenRepo = new TokenRepository();
const marketDataRepo = new MarketDataRepository();
const poolRepo = new PoolRepository();

/** Build token entries with DB market/pool data for a chain */
async function buildTokenReport(chainId: number) {
  const canonical = getCanonicalTokensByChain(chainId);
  const out: Array<{
    chainId: number;
    address: string;
    symbol: string;
    name: string;
    type: string;
    decimals: number;
    currencyCode?: string;
    market?: {
      priceUsd?: number;
      volume24h: number;
      volume7d: number;
      volume30d: number;
      marketCapUsd?: number;
      liquidityUsd: number;
      lastUpdated: string;
    };
    pools: Array<{
      poolAddress: string;
      dex: string;
      token0: string;
      token1: string;
      tvl: number;
      volume24h: number;
    }>;
    fromDb: boolean;
  }> = [];

  for (const spec of canonical) {
    const address = spec.addresses[chainId];
    if (!address || String(address).trim() === '') continue;

    const [dbToken, marketData, pools] = await Promise.all([
      tokenRepo.getToken(chainId, address),
      marketDataRepo.getMarketData(chainId, address),
      poolRepo.getPoolsByToken(chainId, address),
    ]);

    const resolvedPools = await Promise.all(
      pools.map(async (p) => {
        const { token0, token1 } = await resolvePoolTokenDisplays(tokenRepo, chainId, p.token0Address, p.token1Address);
        return {
          poolAddress: p.poolAddress,
          dex: p.dexType,
          token0,
          token1,
          tvl: p.totalLiquidityUsd,
          volume24h: p.volume24h,
        };
      })
    );

    out.push({
      chainId,
      address: address.toLowerCase(),
      symbol: spec.symbol,
      name: dbToken?.name ?? spec.name,
      type: spec.type,
      decimals: spec.decimals,
      currencyCode: spec.currencyCode,
      market: marketData
        ? {
            priceUsd: marketData.priceUsd,
            volume24h: marketData.volume24h,
            volume7d: marketData.volume7d,
            volume30d: marketData.volume30d,
            marketCapUsd: marketData.marketCapUsd,
            liquidityUsd: marketData.liquidityUsd,
            lastUpdated: marketData.lastUpdated?.toISOString() ?? '',
          }
        : undefined,
      pools: resolvedPools.map((p) => ({
        poolAddress: p.poolAddress,
        dex: p.dex,
        token0: p.token0.address,
        token1: p.token1.address,
        token0Symbol: p.token0.symbol,
        token1Symbol: p.token1.symbol,
        tvl: p.tvl,
        volume24h: p.volume24h,
      })),
      fromDb: !!dbToken,
    });
  }

  return out;
}

/** GET /report/cross-chain — cross-chain pools, bridge volume, atomic swaps (Chain 138, ALL Mainnet) */
router.get(
  '/cross-chain',
  cacheMiddleware(2 * 60 * 1000),
  async (req: Request, res: Response) => {
    try {
      const chainId = parseInt(req.query.chainId as string, 10) || 138;
      const report = await buildCrossChainReport(chainId);
      res.json({
        ...report,
        format: 'cross-chain-report',
        documentation: 'Use for CMC/CoinGecko submission alongside single-chain reports. Includes CCIP, Alltra, Trustless bridge events and volume by lane.',
      });
    } catch (error) {
      logger.error('Error building report/cross-chain:', error);
      res.status(500).json({
        error: 'Internal server error',
        crossChainPools: [],
        volumeByLane: [],
        atomicSwapVolume24h: 0,
        bridgeVolume24hTotal: 0,
        events: [],
      });
    }
  }
);

/** GET /report/all — all tokens, pools, liquidity, volume (unified) + cross-chain */
router.get(
  '/all',
  cacheMiddleware(2 * 60 * 1000),
  async (req: Request, res: Response) => {
    try {
      const chainIdParam = req.query.chainId as string | undefined;
      const chainIds = chainIdParam
        ? [parseInt(chainIdParam, 10)].filter((n) => !isNaN(n))
        : getSupportedChainIds();

      const tokensByChain: Record<number, Awaited<ReturnType<typeof buildTokenReport>>> = {};
      const poolsByChain: Record<number, Awaited<ReturnType<typeof poolRepo.getPoolsByChain>>> = {};

      for (const chainId of chainIds) {
        tokensByChain[chainId] = await buildTokenReport(chainId);
        poolsByChain[chainId] = await poolRepo.getPoolsByChain(chainId);
      }

      const crossChainReport = await buildCrossChainReport(138).catch(() => null);

      const totalLiquidityByChain: Record<number, number> = {};
      const totalVolume24hByChain: Record<number, number> = {};
      for (const chainId of chainIds) {
        const pools = poolsByChain[chainId] || [];
        totalLiquidityByChain[chainId] = pools.reduce((s, p) => s + (p.totalLiquidityUsd || 0), 0);
        totalVolume24hByChain[chainId] = pools.reduce((s, p) => s + (p.volume24h || 0), 0);
      }

      res.json({
        generatedAt: new Date().toISOString(),
        chains: chainIds,
        tokens: tokensByChain,
        pools: poolsByChain,
        summary: {
          totalLiquidityUsdByChain: totalLiquidityByChain,
          totalVolume24hUsdByChain: totalVolume24hByChain,
          tokenCountByChain: Object.fromEntries(
            chainIds.map((c) => [c, (tokensByChain[c] || []).length])
          ),
          poolCountByChain: Object.fromEntries(
            chainIds.map((c) => [c, (poolsByChain[c] || []).length])
          ),
          crossChainBridgeVolume24h: crossChainReport?.bridgeVolume24hTotal,
          crossChainAtomicSwapVolume24h: crossChainReport?.atomicSwapVolume24h,
        },
        crossChain: crossChainReport
          ? {
              crossChainPools: crossChainReport.crossChainPools,
              volumeByLane: crossChainReport.volumeByLane,
              atomicSwapVolume24h: crossChainReport.atomicSwapVolume24h,
              bridgeVolume24hTotal: crossChainReport.bridgeVolume24hTotal,
            }
          : undefined,
      });
    } catch (error) {
      logger.error('Error building report/all:', error);
      res.status(500).json({ error: 'Internal server error' });
    }
  }
);

/** GET /report/coingecko — format suitable for CoinGecko submission / API */
router.get(
  '/coingecko',
  cacheMiddleware(2 * 60 * 1000),
  async (req: Request, res: Response) => {
    try {
      const chainId = parseInt(req.query.chainId as string, 10) || 138;
      const tokens = await buildTokenReport(chainId);

      const coingeckoFormat = tokens.map((t) => ({
        chain_id: chainId,
        contract_address: t.address,
        id: `${t.symbol.toLowerCase()}-${chainId}`,
        symbol: t.symbol,
        name: t.name,
        asset_platform_id: chainId === 138 ? 'defi-oracle-meta' : chainId === 651940 ? 'all-mainnet' : `chain-${chainId}`,
        decimals: t.decimals,
        description: t.currencyCode ? `ISO-4217 ${t.currencyCode} compliant token` : undefined,
        market_data: t.market
          ? {
              current_price: { usd: t.market.priceUsd },
              total_volume: t.market.volume24h,
              market_cap: t.market.marketCapUsd,
              liquidity_usd: t.market.liquidityUsd,
              last_updated: t.market.lastUpdated,
            }
          : undefined,
        liquidity_pools: t.pools.map((p) => ({
          pool_address: p.poolAddress,
          dex_id: p.dex,
          tvl_usd: p.tvl,
          volume_24h_usd: p.volume24h,
        })),
      }));

      const crossChainReport = await buildCrossChainReport(chainId).catch(() => null);

      res.json({
        generatedAt: new Date().toISOString(),
        chainId,
        format: 'coingecko-submission',
        tokens: coingeckoFormat,
        crossChain: crossChainReport
          ? {
              crossChainPools: crossChainReport.crossChainPools,
              volumeByLane: crossChainReport.volumeByLane,
              atomicSwapVolume24h: crossChainReport.atomicSwapVolume24h,
              bridgeVolume24hTotal: crossChainReport.bridgeVolume24hTotal,
            }
          : undefined,
        documentation: 'https://www.coingecko.com/en/api/documentation',
      });
    } catch (error) {
      logger.error('Error building report/coingecko:', error);
      res.status(500).json({ error: 'Internal server error' });
    }
  }
);

/** GET /report/cmc — format suitable for CoinMarketCap submission / API */
router.get(
  '/cmc',
  cacheMiddleware(2 * 60 * 1000),
  async (req: Request, res: Response) => {
    try {
      const chainId = parseInt(req.query.chainId as string, 10) || 138;
      const tokens = await buildTokenReport(chainId);

      const cmcFormat = tokens.map((t) => ({
        chain_id: chainId,
        contract_address: t.address,
        symbol: t.symbol,
        name: t.name,
        decimals: t.decimals,
        volume_24h: t.market?.volume24h,
        market_cap: t.market?.marketCapUsd,
        liquidity_usd: t.market?.liquidityUsd ?? t.pools.reduce((s, p) => s + p.tvl, 0),
        pairs: t.pools.map((p) => ({
          pair_address: p.poolAddress,
          dex_id: p.dex,
          base: t.address,
          quote: p.token0 === t.address ? p.token1 : p.token0,
          liquidity_usd: p.tvl,
          volume_24h_usd: p.volume24h,
        })),
      }));

      const crossChainReport = await buildCrossChainReport(chainId).catch(() => null);

      res.json({
        generatedAt: new Date().toISOString(),
        chainId,
        format: 'coinmarketcap-dex',
        tokens: cmcFormat,
        crossChain: crossChainReport
          ? {
              crossChainPools: crossChainReport.crossChainPools,
              volumeByLane: crossChainReport.volumeByLane,
              atomicSwapVolume24h: crossChainReport.atomicSwapVolume24h,
              bridgeVolume24hTotal: crossChainReport.bridgeVolume24hTotal,
            }
          : undefined,
        documentation: 'https://coinmarketcap.com/api/documentation',
      });
    } catch (error) {
      logger.error('Error building report/cmc:', error);
      res.status(500).json({ error: 'Internal server error' });
    }
  }
);

/** GET /report/token-list — flat list of all canonical tokens (Uniswap token list format with logoURI).
 * If TOKEN_LIST_JSON_URL is set (e.g. GitHub raw URL), fetches and returns that JSON; optional ?chainId= filters tokens.
 */
router.get(
  '/token-list',
  cacheMiddleware(5 * 60 * 1000),
  async (req: Request, res: Response) => {
    try {
      const tokenListUrl = process.env.TOKEN_LIST_JSON_URL?.trim();
      if (tokenListUrl) {
        try {
          const data = (await fetchRemoteJson(tokenListUrl)) as {
            name?: string;
            version?: string;
            timestamp?: string;
            logoURI?: string;
            tokens?: Array<{ chainId?: number; address?: string; symbol?: string; name?: string; decimals?: number; [key: string]: unknown }>;
          };
          const chainIdParam = req.query.chainId as string | undefined;
          const chainIdFilter = chainIdParam ? parseInt(chainIdParam, 10) : null;
          let tokens = Array.isArray(data.tokens) ? data.tokens : [];
          if (!isNaN(chainIdFilter as number)) {
            tokens = tokens.filter((t) => t.chainId === chainIdFilter);
          }
          return res.json({
            name: data.name ?? 'Token List',
            version: data.version ?? '1.0.0',
            timestamp: data.timestamp ?? new Date().toISOString(),
            logoURI: data.logoURI,
            tokens,
          });
        } catch (err) {
          logger.error('TOKEN_LIST_JSON_URL fetch failed, using built-in token list:', err);
        }
      }

      const chainIdParam = req.query.chainId as string | undefined;
      const chainIds = chainIdParam
        ? [parseInt(chainIdParam, 10)].filter((n) => !isNaN(n))
        : getSupportedChainIds();

      const list: Array<{
        chainId: number;
        address: string;
        symbol: string;
        name: string;
        decimals: number;
        type: string;
        logoURI: string;
      }> = [];

      for (const chainId of chainIds) {
        const specs = getCanonicalTokensByChain(chainId);
        for (const spec of specs) {
          const address = spec.addresses[chainId];
          if (address) {
            list.push({
              chainId,
              address: address.toLowerCase(),
              symbol: spec.symbol,
              name: spec.name,
              decimals: spec.decimals,
              type: spec.type,
              logoURI: getLogoUriForSpec(spec),
            });
          }
        }
      }

      res.json({
        name: 'GRU Canonical Token List',
        version: '1.0.0',
        timestamp: new Date().toISOString(),
        logoURI: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png',
        tokens: list,
      });
    } catch (error) {
      logger.error('Error building report/token-list:', error);
      res.status(500).json({ error: 'Internal server error' });
    }
  }
);

/** GET /report/canonical — raw canonical spec list (no DB merge) */
router.get(
  '/canonical',
  cacheMiddleware(10 * 60 * 1000),
  async (req: Request, res: Response) => {
    try {
      res.json({
        generatedAt: new Date().toISOString(),
        tokens: CANONICAL_TOKENS.map((t) => ({
          symbol: t.symbol,
          name: t.name,
          type: t.type,
          decimals: t.decimals,
          currencyCode: t.currencyCode,
          addresses: t.addresses,
        })),
      });
    } catch (error) {
      logger.error('Error building report/canonical:', error);
      res.status(500).json({ error: 'Internal server error' });
    }
  }
);

export default router;
