import axios from 'axios';
import BigNumber from 'bignumber.js';

import logger from '../logger';
import type { ExchangeRateData, IExchangeRateProvider, ProviderConfig } from './types';
import { SymbolNotSupportedError } from './types';

/**
 * CoinMarketCap Exchange Rate Provider
 *
 * Fetches token prices from CoinMarketCap API
 * This is the upstream data source for token-data service
 *
 * API Documentation: https://coinmarketcap.com/api/documentation/v1/
 */
export class CoinMarketCapProvider implements IExchangeRateProvider {
  public readonly name = 'coinmarketcap';

  private baseUrl: string;
  private timeout: number;
  private apiKey?: string;

  constructor(config: ProviderConfig = {}) {
    this.baseUrl = config.base_url || 'https://pro-api.coinmarketcap.com/v1';
    this.timeout = config.timeout || 10000; // 10 seconds default
    this.apiKey = config.api_key;

    if (!this.apiKey) {
      logger.warn('CoinMarketCap API key not configured. Please set api_key in provider config.');
    }
  }

  async fetch(symbol: string): Promise<ExchangeRateData> {
    if (!this.apiKey) {
      throw new Error('CoinMarketCap API key is required. Please configure api_key in provider settings.');
    }

    try {
      const url = `${this.baseUrl}/cryptocurrency/quotes/latest`;

      logger.debug('Fetching exchange rate from CoinMarketCap', { symbol, url });

      const response = await axios.get(url, {
        params: {
          symbol: symbol.toUpperCase(),
          convert: 'USD',
        },
        timeout: this.timeout,
        headers: {
          'X-CMC_PRO_API_KEY': this.apiKey,
          Accept: 'application/json',
          'User-Agent': 'PaymentKit/1.0',
        },
      });

      if (!response.data || typeof response.data !== 'object') {
        throw new Error('Invalid response format from CoinMarketCap');
      }

      const { data, status } = response.data;

      // Check API status
      if (status?.error_code !== 0) {
        throw new Error(`CoinMarketCap API error: ${status?.error_message || 'Unknown error'}`);
      }

      // Get token data
      const tokenData = data?.[symbol.toUpperCase()];
      if (!tokenData) {
        // Symbol not found - this is a data issue, not a service issue
        throw new SymbolNotSupportedError(symbol, this.name);
      }

      // Extract USD price from quote
      const quote = tokenData.quote?.USD;
      if (!quote) {
        throw new Error(`No USD quote found for ${symbol}`);
      }

      const usdPrice = quote.price;
      if (!usdPrice || typeof usdPrice !== 'number') {
        throw new Error(`No valid USD price found for ${symbol}`);
      }

      // Validate price is positive
      const rate = new BigNumber(usdPrice);
      if (rate.lte(0) || !rate.isFinite()) {
        throw new Error(`Invalid price value: ${usdPrice}`);
      }

      // Get timestamp
      const timestampMs = quote.last_updated ? new Date(quote.last_updated).getTime() : Date.now();

      return {
        rate: rate.toString(),
        timestamp_ms: timestampMs,
        metadata: {
          source: 'coinmarketcap',
          symbol: tokenData.symbol,
          name: tokenData.name,
          cmc_id: tokenData.id,
          cmc_rank: tokenData.cmc_rank,
          market_cap: quote.market_cap,
          volume_24h: quote.volume_24h,
          percent_change_24h: quote.percent_change_24h,
        },
      };
    } catch (error: any) {
      // Log the error
      logger.error('Failed to fetch exchange rate from CoinMarketCap', {
        symbol,
        error: error.message,
        response: error.response?.data,
      });

      // Determine error type and throw appropriate error
      if (error.response) {
        const { status, data } = error.response;

        // Check for CoinMarketCap specific error codes
        if (data?.status?.error_code) {
          const errorCode = data.status.error_code;
          const errorMessage = data.status.error_message;

          switch (errorCode) {
            case 1001: // API key invalid
              throw new Error('CoinMarketCap API key is invalid');
            case 1002: // API key missing
              throw new Error('CoinMarketCap API key is missing');
            case 1003: // API key plan rate limit
              throw new Error('CoinMarketCap API rate limit exceeded for your plan');
            case 1004: // API key plan payment overdue
              throw new Error('CoinMarketCap API key payment overdue');
            case 1005: // API key plan payment failed
              throw new Error('CoinMarketCap API key payment failed');
            case 1006: // API key plan upgrade required
              throw new Error('CoinMarketCap API plan upgrade required');
            case 1007: // API key disabled
              throw new Error('CoinMarketCap API key has been disabled');
            case 1008: // API key plan minute rate limit
              throw new Error('CoinMarketCap API minute rate limit exceeded');
            case 1009: // API key plan daily rate limit
              throw new Error('CoinMarketCap API daily rate limit exceeded');
            case 1010: // API key plan monthly rate limit
              throw new Error('CoinMarketCap API monthly rate limit exceeded');
            case 400: // Bad Request
              throw new Error(`CoinMarketCap bad request: ${errorMessage}`);
            case 401: // Unauthorized
              throw new Error('CoinMarketCap API authentication failed');
            case 403: // Forbidden
              throw new Error('CoinMarketCap API access forbidden');
            case 429: // Too Many Requests
              throw new Error('CoinMarketCap rate limit exceeded');
            case 500: // Internal Server Error
              throw new Error('CoinMarketCap server error');
            default:
              throw new Error(`CoinMarketCap API error (${errorCode}): ${errorMessage}`);
          }
        }

        // Generic HTTP error handling
        if (status === 404) {
          throw new SymbolNotSupportedError(symbol, this.name);
        } else if (status === 429) {
          throw new Error('CoinMarketCap rate limit exceeded');
        } else if (status >= 500) {
          throw new Error(`CoinMarketCap server error: ${status}`);
        } else {
          throw new Error(`CoinMarketCap request failed: ${status}`);
        }
      } else if (error.code === 'ECONNABORTED') {
        throw new Error(`CoinMarketCap request timeout after ${this.timeout}ms`);
      } else if (error.code === 'ENOTFOUND' || error.code === 'ECONNREFUSED') {
        throw new Error(`Cannot connect to CoinMarketCap: ${error.message}`);
      } else {
        throw new Error(`CoinMarketCap fetch error: ${error.message}`);
      }
    }
  }
}
