import { getData } from "../../ao/messaging/getData";
import { TokenInput } from "../../ao/utils/tokenInput";
import {
  collateralEnabledTickers,
  tokens,
  tokenData,
  SupportedTokensTickers,
  controllerAddress,
} from "../../ao/utils/tokenAddressData";
import { redstoneOracleAddress } from "../../ao/utils/tokenAddressData";
import { getAllPositions } from "../protocolData/getAllPositions";
import { DryRunResult } from "@permaweb/aoconnect/dist/lib/dryrun"

export interface GetLiquidationsRes {
  liquidations: Map<string, QualifyingPosition>;
  prices: RedstonePrices;
}

export interface QualifyingPosition {
  /* The tokens that can be liquidated */
  debts: {
    ticker: string;
    quantity: BigInt;
  }[];
  /* The available collaterals that can be received for the liquidation */
  collaterals: {
    ticker: string;
    quantity: BigInt;
  }[];
  /** The current discount percentage for this liquidation (multiplied by the precision factor) */
  discount: BigInt;
}

export type RedstonePrices = Record<
  string,
  { t: number; a: string; v: number }
>;

interface Tag {
  name: string;
  value: string;
}

// Base token position with the core metrics
interface TokenPosition {
  borrowBalance: BigInt;
  capacity: BigInt;
  collateralization: BigInt;
  liquidationLimit: BigInt;
}

// Extended token position with USD values
interface TokenPositionUSD extends TokenPosition {
  borrowBalanceUSD: BigInt;
  capacityUSD: BigInt;
  collateralizationUSD: BigInt;
  liquidationLimitUSD: BigInt;
}

// Global position across all tokens
interface GlobalPosition {
  borrowBalanceUSD: BigInt;
  capacityUSD: BigInt;
  collateralizationUSD: BigInt;
  liquidationLimitUSD: BigInt;
  tokenPositions: {
    [token: string]: TokenPosition;
  };
}

