import axios from 'axios';
// eslint-disable-next-line import/no-extraneous-dependencies
import BigNumber from 'bignumber.js';

import logger from '../logger';
import { getTokenInfo } from './token-address-mapping';
import type { ExchangeRateData, IExchangeRateProvider, ProviderConfig } from './types';
import { SymbolNotSupportedError } from './types';

/**
 * Token Data Provider (ArcBlock)
 *
 * Fetches token prices from token-data.arcblock.io using contract addresses
 * Uses Ethereum mainnet (chainId: 1) contract addresses
 */
export class TokenDataProvider implements IExchangeRateProvider {
  public readonly name = 'token-data';

  private baseUrl: string;
  private timeout: number;
  private zeroAddress = '0x0000000000000000000000000000000000000000';

  constructor(config: ProviderConfig = {}) {
    this.baseUrl = config.base_url || 'https://token-data.arcblock.io';
    this.timeout = config.timeout || 10000; // 10 seconds default
  }

  async fetch(symbol: string): Promise<ExchangeRateData> {
    try {
      // Get token address from mapping
      const tokenInfo = getTokenInfo(symbol);
      if (!tokenInfo) {
        // Symbol not in mapping - this is a data issue, not a service issue
        throw new SymbolNotSupportedError(symbol, this.name);
      }

      const addressLower = (tokenInfo.address || '').toLowerCase();
      const useSymbolEndpoint = !addressLower || addressLower === this.zeroAddress || !addressLower.startsWith('0x');
      const url = useSymbolEndpoint
        ? `${this.baseUrl}/api/token-price-by-symbol`
        : `${this.baseUrl}/api/token-price-by-address`;

      logger.debug('Fetching exchange rate from token-data', {
        symbol,
        address: tokenInfo.address,
        endpoint: useSymbolEndpoint ? 'token-price-by-symbol' : 'token-price-by-address',
      });

      const response = await axios.get(url, {
        params: useSymbolEndpoint
          ? {
              symbols: tokenInfo.symbol,
            }
          : {
              addresses: addressLower,
            },
        timeout: this.timeout,
        headers: {
          'User-Agent': 'PaymentKit/1.0',
        },
      });

      if (!response.data || typeof response.data !== 'object') {
        logger.error('Invalid response format from token-data', { response: response.data });
        throw new Error('Invalid response format from token-data');
      }

      const responseData = response.data;

      // API returns: { data: [...], total, page, size }
      if (!responseData.data || !Array.isArray(responseData.data) || responseData.data.length === 0) {
        logger.error('No token data in response', {
          symbol,
          address: addressLower,
          endpoint: useSymbolEndpoint ? 'token-price-by-symbol' : 'token-price-by-address',
        });
        throw new Error(`No token data found for ${symbol}`);
      }

      const tokenData = responseData.data[0];

      // Extract USD price from tokenData.price.USD
      const usdPrice = tokenData.price?.USD;
      if (!usdPrice || typeof usdPrice !== 'number') {
        logger.error('No valid USD price in token data', {
          symbol,
          priceObject: tokenData.price,
        });
        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 - use updatedAt from token data
      const timestampMs = tokenData.updatedAt ? new Date(tokenData.updatedAt).getTime() : Date.now();

      return {
        rate: rate.toString(),
        timestamp_ms: timestampMs,
        metadata: {
          source: 'token-data',
          address: tokenInfo.address,
          symbol: tokenData.symbol,
          name: tokenData.name,
          percent_change_24h: tokenData.percent_change_24h?.USD,
        },
      };
    } catch (error: any) {
      // Log the error
      logger.error('Failed to fetch exchange rate from token-data', {
        symbol,
        error: error.message,
        response: error.response?.data,
      });

      // Determine error type and throw appropriate error
      // Re-throw SymbolNotSupportedError as-is (don't wrap it)
      if (error instanceof SymbolNotSupportedError) {
        throw error;
      }
      if (error.response) {
        if (error.response.status === 404) {
          throw new SymbolNotSupportedError(symbol, this.name);
        } else if (error.response.status >= 500) {
          throw new Error(`token-data server error: ${error.response.status}`);
        } else {
          throw new Error(`token-data request failed: ${error.response.status}`);
        }
      } else if (error.code === 'ECONNABORTED') {
        throw new Error(`token-data request timeout after ${this.timeout}ms`);
      } else if (error.code === 'ENOTFOUND' || error.code === 'ECONNREFUSED') {
        throw new Error(`Cannot connect to token-data: ${error.message}`);
      } else {
        throw new Error(`token-data fetch error: ${error.message}`);
      }
    }
  }
}
