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 { OHLCVGenerator } from '../../indexer/ohlcv-generator';
import { CoinGeckoAdapter } from '../../adapters/coingecko-adapter';
import { CoinMarketCapAdapter } from '../../adapters/cmc-adapter';
import { DexScreenerAdapter } from '../../adapters/dexscreener-adapter';
import { cacheMiddleware } from '../middleware/cache';
import { logger } from '../../utils/logger';

const router: Router = Router();
const tokenRepo = new TokenRepository();
const marketDataRepo = new MarketDataRepository();
const poolRepo = new PoolRepository();
const ohlcvGenerator = new OHLCVGenerator();
const coingeckoAdapter = new CoinGeckoAdapter();
const cmcAdapter = new CoinMarketCapAdapter();
const dexscreenerAdapter = new DexScreenerAdapter();

router.get('/chains', cacheMiddleware(5 * 60 * 1000), async (req: Request, res: Response) => {
  try {
    res.json({
      chains: [
        {
          chainId: 138,
          name: 'DeFi Oracle Meta Mainnet',
          explorerUrl: 'https://explorer.d-bis.org',
        },
        {
          chainId: 651940,
          name: 'ALL Mainnet',
          explorerUrl: 'https://alltra.global',
        },
      ],
    });
  } catch (error) {
    res.status(500).json({ error: 'Internal server error' });
  }
});

router.get('/tokens', cacheMiddleware(60 * 1000), async (req: Request, res: Response) => {
  try {
    const chainId = parseInt(req.query.chainId as string, 10);
    const limit = parseInt(req.query.limit as string, 10) || 50;
    const offset = parseInt(req.query.offset as string, 10) || 0;
    const includeDodoPool = (req.query.includeDodoPool as string) === '1' || (req.query.includeDodoPool as string) === 'true';

    if (!chainId) {
      return res.status(400).json({ error: 'chainId is required' });
    }

    const tokens = await tokenRepo.getTokens(chainId, limit, offset);
    const tokensWithMarketData = await Promise.all(
      tokens.map(async (token) => {
        const marketData = await marketDataRepo.getMarketData(chainId, token.address);
        const out: Record<string, unknown> = {
          ...token,
          market: marketData || undefined,
        };
        if (includeDodoPool) {
          const pools = await poolRepo.getPoolsByToken(chainId, token.address);
          const dodoPool = pools.find((p) => (p.dexType || '').toLowerCase() === 'dodo');
          out.hasDodoPool = !!dodoPool;
          out.pmmPool = dodoPool?.poolAddress || undefined;
        }
        return out;
      })
    );

    res.json({
      tokens: tokensWithMarketData,
      pagination: {
        limit,
        offset,
        count: tokensWithMarketData.length,
      },
    });
  } catch (error) {
    logger.error('Error fetching tokens:', error);
    res.status(500).json({ error: 'Internal server error' });
  }
});

router.get('/tokens/:address', cacheMiddleware(60 * 1000), async (req: Request, res: Response) => {
  try {
    const chainId = parseInt(req.query.chainId as string, 10);
    const address = req.params.address;

    if (!chainId) {
      return res.status(400).json({ error: 'chainId is required' });
    }

    const token = await tokenRepo.getToken(chainId, address);
    if (!token) {
      return res.status(404).json({ error: 'Token not found' });
    }

    const [marketData, pools, coingeckoData, cmcData, dexscreenerData] = await Promise.all([
      marketDataRepo.getMarketData(chainId, address),
      poolRepo.getPoolsByToken(chainId, address),
      coingeckoAdapter.getTokenByContract(chainId, address),
      cmcAdapter.getTokenByContract(chainId, address),
      dexscreenerAdapter.getTokenByContract(chainId, address),
    ]);

    res.json({
      token: {
        ...token,
        onChain: {
          totalSupply: token.totalSupply,
        },
        market: marketData || undefined,
        external: {
          coingecko: coingeckoData || undefined,
          cmc: cmcData || undefined,
          dexscreener: dexscreenerData || undefined,
        },
        pools: pools.map((pool) => ({
          address: pool.poolAddress,
          dex: pool.dexType,
          token0: pool.token0Address,
          token1: pool.token1Address,
          reserves: {
            token0: pool.reserve0,
            token1: pool.reserve1,
          },
          tvl: pool.totalLiquidityUsd,
          volume24h: pool.volume24h,
        })),
        hasDodoPool: pools.some((p) => (p.dexType || '').toLowerCase() === 'dodo'),
        pmmPool: pools.find((p) => (p.dexType || '').toLowerCase() === 'dodo')?.poolAddress || undefined,
      },
    });
  } catch (error) {
    logger.error('Error fetching token:', error);
    res.status(500).json({ error: 'Internal server error' });
  }
});

