import { ethers } from 'ethers';
import { getChainConfig, getSupportedChainIds } from '../config/chains';
import { TokenIndexer } from './token-indexer';
import { PoolIndexer } from './pool-indexer';
import { VolumeCalculator } from './volume-calculator';
import { OHLCVGenerator } from './ohlcv-generator';
import { MarketDataRepository } from '../database/repositories/market-data-repo';
import { CoinGeckoAdapter } from '../adapters/coingecko-adapter';
import { CoinMarketCapAdapter } from '../adapters/cmc-adapter';
import { DexScreenerAdapter } from '../adapters/dexscreener-adapter';
import { logger } from '../utils/logger';
import { getCanonicalPriceUsd } from '../services/canonical-price-oracle';
import { getChain138EthUsdOnChain } from '../services/chain138-onchain-reference-prices';
import { pickExternalMarketDataForIndexer } from '../services/valuation-precedence';

const CHAIN138_WETH = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2';

export class ChainIndexer {
  private chainId: number;
  private provider: ethers.JsonRpcProvider;
  private tokenIndexer: TokenIndexer;
  private poolIndexer: PoolIndexer;
  private volumeCalculator: VolumeCalculator;
  private ohlcvGenerator: OHLCVGenerator;
  private marketDataRepo: MarketDataRepository;
  private adapters: {
    coingecko: CoinGeckoAdapter;
    cmc: CoinMarketCapAdapter;
    dexscreener: DexScreenerAdapter;
  };
  private isRunning: boolean = false;
  private isIndexing: boolean = false;
  private indexingInterval?: NodeJS.Timeout;

  constructor(chainId: number) {
    const config = getChainConfig(chainId);
    if (!config) {
      throw new Error(`Chain ${chainId} not configured`);
    }

    this.chainId = chainId;
    this.provider = new ethers.JsonRpcProvider(config.rpcUrl);
    this.tokenIndexer = new TokenIndexer(chainId, config.rpcUrl);
    this.poolIndexer = new PoolIndexer(chainId, config.rpcUrl);
    this.volumeCalculator = new VolumeCalculator();
    this.ohlcvGenerator = new OHLCVGenerator();
    this.marketDataRepo = new MarketDataRepository();

    this.adapters = {
      coingecko: new CoinGeckoAdapter(),
      cmc: new CoinMarketCapAdapter(),
      dexscreener: new DexScreenerAdapter(),
    };
  }

  /**
   * Start indexing process
   */
  async start(): Promise<void> {
    if (this.isRunning) {
      logger.warn(`Chain indexer for ${this.chainId} is already running`);
      return;
    }

    this.isRunning = true;
    logger.info(`Starting chain indexer for chain ${this.chainId}`);

    // Initial indexing
    await this.indexAll();

    // Set up periodic indexing
    const interval = parseInt(process.env.INDEXING_INTERVAL || '5000', 10);
    this.indexingInterval = setInterval(() => {
      this.indexAll().catch((error) => {
        logger.error(`Error in periodic indexing for chain ${this.chainId}:`, error);
      });
    }, interval);
  }

  /**
   * Stop indexing process
   */
  stop(): void {
    if (!this.isRunning) {
      return;
    }

    this.isRunning = false;
    if (this.indexingInterval) {
      clearInterval(this.indexingInterval);
      this.indexingInterval = undefined;
    }

    logger.info(`Stopped chain indexer for chain ${this.chainId}`);
  }