export async function getLiquidations(
  precisionFactor: number,
): Promise<GetLiquidationsRes> {
  try {
    if (!Number.isInteger(precisionFactor)) {
      throw new Error("The precision factor has to be an integer");
    }

    // Get list of tokens to process
    const tokensList = Object.keys(tokens);

    /*const [redstonePriceFeedRes, positionsList, auctionsRes] =
      await Promise.all([
        // Make a request to RedStone oracle process for prices (same used onchain)
        getData({
          Target: redstoneOracleAddress,
          Action: "v2.Request-Latest-Data",
          Tickers: JSON.stringify(
            collateralEnabledTickers.map((ticker) =>
              ticker === "QAR" ? "AR" : ticker,
            ),
          ),
        }),
        // Get positions for each token
        Promise.all(
          tokensList.map(async (token) => ({
            token,
            positions: await getAllPositions({ token }),
          })),
        ),
        // get discovered liquidations
        getData({
          Target: controllerAddress,
          Action: "Get-Auctions",
        })
      ]);*/
/*
    console.log(
      JSON.stringify(redstonePriceFeedRes.Messages, null, 2),
      JSON.stringify(positionsList, null, 2),
      JSON.stringify(auctionsRes.Messages, null, 2)
    )*/

    const [redstonePriceFeedRes, positionsList, auctionsRes] = [
      {
        Messages: [{
          Data: JSON.stringify({
            "AR": {
              "a": "0x0000000000000000000000000000000000000000",
              "v": 2.1,
              "t": 1742900720937
            },
            "USDC": {
              "a": "0x0000000000000000000000000000000000000000",
              "v": 1,
              "t": 1742900720937
            }
          })
        }]
      },
      [
        {
          token: "QAR",
          positions: {
            "-CZI5_c-SA_0gnwyXxE9zJyBvEvy5gxEoTwtxLID9UE": {
              "collateralization": BigInt("20000007272774"),
              "capacity": BigInt("10000003636387"),
              "borrowBalance": BigInt("0"),
              "liquidationLimit": BigInt("10200003709114")
            },
            "ljvCPN31XCLPkBo9FUeB7vAK0VC6-eY52-CS-6Iho8U": {
              "collateralization": BigInt("0"),
              "capacity": BigInt("0"),
              "borrowBalance": BigInt("2500007272774"),
              "liquidationLimit": BigInt("0")
            }
          } as { [a: string]: TokenPosition }
        },
        {
          token: "USDC",
          positions: {
            "ljvCPN31XCLPkBo9FUeB7vAK0VC6-eY52-CS-6Iho8U": {
              "borrowBalance": BigInt("0"),
              "collateralization": BigInt("10000000000000"),
              "liquidationLimit": BigInt("5100000000000"),
              "capacity": BigInt("5000000000000")
            }
          } as { [a: string]: TokenPosition }
        }
      ],
      {
        Messages: [{
          Tags: [
            { name: "Initial-Discount", value: "5" },
            { name: "Discount-Interval", value: (1000 * 60 * 60).toString() }
          ],
          Data: JSON.stringify({
            "ljvCPN31XCLPkBo9FUeB7vAK0VC6-eY52-CS-6Iho8U": Date.now() - 1000 * 60 * 0
          })
        }]
      }
    ]

    // parse prices and auctions
    const prices: RedstonePrices = JSON.parse(
      redstonePriceFeedRes.Messages[0].Data,
    );
    const auctions: Record<string, number> = JSON.parse(
      auctionsRes.Messages[0].Data,
    );

    // maximum discount percentage and discount period
    const auctionTags = Object.fromEntries(
      auctionsRes.Messages[0].Tags.map((tag: Tag) => [tag.name, tag.value]),
    );
    const maxDiscount = parseFloat(auctionTags["Initial-Discount"] || "0");
    const discountInterval = parseInt(auctionTags["Discount-Interval"] || "0");

    // Create a map to store global positions by wallet address
    const globalPositions = new Map<string, GlobalPosition>();

    // Calculate global positions for all wallets across all tokens
    for (const { token, positions: localPositions } of positionsList) {
      // token data
      const tokenPrice = prices[token === "QAR" ? "AR" : token].v;
      const tokenDenomination =
        tokenData[token as SupportedTokensTickers].denomination;

      // Use the token's specific denomination for scaling
      const scale = BigInt(10) ** tokenDenomination;
      const priceScaled = BigInt(Math.round(tokenPrice * Number(scale)));

      // loop through all positions, add them to the global positions
      for (const [walletAddress, position] of Object.entries<TokenPosition>(
        localPositions,
      )) {
        const posValueUSD = {
          borrowBalanceUSD:
            ((position.borrowBalance as bigint) * priceScaled) / scale,
          capacityUSD: ((position.capacity as bigint) * priceScaled) / scale,
          collateralizationUSD:
            ((position.collateralization as bigint) * priceScaled) / scale,
          liquidationLimitUSD:
            ((position.liquidationLimit as bigint) * priceScaled) / scale,
        };

        if (!globalPositions.has(walletAddress)) {
          // no global position calculated for this user yet
          globalPositions.set(walletAddress, {
            ...posValueUSD,
            tokenPositions: { [token]: position },
          });
        } else {
          // update existing global position
          const globalPos = globalPositions.get(walletAddress);

          // @ts-expect-error
          globalPos!.borrowBalanceUSD += posValueUSD.borrowBalanceUSD;
          // @ts-expect-error
          globalPos!.capacityUSD += posValueUSD.capacityUSD;
          // @ts-expect-error
          globalPos!.collateralizationUSD += posValueUSD.collateralizationUSD;
          // @ts-expect-error
          globalPos!.liquidationLimitUSD += posValueUSD.liquidationLimitUSD;
          globalPos!.tokenPositions[token] = position;
        }
      }
    }

    // Initialize available liquidations object
    const res = new Map<string, QualifyingPosition>();

    // Initialize liquidations object for each supported token
    for (const [walletAddress, position] of globalPositions) {
      // Check if the position is eligible for liquidation
      // A position is eligible if borrowBalanceUSD > liquidationLimitUSD
      if (position.borrowBalanceUSD <= position.liquidationLimitUSD) continue;

      // time calculations for the discount
      const currentTime = Date.now();
      let timeSinceDiscovery =
        currentTime - (auctions[walletAddress] || currentTime);

      // maximum price reached, no discount applied
      if (timeSinceDiscovery > discountInterval) {
        timeSinceDiscovery = discountInterval;
      }

      // calculate the discount for this user
      const discount = BigInt(
        Math.max(
          Math.floor(
            ((discountInterval - timeSinceDiscovery) *
              maxDiscount *
              precisionFactor) /
              discountInterval,
          ),
          0,
        ),
      );

      // the final position that can be liquidated, with rewards and collaterals
      const qualifyingPos: QualifyingPosition = {
        debts: [],
        collaterals: [],
        discount,
      };

      // find rewards and debt
      for (const [token, localPosition] of Object.entries<TokenPosition>(
        position.tokenPositions,
      )) {
        // found a debt
        if ((localPosition.borrowBalance as bigint) > BigInt(0)) {
          qualifyingPos.debts.push({
            ticker: token,
            quantity: localPosition.borrowBalance,
          });
        }

        // found a reward
        if ((localPosition.collateralization as bigint) > BigInt(0)) {
          qualifyingPos.collaterals.push({
            ticker: token,
            quantity: localPosition.collateralization,
          });
        }
      }

      // add qualifying position as an opportunity to liquidate
      res.set(walletAddress, qualifyingPos);
    }

    return {
      liquidations: res,
      prices,
    };
  } catch (error) {
    throw new Error(`Error in getLiquidations function: ${error}`);
  }
}
