import { constructSimpleSDK, type OptimalRate } from '@paraswap/sdk';
import type { SupportedEvmChain } from '../../chains.js';
import { ChainProvider } from '../../core/evm/chain-provider.js';
import { getContract, type Address } from 'viem';
import { ERC20ABI } from '../../core/evm/abi/erc20.js';
import { isNativeEvmToken, WRAPPED_ETH_ADDRESSES } from '../../constants.js';

export type ParaswapQuoteRequestParams = {
  srcToken: string;
  destToken: string;
  amount: bigint;
  side: 'SELL' | 'BUY';
};

export type SwapEstimateResult = {
  amountOut: bigint;
  amountIn: bigint;
  amountOutUsd: number;
  amountInUsd: number;
  quote: OptimalRate;
};

export class ParaswapQuoteProvider {
  protected chainId: SupportedEvmChain;

  constructor(chainId: SupportedEvmChain) {
    this.chainId = chainId;
  }

  private async getTokenDecimals(tokenAddress: Address): Promise<number> {
    const client = ChainProvider.getClient(this.chainId);
    const erc20Contract = getContract({
      abi: ERC20ABI,
      address: tokenAddress,
      client,
    });

    return erc20Contract.read.decimals();
  }

  private async getQuote(params: ParaswapQuoteRequestParams): Promise<OptimalRate> {
    if (isNativeEvmToken(params.destToken)) {
      params.destToken = WRAPPED_ETH_ADDRESSES[this.chainId];
    }

    const [srcTokenDecimals, destTokenDecimals] = await Promise.all([
      this.getTokenDecimals(params.srcToken as Address),
      this.getTokenDecimals(params.destToken as Address),
    ]);

    const sdk = constructSimpleSDK({ chainId: this.chainId, fetch: fetch });
    return sdk.swap.getRate({
      amount: params.amount.toString(),
      destDecimals: destTokenDecimals,
      destToken: params.destToken,
      side: params.side,
      srcDecimals: srcTokenDecimals,
      srcToken: params.srcToken,
    });
  }

  public async getSwapEstimation(params: ParaswapQuoteRequestParams): Promise<SwapEstimateResult> {
    const optimalRate = await this.getQuote(params);
    const amountOut = optimalRate.destAmount;
    const amountIn = optimalRate.srcAmount;

    const amountOutUsd = optimalRate.destUSD ?? 0;
    const amountInUsd = optimalRate.srcUSD ?? 0;

    return {
      amountIn: BigInt(amountIn),
      amountOut: BigInt(amountOut),
      amountOutUsd: Number(amountOutUsd),
      amountInUsd: Number(amountInUsd),
      quote: optimalRate,
    };
  }
}