  /**
   * Index all data (pools, tokens, market data)
   */
  private async indexAll(): Promise<void> {
    if (this.isIndexing) {
      logger.info(`Skipping overlapping index cycle for chain ${this.chainId}`);
      return;
    }

    this.isIndexing = true;
    try {
      // 1. Index pools
      logger.info(`Indexing pools for chain ${this.chainId}...`);
      const pools = await this.poolIndexer.indexAllPools();

      // 2. Discover and index tokens from pools
      logger.info(`Discovering tokens for chain ${this.chainId}...`);
      const tokenAddresses = new Set<string>();
      pools.forEach((pool) => {
        tokenAddresses.add(pool.token0Address);
        tokenAddresses.add(pool.token1Address);
      });
      await this.tokenIndexer.indexTokens(Array.from(tokenAddresses));

      // 3. Calculate volumes and update market data
      logger.info(`Calculating volumes for chain ${this.chainId}...`);
      for (const tokenAddress of tokenAddresses) {
        await this.updateMarketData(tokenAddress, pools);
      }

      // 4. Generate OHLCV data
      logger.info(`Generating OHLCV for chain ${this.chainId}...`);
      const intervals: Array<'5m' | '1h' | '24h'> = ['5m', '1h', '24h'];
      const now = new Date();
      const from = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); // Last 7 days

      for (const tokenAddress of tokenAddresses) {
        for (const interval of intervals) {
          await this.ohlcvGenerator.generateAndStore(
            this.chainId,
            tokenAddress,
            interval,
            from,
            now
          );
        }
      }
    } catch (error) {
      logger.error(`Error in indexAll for chain ${this.chainId}:`, error);
      throw error;
    } finally {
      this.isIndexing = false;
    }
  }

  /**
   * Update market data for a token
   */
  private async updateMarketData(tokenAddress: string, pools: Awaited<ReturnType<PoolIndexer['indexAllPools']>>): Promise<void> {
    try {
      // Calculate on-chain volume
      const volumeMetrics = await this.volumeCalculator.calculateTokenVolume(
        this.chainId,
        tokenAddress
      );

      // Get external market data
      const [coingeckoData, cmcData, dexscreenerData] = await Promise.all([
        this.adapters.coingecko.getMarketData(this.chainId, tokenAddress),
        this.adapters.cmc.getMarketData(this.chainId, tokenAddress),
        this.adapters.dexscreener.getMarketData(this.chainId, tokenAddress),
      ]);

      const externalData = pickExternalMarketDataForIndexer(this.chainId, tokenAddress.toLowerCase(), {
        coingecko: coingeckoData,
        cmc: cmcData,
        dexscreener: dexscreenerData,
      });
      const canonicalPriceUsd = getCanonicalPriceUsd(this.chainId, tokenAddress);

      let priceUsd = externalData?.priceUsd ?? canonicalPriceUsd;
      if (this.chainId === 138 && tokenAddress.toLowerCase() === CHAIN138_WETH) {
        const native = await getChain138EthUsdOnChain();
        if (native?.priceUsd != null && native.priceUsd > 0) {
          priceUsd = native.priceUsd;
        }
      }

      // Get pools for liquidity calculation
      const tokenPools = pools.filter(
        (p) => p.token0Address === tokenAddress || p.token1Address === tokenAddress
      );
      const totalLiquidity = tokenPools.reduce((sum, p) => sum + p.totalLiquidityUsd, 0);

      // Update market data
      await this.marketDataRepo.upsertMarketData({
        chainId: this.chainId,
        tokenAddress,
        priceUsd,
        priceChange24h: externalData?.priceChange24h,
        volume24h: volumeMetrics.volume24h || externalData?.volume24h || 0,
        volume7d: volumeMetrics.volume7d,
        volume30d: volumeMetrics.volume30d,
        marketCapUsd: externalData?.marketCapUsd,
        liquidityUsd: totalLiquidity || externalData?.liquidityUsd || 0,
        holdersCount: 0, // Would need to calculate from token transfers
        transfers24h: volumeMetrics.txCount24h,
        lastUpdated: new Date(),
      });
    } catch (error) {
      logger.error(`Error updating market data for ${tokenAddress}:`, error);
    }
  }

  /**
   * Get current block number
   */
  async getCurrentBlock(): Promise<number> {
    return await this.provider.getBlockNumber();
  }
}

/**
 * Multi-chain indexer orchestrator
 */
export class MultiChainIndexer {
  private indexers: Map<number, ChainIndexer> = new Map();

  /**
   * Initialize indexers for all supported chains
   */
  async initialize(): Promise<void> {
    const chainIds = getSupportedChainIds();
    for (const chainId of chainIds) {
      try {
        const indexer = new ChainIndexer(chainId);
        this.indexers.set(chainId, indexer);
        logger.info(`Initialized indexer for chain ${chainId}`);
      } catch (error) {
        logger.error(`Failed to initialize indexer for chain ${chainId}:`, error);
      }
    }
  }

  /**
   * Start all indexers
   */
  async startAll(): Promise<void> {
    for (const [chainId, indexer] of this.indexers) {
      try {
        await indexer.start();
      } catch (error) {
        logger.error(`Failed to start indexer for chain ${chainId}:`, error);
      }
    }
  }

  /**
   * Stop all indexers
   */
  stopAll(): void {
    for (const [chainId, indexer] of this.indexers) {
      try {
        indexer.stop();
      } catch (error) {
        logger.error(`Failed to stop indexer for chain ${chainId}:`, error);
      }
    }
  }

  /**
   * Get indexer for a specific chain
   */
  getIndexer(chainId: number): ChainIndexer | undefined {
    return this.indexers.get(chainId);
  }
}