router.get('/tokens/:address/pools', cacheMiddleware(60 * 1000), async (req: Request, res: Response) => {
  try {
    const chainId = parseInt(req.query.chainId as string, 10);
    const address = req.params.address;

    if (!chainId) {
      return res.status(400).json({ error: 'chainId is required' });
    }

    const pools = await poolRepo.getPoolsByToken(chainId, address);

    res.json({
      pools: pools.map((pool) => ({
        address: pool.poolAddress,
        dex: pool.dexType,
        token0: pool.token0Address,
        token1: pool.token1Address,
        reserves: {
          token0: pool.reserve0,
          token1: pool.reserve1,
        },
        tvl: pool.totalLiquidityUsd,
        volume24h: pool.volume24h,
        feeTier: pool.feeTier,
      })),
    });
  } catch (error) {
    logger.error('Error fetching pools:', error);
    res.status(500).json({ error: 'Internal server error' });
  }
});

router.get('/tokens/:address/ohlcv', cacheMiddleware(5 * 60 * 1000), async (req: Request, res: Response) => {
  try {
    const chainId = parseInt(req.query.chainId as string, 10);
    const address = req.params.address;
    const interval = (req.query.interval as string) || '1h';
    const from = req.query.from ? new Date(req.query.from as string) : new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
    const to = req.query.to ? new Date(req.query.to as string) : new Date();
    const poolAddress = req.query.poolAddress as string | undefined;

    if (!chainId) {
      return res.status(400).json({ error: 'chainId is required' });
    }

    if (!['5m', '15m', '1h', '4h', '24h'].includes(interval)) {
      return res.status(400).json({ error: 'Invalid interval. Must be one of: 5m, 15m, 1h, 4h, 24h' });
    }

    const ohlcv = await ohlcvGenerator.getOHLCV(
      chainId,
      address,
      interval as '5m' | '15m' | '1h' | '4h' | '24h',
      from,
      to,
      poolAddress
    );

    res.json({
      chainId,
      tokenAddress: address,
      interval,
      data: ohlcv,
    });
  } catch (error) {
    logger.error('Error fetching OHLCV:', error);
    res.status(500).json({ error: 'Internal server error' });
  }
});

router.get('/tokens/:address/signals', cacheMiddleware(5 * 60 * 1000), async (req: Request, res: Response) => {
  try {
    const chainId = parseInt(req.query.chainId as string, 10);
    const address = req.params.address;

    if (!chainId) {
      return res.status(400).json({ error: 'chainId is required' });
    }

    const trending = await coingeckoAdapter.getTrending();

    res.json({
      chainId,
      tokenAddress: address,
      signals: {
        trendingRank: trending.findIndex((t) => t.symbol.toLowerCase() === address.toLowerCase()),
      },
    });
  } catch (error) {
    logger.error('Error fetching signals:', error);
    res.status(500).json({ error: 'Internal server error' });
  }
});

router.get('/search', cacheMiddleware(60 * 1000), async (req: Request, res: Response) => {
  try {
    const chainId = parseInt(req.query.chainId as string, 10);
    const query = req.query.q as string;

    if (!chainId || !query) {
      return res.status(400).json({ error: 'chainId and q (query) are required' });
    }

    const tokens = await tokenRepo.searchTokens(chainId, query, 20);

    res.json({
      query,
      chainId,
      results: tokens,
    });
  } catch (error) {
    logger.error('Error searching tokens:', error);
    res.status(500).json({ error: 'Internal server error' });
  }
});

router.get('/pools/:poolAddress', cacheMiddleware(60 * 1000), async (req: Request, res: Response) => {
  try {
    const chainId = parseInt(req.query.chainId as string, 10);
    const poolAddress = req.params.poolAddress;

    if (!chainId) {
      return res.status(400).json({ error: 'chainId is required' });
    }

    const pool = await poolRepo.getPool(chainId, poolAddress);
    if (!pool) {
      return res.status(404).json({ error: 'Pool not found' });
    }

    res.json({
      pool: {
        address: pool.poolAddress,
        dex: pool.dexType,
        token0: pool.token0Address,
        token1: pool.token1Address,
        reserves: {
          token0: pool.reserve0,
          token1: pool.reserve1,
        },
        tvl: pool.totalLiquidityUsd,
        volume24h: pool.volume24h,
        feeTier: pool.feeTier,
      },
    });
  } catch (error) {
    logger.error('Error fetching pool:', error);
    res.status(500).json({ error: 'Internal server error' });
  }
});

export default router;
