import axios, { AxiosInstance } from 'axios';
import { ExternalApiAdapter, TokenMetadata, MarketData } from './base-adapter';
import { logger } from '../utils/logger';

interface CMCDexPair {
  pair_address: string;
  base: {
    address: string;
    symbol: string;
  };
  quote: {
    address: string;
    symbol: string;
  };
  dex_id: string;
  price: string;
  price_usd?: string;
  volume_24h?: {
    base: string;
    quote: string;
    usd?: string;
  };
  liquidity?: {
    usd?: string;
  };
}

interface CMCDexPairsResponse {
  data: CMCDexPair[];
}

interface CMCPairQuote {
  pair_address: string;
  price: string;
  price_usd?: string;
  volume_24h?: {
    usd?: string;
  };
  liquidity?: {
    usd?: string;
  };
}

interface CMCPairQuotesResponse {
  data: Record<string, CMCPairQuote>;
}

interface CMCOHLCV {
  time_open: string;
  time_close: string;
  quote: {
    open: string;
    high: string;
    low: string;
    close: string;
    volume: string;
  };
}

interface CMCOHLCVResponse {
  data: {
    pairs: Array<{
      pair_address: string;
      timeframes: Record<string, CMCOHLCV[]>;
    }>;
  };
}

// Chain ID to CMC chain identifier mapping
// Note: CMC uses different identifiers, these may need to be updated
const CHAIN_TO_CMC_ID: Record<number, string> = {
  1: '1', // Ethereum
  56: '1839', // BSC
  137: '3890', // Polygon
  43114: '5805', // Avalanche
  42161: '42161', // Arbitrum
  10: '42170', // Optimism
  8453: '8453', // Base
  // 138 and 651940 likely not supported
};

export class CoinMarketCapAdapter implements ExternalApiAdapter {
  private api: AxiosInstance;
  private apiKey?: string;
  private cache: Map<string, { data: MarketData; expiresAt: Date }> = new Map();

  constructor() {
    this.apiKey = process.env.COINMARKETCAP_API_KEY;
    if (!this.apiKey) {
      logger.warn('CoinMarketCap API key not provided. CMC adapter will not function.');
    }

    this.api = axios.create({
      baseURL: 'https://pro-api.coinmarketcap.com',
      timeout: 10000,
      headers: {
        'X-CMC_PRO_API_KEY': this.apiKey || '',
        'Accept': 'application/json',
      },
    });
  }

  getProviderName(): string {
    return 'coinmarketcap';
  }

  /**
   * Check if chain is supported by CoinMarketCap DEX API
   */
  async checkChainSupport(chainId: number): Promise<boolean> {
    // CMC DEX API support is limited and requires API key
    if (!this.apiKey) {
      return false;
    }

    const cmcChainId = CHAIN_TO_CMC_ID[chainId];
    if (!cmcChainId) {
      return false;
    }

    // Try to fetch DEX pairs to verify support
    try {
      const response = await this.api.get('/v4/dex/spot-pairs/latest', {
        params: {
          chain_id: cmcChainId,
          limit: 1,
        },
      });
      return response.status === 200;
    } catch (error: unknown) {
      const err = error as { response?: { status?: number } };
      if (err.response?.status === 400 || err.response?.status === 404) {
        return false; // Chain not supported
      }
      logger.error(`Error checking CMC chain support for ${chainId}:`, error);
      return false;
    }
  }

  /**
   * Get token by contract address (CMC doesn't have direct contract lookup in free tier)
   */
  // eslint-disable-next-line @typescript-eslint/no-unused-vars -- interface requires (chainId, address)
  async getTokenByContract(_chainId: number, _address: string): Promise<TokenMetadata | null> {
    // CMC DEX API doesn't provide token metadata directly
    // Would need CMC Pro API with different endpoints
    return null;
  }

