import {encodeFunctionData} from "viem";

import StBTCABI from "../../abis/StBTC.json" with {type: "json"};
import {BOTANIX} from "../../configs/chains.js";
import {getContract} from "../../configs/contracts.js";
import {TokensData} from "../../types/tokens.js";
import {ExternalSwapAggregator, ExternalSwapQuote} from "../../types/trade.js";
import {bigMath} from "../bigmath.js";
import {BASIS_POINTS_DIVISOR_BIGINT, getBasisPoints} from "../numbers.js";
import {AVAILABLE_BOTANIX_DEPOSIT_PAIRS, AVAILABLE_BOTANIX_WITHDRAW_PAIRS} from "./externalSwapPath.js";
import {convertToUsd, getMidPrice, getTokenData} from "../tokens.js";

const COEF_REDUCER = getBasisPoints(1n, 10000n);

export const getBotanixStakingExternalSwapQuote = ({
  tokenInAddress,
  tokenOutAddress,
  amountIn,
  gasPrice,
  receiverAddress,
  tokensData,
  assetsPerShare,
}: {
  tokenInAddress: string;
  tokenOutAddress: string;
  amountIn: bigint;
  gasPrice: bigint;
  receiverAddress: string;
  tokensData: TokensData;
  assetsPerShare: bigint;
}): ExternalSwapQuote | undefined => {
  const inTokenData = getTokenData(tokensData, tokenInAddress);
  const outTokenData = getTokenData(tokensData, tokenOutAddress);

  const assetsPerShareRate = getBasisPoints(assetsPerShare, 10n ** 18n) - COEF_REDUCER;
  const sharesPerAssetRate = getBasisPoints(10n ** 18n, assetsPerShare) - COEF_REDUCER;

  if (!inTokenData || !outTokenData) {
    return undefined;
  }

  if (AVAILABLE_BOTANIX_DEPOSIT_PAIRS.some((pair) => pair.from === tokenInAddress && pair.to === tokenOutAddress)) {
    const priceIn = getMidPrice(inTokenData.prices);
    const priceOut = bigMath.mulDiv(priceIn, sharesPerAssetRate, BASIS_POINTS_DIVISOR_BIGINT);
    const usdIn = convertToUsd(amountIn, inTokenData.decimals, priceIn);
    const amountOut =
      amountIn > 0n ? bigMath.mulDiv(amountIn, sharesPerAssetRate, BASIS_POINTS_DIVISOR_BIGINT) - gasPrice : 0n;
    const usdOut = amountOut > 0n ? convertToUsd(amountOut, outTokenData.decimals, priceOut) : 0n;
    return {
      aggregator: ExternalSwapAggregator.BotanixStaking,
      inTokenAddress: tokenInAddress,
      outTokenAddress: tokenOutAddress,
      receiver: receiverAddress,
      amountIn,
      amountOut,
      usdIn: usdIn!,
      usdOut: usdOut!,
      priceIn,
      priceOut,
      feesUsd: gasPrice,
      needSpenderApproval: true,
      txnData: {
        to: getContract(BOTANIX, "StBTC"),
        data: encodeFunctionData({
          abi: StBTCABI.abi,
          functionName: "deposit",
          args: [amountIn, receiverAddress],
        }),
        value: 0n,
        estimatedGas: gasPrice,
        estimatedExecutionFee: gasPrice,
      },
    };
  }

  if (AVAILABLE_BOTANIX_WITHDRAW_PAIRS.some((pair) => pair.from === tokenInAddress && pair.to === tokenOutAddress)) {
    const priceIn = getMidPrice(inTokenData.prices);
    const priceOut = bigMath.mulDiv(priceIn, assetsPerShareRate, BASIS_POINTS_DIVISOR_BIGINT);
    const usdIn = convertToUsd(amountIn, inTokenData.decimals, priceIn);
    const amountOut =
      amountIn > 0n ? bigMath.mulDiv(amountIn, assetsPerShareRate, BASIS_POINTS_DIVISOR_BIGINT) - gasPrice : 0n;
    const usdOut = amountOut > 0n ? convertToUsd(amountOut, outTokenData.decimals, priceOut) : 0n;

    return {
      aggregator: ExternalSwapAggregator.BotanixStaking,
      inTokenAddress: tokenInAddress,
      outTokenAddress: tokenOutAddress,
      receiver: receiverAddress,
      amountIn,
      amountOut,
      usdIn: usdIn!,
      usdOut: usdOut!,
      priceIn,
      priceOut,
      feesUsd: gasPrice,
      needSpenderApproval: true,
      txnData: {
        to: getContract(BOTANIX, "StBTC"),
        data: encodeFunctionData({
          abi: StBTCABI.abi,
          functionName: "withdraw",
          args: [amountIn, receiverAddress, getContract(BOTANIX, "ExternalHandler")],
        }),
        value: 0n,
        estimatedGas: gasPrice,
        estimatedExecutionFee: gasPrice,
      },
    };
  }
};
