interface OpenOceanToken {
  address: string
  symbol: string
  decimals: number
  name: string
  icon?: string
  usd?: string
  chainId?: number
}

export class OpenOceanService {
  // private static readonly API_V1_URL = 'https://ethapi.openocean.finance/v1'
  //private static readonly API_V2_URL = 'https://ethapi.openocean.finance/v2'
  private static readonly API_V3_URL = 'https://open-api.openocean.finance/v3'
  private static readonly API_V4_URL = 'https://open-api.openocean.finance/v4'

  // Chain ID to OpenOcean chain name mapping
  private static readonly CHAIN_ID_MAP: Record<string | number, string> = {
    1151111081099710: 'solana', // Solana mainnet
  }

  // Get OpenOcean supported chain name
  private static getChainName(chainId: string | number): string {
    return this.CHAIN_ID_MAP[chainId] || chainId.toString()
  }

  // Get API URL based on chain ID
  private static getApiUrl(chainId: string | number): string {
    // If chainId exists in CHAIN_ID_MAP, use V1 API
    const chainName = this.getChainName(chainId)
    return Object.keys(this.CHAIN_ID_MAP).includes(chainId.toString())
      ? `${this.API_V4_URL}/${chainName}`
      : `${this.API_V4_URL}/${chainId}`
  }

  private static getSolanaAddress(chain: string, tokenAddress: string): string {
    if (chain === '1151111081099710' && tokenAddress === 'So11111111111111111111111111111111111111112') {
      return '11111111111111111111111111111111'
    } else if (chain === '1151111081099710' && tokenAddress === '11111111111111111111111111111111') {
      return 'So11111111111111111111111111111111111111112'
    }
    return tokenAddress
  }

  static async getQuote(params: {
    chain: string
    inTokenAddress: string
    inTokenSymbol: string
    outTokenAddress: string
    outTokenSymbol: string
    amount: string
    slippage?: string
    gasPrice?: string
    disabledDexIds?: string
    enabledDexIds?: string
    referrer?: string
  }) {
    const apiUrl = this.getApiUrl(params.chain)
    const slippage = (Number(params.slippage || 0.01)).toString();
    const queryParams = new URLSearchParams({
      quoteType: 'quote',
      inTokenSymbol: params.inTokenSymbol,
      inTokenAddress: params.inTokenAddress,
      outTokenSymbol: params.outTokenSymbol,
      outTokenAddress: params.outTokenAddress,
      amountDecimals: params.amount,
      slippage,
      gasPriceDecimals: params.gasPrice || '',
      disabledDexIds: params.disabledDexIds || '',
      enabledDexIds: params.enabledDexIds || '',
      referrer: params.referrer || '0x3487ef9f9b36547e43268b8f0e2349a226c70b53',
    })

    const response = await fetch(`${apiUrl}/quote?${queryParams.toString()}`)
    return response.json()
  }

  static async getSwapQuote(params: {
    chain: string
    inTokenAddress: string
    inTokenSymbol: string
    outTokenAddress: string
    outTokenSymbol: string
    amount: string
    account: string
    slippage?: string
    gasPrice?: string
    disabledDexIds?: string
    enabledDexIds?: string
    referrer?: string,
    referrerFee?: string,
    referrerFeeShare?: string
  }) {
    const apiUrl = this.getApiUrl(params.chain)
    const slippage = (Number(params.slippage || 0.01)).toString();
    const referrer: any = {
      referrer: params.referrer || '0x3487ef9f9b36547e43268b8f0e2349a226c70b53',
    }
    if (params.referrerFee) {
      referrer.referrerFee = Number(params.referrerFee);
      // referrer.referrerFeeShare = params.referrerFeeShare || '1500';
    }
    const queryParams = new URLSearchParams({
      quoteType: 'swap',
      inTokenSymbol: params.inTokenSymbol,
      inTokenAddress: this.getSolanaAddress(params.chain, params.inTokenAddress),
      outTokenSymbol: params.outTokenSymbol,
      outTokenAddress: this.getSolanaAddress(params.chain, params.outTokenAddress),
      amountDecimals: params.amount,
      account: params.account,
      slippage,
      gasPriceDecimals: params.gasPrice || '',
      disabledDexIds: params.disabledDexIds || '',
      enabledDexIds: params.enabledDexIds || '',
      ...referrer,
    })
    // const isV1Api = Object.keys(this.CHAIN_ID_MAP).includes(params.chain.toString())
    // const swapEndpoint = isV1Api ? 'swap-quote' : 'swap'
    const response = await fetch(`${apiUrl}/swap?${queryParams.toString()}`)
    return response.json()
  }

