import BN from "bn.js";
import { ceilDiv, fee } from "./util";
import {
  BuyBaseInputResult,
  BuyQuoteInputResult,
  GlobalConfig,
} from "../types/sdk";
import { PublicKey } from "@solana/web3.js";

export function buyBaseInputInternal(
  base: BN,
  slippage: number, // 1 => 1%
  baseReserve: BN,
  quoteReserve: BN,
  globalConfig: GlobalConfig,
  coinCreator: PublicKey,
): BuyBaseInputResult {
  // -----------------------------------------------------
  // 1) Basic validations
  // -----------------------------------------------------
  if (baseReserve.isZero() || quoteReserve.isZero()) {
    throw new Error(
      "Invalid input: 'baseReserve' or 'quoteReserve' cannot be zero.",
    );
  }
  if (base.gt(baseReserve)) {
    throw new Error("Cannot buy more base tokens than the pool reserves.");
  }

  // -----------------------------------------------------
  // 2) Calculate the raw quote needed (Raydium-like formula)
  //    quote_amount_in = ceil_div(quote_reserve * base, base_reserve - base)
  // -----------------------------------------------------
  const numerator = quoteReserve.mul(base);
  const denominator = baseReserve.sub(base);

  if (denominator.isZero()) {
    throw new Error("Pool would be depleted; denominator is zero.");
  }

  const quoteAmountIn = ceilDiv(numerator, denominator);

  // -----------------------------------------------------
  // 3) Calculate fees
  //    - LP Fee = floor((quoteAmountIn * lpFeeBps) / 10000)
  //    - Protocol Fee = floor((quoteAmountIn * protocolFeeBps) / 10000)
  // -----------------------------------------------------
  const lpFee = fee(quoteAmountIn, globalConfig.lpFeeBasisPoints);
  const protocolFee = fee(quoteAmountIn, globalConfig.protocolFeeBasisPoints);
  const coinCreatorFee = PublicKey.default.equals(coinCreator)
    ? new BN(0)
    : fee(quoteAmountIn, globalConfig.coinCreatorFeeBasisPoints);
  const totalQuote = quoteAmountIn
    .add(lpFee)
    .add(protocolFee)
    .add(coinCreatorFee);

  // -----------------------------------------------------
  // 4) Calculate maxQuote with slippage
  //    If slippage=1 => factor = (1 + 1/100) = 1.01
  // -----------------------------------------------------
  const precision = new BN(1_000_000_000); // For slippage calculations
  const slippageFactorFloat = (1 + slippage / 100) * 1_000_000_000;
  const slippageFactor = new BN(Math.floor(slippageFactorFloat));

  // maxQuote = totalQuote * slippageFactor / 1e9
  const maxQuote = totalQuote.mul(slippageFactor).div(precision);

  return {
    internalQuoteAmount: quoteAmountIn,
    uiQuote: totalQuote, // Final total quote after fees
    maxQuote,
  };
}

export function buyQuoteInputInternal(
  quote: BN,
  slippage: number, // 1 => 1%
  baseReserve: BN,
  quoteReserve: BN,
  globalConfig: GlobalConfig,
  coinCreator: PublicKey,
): BuyQuoteInputResult {
  // -----------------------------------------------------
  // 1) Basic validations
  // -----------------------------------------------------
  if (baseReserve.isZero() || quoteReserve.isZero()) {
    throw new Error(
      "Invalid input: 'baseReserve' or 'quoteReserve' cannot be zero.",
    );
  }

  // -----------------------------------------------------
  // 2) Calculate total fee basis points and denominator
  // -----------------------------------------------------
  const totalFeeBps = globalConfig.lpFeeBasisPoints
    .add(globalConfig.protocolFeeBasisPoints)
    .add(
      PublicKey.default.equals(coinCreator)
        ? new BN(0)
        : globalConfig.coinCreatorFeeBasisPoints,
    );
  const denominator = new BN(10_000).add(totalFeeBps);

  // -----------------------------------------------------
  // 3) Calculate effective quote amount
  // -----------------------------------------------------
  const effectiveQuote = quote.mul(new BN(10_000)).div(denominator);

  // -----------------------------------------------------
  // 4) Calculate the base tokens received using effectiveQuote
  //    base_amount_out = floor(base_reserve * effectiveQuote / (quote_reserve + effectiveQuote))
  // -----------------------------------------------------
  const numerator = baseReserve.mul(effectiveQuote);
  const denominatorEffective = quoteReserve.add(effectiveQuote);

  if (denominatorEffective.isZero()) {
    throw new Error("Pool would be depleted; denominator is zero.");
  }

  const baseAmountOut = numerator.div(denominatorEffective);

  // -----------------------------------------------------
  // 5) Calculate maxQuote with slippage
  //    If slippage=1 => factor = (1 + 1/100) = 1.01
  // -----------------------------------------------------
  const precision = new BN(1_000_000_000); // For slippage calculations
  const slippageFactorFloat = (1 + slippage / 100) * 1_000_000_000;
  const slippageFactor = new BN(Math.floor(slippageFactorFloat));

  // maxQuote = quote * slippageFactor / 1e9
  const maxQuote = quote.mul(slippageFactor).div(precision);

  return {
    base: baseAmountOut, // Base tokens received after fees
    internalQuoteWithoutFees: effectiveQuote,
    maxQuote, // Maximum quote tokens to pay (with slippage)
  };
}