  /**
   * Get market data via DEX pairs
   */
  async getMarketData(chainId: number, address: string): Promise<MarketData | null> {
    if (!this.apiKey) {
      return null;
    }

    const cmcChainId = CHAIN_TO_CMC_ID[chainId];
    if (!cmcChainId) {
      return null;
    }

    const cacheKey = `cmc_market_${chainId}_${address.toLowerCase()}`;
    const cached = this.cache.get(cacheKey);
    if (cached && cached.expiresAt > new Date()) {
      return cached.data;
    }

    try {
      // Get DEX pairs for this token
      const response = await this.api.get<CMCDexPairsResponse>('/v4/dex/spot-pairs/latest', {
        params: {
          chain_id: cmcChainId,
          base_address: address.toLowerCase(),
          limit: 10,
        },
      });

      if (!response.data.data || response.data.data.length === 0) {
        return null;
      }

      // Aggregate data from all pairs
      let totalVolume24h = 0;
      let totalLiquidity = 0;
      let avgPrice = 0;
      let priceCount = 0;

      response.data.data.forEach((pair) => {
        if (pair.price_usd) {
          avgPrice += parseFloat(pair.price_usd);
          priceCount++;
        }
        if (pair.volume_24h?.usd) {
          totalVolume24h += parseFloat(pair.volume_24h.usd);
        }
        if (pair.liquidity?.usd) {
          totalLiquidity += parseFloat(pair.liquidity.usd);
        }
      });

      const marketData: MarketData = {
        priceUsd: priceCount > 0 ? avgPrice / priceCount : undefined,
        volume24h: totalVolume24h > 0 ? totalVolume24h : undefined,
        liquidityUsd: totalLiquidity > 0 ? totalLiquidity : undefined,
        lastUpdated: new Date(),
      };

      // Cache for 5 minutes
      this.cache.set(cacheKey, {
        data: marketData,
        expiresAt: new Date(Date.now() + 5 * 60 * 1000),
      });

      return marketData;
    } catch (error: unknown) {
      const err = error as { response?: { status?: number } };
      if (err.response?.status === 404 || err.response?.status === 400) {
        return null;
      }
      logger.error(`Error fetching CMC market data for ${address} on chain ${chainId}:`, error);
      return null;
    }
  }

  /**
   * Get DEX pairs for a token
   */
  async getDexPairs(chainId: number, tokenAddress: string): Promise<CMCDexPair[]> {
    if (!this.apiKey) {
      return [];
    }

    const cmcChainId = CHAIN_TO_CMC_ID[chainId];
    if (!cmcChainId) {
      return [];
    }

    try {
      const response = await this.api.get<CMCDexPairsResponse>('/v4/dex/spot-pairs/latest', {
        params: {
          chain_id: cmcChainId,
          base_address: tokenAddress.toLowerCase(),
          limit: 100,
        },
      });

      return response.data.data || [];
    } catch (error) {
      logger.error(`Error fetching CMC DEX pairs for ${tokenAddress} on chain ${chainId}:`, error);
      return [];
    }
  }

  /**
   * Get pair quotes
   */
  async getPairQuotes(chainId: number, pairAddresses: string[]): Promise<CMCPairQuote[]> {
    if (!this.apiKey || pairAddresses.length === 0) {
      return [];
    }

    const cmcChainId = CHAIN_TO_CMC_ID[chainId];
    if (!cmcChainId) {
      return [];
    }

    try {
      const response = await this.api.get<CMCPairQuotesResponse>('/v4/dex/pairs/quotes/latest', {
        params: {
          chain_id: cmcChainId,
          pair_addresses: pairAddresses.join(','),
        },
      });

      return Object.values(response.data.data || {});
    } catch (error) {
      logger.error(`Error fetching CMC pair quotes for chain ${chainId}:`, error);
      return [];
    }
  }

  /**
   * Get OHLCV data for pairs
   */
  async getOHLCV(
    chainId: number,
    pairAddress: string,
    interval: '5m' | '15m' | '1h' | '4h' | '24h',
    from: Date,
    to: Date
  ): Promise<CMCOHLCV[]> {
    if (!this.apiKey) {
      return [];
    }

    const cmcChainId = CHAIN_TO_CMC_ID[chainId];
    if (!cmcChainId) {
      return [];
    }

    const intervalMap: Record<string, string> = {
      '5m': '5m',
      '15m': '15m',
      '1h': '1h',
      '4h': '4h',
      '24h': '1d',
    };

    try {
      const response = await this.api.get<CMCOHLCVResponse>('/v4/dex/pairs/ohlcv/historical', {
        params: {
          chain_id: cmcChainId,
          pair_address: pairAddress.toLowerCase(),
          interval: intervalMap[interval] || '1h',
          time_start: Math.floor(from.getTime() / 1000),
          time_end: Math.floor(to.getTime() / 1000),
        },
      });

      const pair = response.data.data.pairs?.[0];
      if (!pair) {
        return [];
      }

      return pair.timeframes[intervalMap[interval] || '1h'] || [];
    } catch (error) {
      logger.error(`Error fetching CMC OHLCV for ${pairAddress} on chain ${chainId}:`, error);
      return [];
    }
  }
}