  static async getTokenList(chain: string) {
    const chainName = this.getChainName(chain)
    const response = await fetch(`${this.API_V4_URL}/${chainName}/tokenList`)
    const data = await response.json()
    if (data.code !== 200) {
      throw new Error('Failed to fetch token list')
    }
    return data.data.map((token: OpenOceanToken) => {
      let address = token.address
      // Convert Solana native token address
      if (chain === '1151111081099710' && address === 'So11111111111111111111111111111111111111112') {
        address = '11111111111111111111111111111111'
      }
      // Convert Base chain native token address
      if (address === '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE') {
        address = '0x0000000000000000000000000000000000000000'
      }
      return {
        address,
        symbol: token.symbol,
        decimals: token.decimals,
        name: token.name,
        logoURI: token.icon,
        priceUSD: token.usd,
        chainId: Number(chain),
        amount: 0n,
        featured: false,
        popular: false
      }
    })
  }

  static async getDexList(chain: string) {
    const apiUrl = this.getApiUrl(chain)
    const response = await fetch(`${apiUrl}/dexList`)
    return response.json()
  }

  static async getGasPrice(chain: string) {
    if (!chain || chain === '1151111081099710') {
      return {
        data: {
          gasPrice: '1000000000000000000',
        },
      }
    }
    // const apiUrl = this.getApiUrl(chain)
    const response = await fetch(`${this.API_V4_URL}/${chain}/gasPrice`)
    const { data } = await response.json()
    return data
  }

  static async getTokenInfo(chain: string, tokenAddress: string) {
    const chainName = this.getChainName(chain)
    // Check if the address is valid
    if (!tokenAddress || (!/^0x[a-fA-F0-9]{40}$/.test(tokenAddress) && !/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(tokenAddress))) {
      throw new Error('Invalid token address')
    } 
    const response = await fetch(`${this.API_V4_URL}/${chainName}/getTokenInfo?tokenAddress=${tokenAddress}`)
    const data = await response.json()
    if (!data || !data.address || !data.symbol || !data.decimals) {
      throw new Error('Failed to fetch token info')
    }
    return {
      address: data.address,
      symbol: data.symbol,
      decimals: data.decimals,
      name: data.name,
      logoURI: data.icon,
      priceUSD: data.usd,
      chainId: Number(chain),
      amount: 0n,
      featured: false,
      popular: false
    }
  }

  /**
   * Get prices for specified tokens
   * @param chain chain name, e.g. 'arbitrum'
   * @param tokenAddresses array of token addresses
   * @returns Promise<Record<string, string>> returns an object where key is token address and value is price
   */
  static async getTokensPrice(chain: string, tokenAddresses: string[]): Promise<Record<string, string>> {
    const chainName = this.getChainName(chain)
    const tokenAddressesStr = tokenAddresses.join(',')
    const response = await fetch(`${this.API_V3_URL}/${chainName}/designated_tokenList?tokens=${tokenAddressesStr}`)
    const data = await response.json()
    
    if (data.code !== 200) {
      throw new Error('Failed to fetch token prices')
    }

    const prices = data.data.reduce((acc: Record<string, string>, token: OpenOceanToken) => {
      if (token.address && token.usd) {
        acc[token.address.toLowerCase()] = token.usd
      }
      return acc
    }, {})

    return prices
  }
}

