import {CLAIMABLE_FUNDING_AMOUNT, MAX_PNL_FACTOR_FOR_TRADERS_KEY} from "../../configs/dataStore.js";
import {HASHED_MARKET_CONFIG_KEYS, HASHED_MARKET_VALUES_KEYS} from "../../prebuilt/index.js";
import {MarketsData} from "../../types/markets.js";
import {TokensData} from "../../types/tokens.js";
import {hashDataMap} from "../../utils/hash.js";
import {hashMarketConfigKeys, hashMarketValuesKeys} from "../../utils/marketKeysAndConfigs.js";
import {getContractMarketPrices} from "../../utils/markets.js";
import {getByKey} from "../../utils/objects.js";
import {MarketConfigMulticallRequestConfig, MarketValuesMulticallRequestConfig} from "./types.js";
import {getContract} from "../../configs/contracts.js";
import {ContractCallsConfig} from "../../utils/multicall.js";
import {ContractsChainId} from "../../configs/chains.js";

export function buildClaimableFundingDataRequest({
  marketsAddresses,
  marketsData,
  chainId,
  account,
}: {
  marketsAddresses: string[] | undefined;
  marketsData: MarketsData | undefined;
  account: string;
  chainId: ContractsChainId;
}) {
  if (!marketsAddresses) {
    return {};
  }

  return marketsAddresses.reduce(
    (request, marketAddress) => {
      const market = getByKey(marketsData, marketAddress);

      if (!market) {
        return request;
      }

      const keys = hashDataMap({
        claimableFundingAmountLong: [
          ["bytes32", "address", "address", "address"],
          [CLAIMABLE_FUNDING_AMOUNT, marketAddress, market.longTokenAddress, account],
        ],
        claimableFundingAmountShort: [
          ["bytes32", "address", "address", "address"],
          [CLAIMABLE_FUNDING_AMOUNT, marketAddress, market.shortTokenAddress, account],
        ],
      });

      request[marketAddress] = {
        contractAddress: getContract(chainId, "DataStore"),
        abiId: "DataStore",
        calls: {
          claimableFundingAmountLong: {
            methodName: "getUint",
            params: [keys.claimableFundingAmountLong],
          },
          claimableFundingAmountShort: {
            methodName: "getUint",
            params: [keys.claimableFundingAmountShort],
          },
        },
      } satisfies ContractCallsConfig<any>;

      return request;
    },
    {} as Record<string, ContractCallsConfig<any>>
  );
}

export async function buildMarketsValuesRequest(
  chainId: number,
  {
    marketsAddresses,
    marketsData,
    tokensData,
    dataStoreAddress,
    syntheticsReaderAddress,
  }: {
    marketsAddresses: string[] | undefined;
    marketsData: MarketsData | undefined;
    tokensData: TokensData | undefined;
    dataStoreAddress: string;
    syntheticsReaderAddress: string;
  }
) {
  const request: MarketValuesMulticallRequestConfig = {};

  for (const marketAddress of marketsAddresses || []) {
    const market = getByKey(marketsData, marketAddress)!;

    if (!market) {
      // eslint-disable-next-line no-console
      console.warn(`No market data found for ${marketAddress}, skipping market values request`);
      continue;
    }

    const marketPrices = getContractMarketPrices(tokensData!, market)!;

    if (!marketPrices) {
      // eslint-disable-next-line no-console
      console.warn(`No market prices for ${marketAddress}, skipping market values request`);
      continue;
    }

    const marketProps = {
      marketToken: market.marketTokenAddress,
      indexToken: market.indexTokenAddress,
      longToken: market.longTokenAddress,
      shortToken: market.shortTokenAddress,
    };

    request[`${marketAddress}-reader`] = {
      contractAddress: syntheticsReaderAddress,
      abiId: "SyntheticsReader",
      calls: {
        marketInfo: {
          methodName: "getMarketInfo",
          params: [dataStoreAddress, marketPrices, marketAddress],
        },
        marketTokenPriceMax: {
          methodName: "getMarketTokenPrice",
          params: [
            dataStoreAddress,
            marketProps,
            marketPrices.indexTokenPrice,
            marketPrices.longTokenPrice,
            marketPrices.shortTokenPrice,
            MAX_PNL_FACTOR_FOR_TRADERS_KEY,
            true,
          ],
        },
        marketTokenPriceMin: {
          methodName: "getMarketTokenPrice",
          params: [
            dataStoreAddress,
            marketProps,
            marketPrices.indexTokenPrice,
            marketPrices.longTokenPrice,
            marketPrices.shortTokenPrice,
            MAX_PNL_FACTOR_FOR_TRADERS_KEY,
            false,
          ],
        },
      },
    } satisfies ContractCallsConfig<any>;

    let prebuiltHashedKeys = HASHED_MARKET_VALUES_KEYS[chainId]?.[marketAddress];

    if (!prebuiltHashedKeys) {
      // eslint-disable-next-line no-console
      console.warn(
        `No pre-built hashed market keys found for the market ${marketAddress}. Run \`yarn prebuild\` to generate them.`
      );

      if (!marketsData?.[marketAddress]) {
        throw new Error(`No market data found for the market ${marketAddress}`);
      }

      prebuiltHashedKeys = hashMarketValuesKeys(marketsData[marketAddress]);
    }

    const keys = {
      ...prebuiltHashedKeys,
    };

    request[`${marketAddress}-dataStore`] = {
      contractAddress: dataStoreAddress,
      abiId: "DataStore",
      calls: {
        longPoolAmount: {
          methodName: "getUint",
          params: [keys.longPoolAmount],
        },
        shortPoolAmount: {
          methodName: "getUint",
          params: [keys.shortPoolAmount],
        },
        positionImpactPoolAmount: {
          methodName: "getUint",
          params: [keys.positionImpactPoolAmount],
        },
        swapImpactPoolAmountLong: {
          methodName: "getUint",
          params: [keys.swapImpactPoolAmountLong],
        },
        swapImpactPoolAmountShort: {
          methodName: "getUint",
          params: [keys.swapImpactPoolAmountShort],
        },
        longInterestUsingLongToken: {
          methodName: "getUint",
          params: [keys.longInterestUsingLongToken],
        },
        longInterestUsingShortToken: {
          methodName: "getUint",
          params: [keys.longInterestUsingShortToken],
        },
        shortInterestUsingLongToken: {
          methodName: "getUint",
          params: [keys.shortInterestUsingLongToken],
        },
        shortInterestUsingShortToken: {
          methodName: "getUint",
          params: [keys.shortInterestUsingShortToken],
        },
        longInterestInTokensUsingLongToken: {
          methodName: "getUint",
          params: [keys.longInterestInTokensUsingLongToken],
        },
        longInterestInTokensUsingShortToken: {
          methodName: "getUint",
          params: [keys.longInterestInTokensUsingShortToken],
        },
        shortInterestInTokensUsingLongToken: {
          methodName: "getUint",
          params: [keys.shortInterestInTokensUsingLongToken],
        },
        shortInterestInTokensUsingShortToken: {
          methodName: "getUint",
          params: [keys.shortInterestInTokensUsingShortToken],
        },
      },
    } satisfies ContractCallsConfig<any>;
  }

  return request;
}

export async function buildMarketsConfigsRequest(
  chainId: number,
  {
    marketsData,
    marketsAddresses,
    dataStoreAddress,
  }: {
    marketsData: MarketsData | undefined;
    marketsAddresses: string[] | undefined;
    dataStoreAddress: string;
  }
) {
  const request: MarketConfigMulticallRequestConfig = {};
  for (const marketAddress of marketsAddresses || []) {
    let prebuiltHashedKeys = HASHED_MARKET_CONFIG_KEYS[chainId]?.[marketAddress];

    if (!prebuiltHashedKeys) {
      // eslint-disable-next-line no-console
      console.warn(
        `No pre-built hashed config keys found for the market ${marketAddress}. Run \`yarn prebuild\` to generate them.`
      );

      if (!marketsData?.[marketAddress]) {
        throw new Error(`No market data found for the market ${marketAddress}`);
      }

      prebuiltHashedKeys = hashMarketConfigKeys(marketsData[marketAddress]);
    }

    // Add validation to check for undefined keys
    const requiredKeys = [
      'isDisabled', 'maxLongPoolAmount', 'maxShortPoolAmount', 'maxLongPoolUsdForDeposit', 
      'maxShortPoolUsdForDeposit', 'longPoolAmountAdjustment', 'shortPoolAmountAdjustment',
      'reserveFactorLong', 'reserveFactorShort', 'openInterestReserveFactorLong', 
      'openInterestReserveFactorShort', 'maxOpenInterestLong', 'maxOpenInterestShort',
      'minPositionImpactPoolAmount', 'positionImpactPoolDistributionRate', 'borrowingFactorLong',
      'borrowingFactorShort', 'borrowingExponentFactorLong', 'borrowingExponentFactorShort',
      'fundingFactor', 'fundingExponentFactor', 'fundingIncreaseFactorPerSecond',
      'fundingDecreaseFactorPerSecond', 'thresholdForStableFunding', 'thresholdForDecreaseFunding',
      'minFundingFactorPerSecond', 'maxFundingFactorPerSecond', 'maxPnlFactorForTradersLong',
      'maxPnlFactorForTradersShort', 'positionFeeFactorForBalanceWasImproved',
      'positionFeeFactorForBalanceWasNotImproved', 'positionFeeFactorForPositiveImpact',
      'positionFeeFactorForNegativeImpact', 'positionImpactFactorPositive', 'positionImpactFactorNegative',
      'maxPositionImpactFactorPositive', 'maxPositionImpactFactorNegative', 'maxPositionImpactFactorForLiquidations',
      'maxLendableImpactFactor', 'maxLendableImpactFactorForWithdrawals', 'maxLendableImpactUsd',
      'lentPositionImpactPoolAmount', 'minCollateralFactor', 'minCollateralFactorForLiquidation',
      'minCollateralFactorForOpenInterestLong', 'minCollateralFactorForOpenInterestShort',
      'positionImpactExponentFactor', 'swapFeeFactorForBalanceWasImproved', 'swapFeeFactorForBalanceWasNotImproved',
      'swapFeeFactorForPositiveImpact', 'swapFeeFactorForNegativeImpact', 'atomicSwapFeeFactor',
      'swapImpactFactorPositive', 'swapImpactFactorNegative', 'swapImpactExponentFactor',
      'virtualMarketId', 'virtualShortTokenId', 'virtualLongTokenId'
    ];

    for (const key of requiredKeys) {
      if (prebuiltHashedKeys[key] === undefined) {
        if (!marketsData?.[marketAddress]) {
          throw new Error(`No market data found for the market ${marketAddress}`);
        }
        // Regenerate all keys if any are missing
        prebuiltHashedKeys = hashMarketConfigKeys(marketsData[marketAddress]);
        break;
      }
    }

    // Create calls object, filtering out any undefined keys
    const calls: Record<string, any> = {};
    
    // Helper function to add call if key exists
    const addCall = (callKey: string, methodName: string, hashKey: any) => {
      if (hashKey !== undefined) {
        calls[callKey] = {
          methodName,
          params: [hashKey],
        };
      } else {
        console.warn(`Skipping undefined hash key: ${callKey} for market ${marketAddress}`);
      }
    };

    addCall('isDisabled', 'getBool', prebuiltHashedKeys.isDisabled);
    addCall('maxLongPoolAmount', 'getUint', prebuiltHashedKeys.maxLongPoolAmount);
    addCall('maxShortPoolAmount', 'getUint', prebuiltHashedKeys.maxShortPoolAmount);
    addCall('maxLongPoolUsdForDeposit', 'getUint', prebuiltHashedKeys.maxLongPoolUsdForDeposit);
    addCall('maxShortPoolUsdForDeposit', 'getUint', prebuiltHashedKeys.maxShortPoolUsdForDeposit);
    addCall('longPoolAmountAdjustment', 'getUint', prebuiltHashedKeys.longPoolAmountAdjustment);
    addCall('shortPoolAmountAdjustment', 'getUint', prebuiltHashedKeys.shortPoolAmountAdjustment);
    addCall('reserveFactorLong', 'getUint', prebuiltHashedKeys.reserveFactorLong);
    addCall('reserveFactorShort', 'getUint', prebuiltHashedKeys.reserveFactorShort);
    addCall('openInterestReserveFactorLong', 'getUint', prebuiltHashedKeys.openInterestReserveFactorLong);
    addCall('openInterestReserveFactorShort', 'getUint', prebuiltHashedKeys.openInterestReserveFactorShort);
    addCall('maxOpenInterestLong', 'getUint', prebuiltHashedKeys.maxOpenInterestLong);
    addCall('maxOpenInterestShort', 'getUint', prebuiltHashedKeys.maxOpenInterestShort);
    addCall('minPositionImpactPoolAmount', 'getUint', prebuiltHashedKeys.minPositionImpactPoolAmount);
    addCall('positionImpactPoolDistributionRate', 'getUint', prebuiltHashedKeys.positionImpactPoolDistributionRate);
    addCall('borrowingFactorLong', 'getUint', prebuiltHashedKeys.borrowingFactorLong);
    addCall('borrowingFactorShort', 'getUint', prebuiltHashedKeys.borrowingFactorShort);
    addCall('borrowingExponentFactorLong', 'getUint', prebuiltHashedKeys.borrowingExponentFactorLong);
    addCall('borrowingExponentFactorShort', 'getUint', prebuiltHashedKeys.borrowingExponentFactorShort);
    addCall('fundingFactor', 'getUint', prebuiltHashedKeys.fundingFactor);
    addCall('fundingExponentFactor', 'getUint', prebuiltHashedKeys.fundingExponentFactor);
    addCall('fundingIncreaseFactorPerSecond', 'getUint', prebuiltHashedKeys.fundingIncreaseFactorPerSecond);
    addCall('fundingDecreaseFactorPerSecond', 'getUint', prebuiltHashedKeys.fundingDecreaseFactorPerSecond);
    addCall('thresholdForStableFunding', 'getUint', prebuiltHashedKeys.thresholdForStableFunding);
    addCall('thresholdForDecreaseFunding', 'getUint', prebuiltHashedKeys.thresholdForDecreaseFunding);
    addCall('minFundingFactorPerSecond', 'getUint', prebuiltHashedKeys.minFundingFactorPerSecond);
    addCall('maxFundingFactorPerSecond', 'getUint', prebuiltHashedKeys.maxFundingFactorPerSecond);
    addCall('maxPnlFactorForTradersLong', 'getUint', prebuiltHashedKeys.maxPnlFactorForTradersLong);
    addCall('maxPnlFactorForTradersShort', 'getUint', prebuiltHashedKeys.maxPnlFactorForTradersShort);
    addCall('positionFeeFactorForBalanceWasImproved', 'getUint', prebuiltHashedKeys.positionFeeFactorForBalanceWasImproved);
    addCall('positionFeeFactorForBalanceWasNotImproved', 'getUint', prebuiltHashedKeys.positionFeeFactorForBalanceWasNotImproved);
    addCall('positionFeeFactorForPositiveImpact', 'getUint', prebuiltHashedKeys.positionFeeFactorForPositiveImpact);
    addCall('positionFeeFactorForNegativeImpact', 'getUint', prebuiltHashedKeys.positionFeeFactorForNegativeImpact);
    addCall('positionImpactFactorPositive', 'getUint', prebuiltHashedKeys.positionImpactFactorPositive);
    addCall('positionImpactFactorNegative', 'getUint', prebuiltHashedKeys.positionImpactFactorNegative);
    addCall('maxPositionImpactFactorPositive', 'getUint', prebuiltHashedKeys.maxPositionImpactFactorPositive);
    addCall('maxPositionImpactFactorNegative', 'getUint', prebuiltHashedKeys.maxPositionImpactFactorNegative);
    addCall('maxPositionImpactFactorForLiquidations', 'getUint', prebuiltHashedKeys.maxPositionImpactFactorForLiquidations);
    addCall('maxLendableImpactFactor', 'getUint', prebuiltHashedKeys.maxLendableImpactFactor);
    addCall('maxLendableImpactFactorForWithdrawals', 'getUint', prebuiltHashedKeys.maxLendableImpactFactorForWithdrawals);
    addCall('maxLendableImpactUsd', 'getUint', prebuiltHashedKeys.maxLendableImpactUsd);
    addCall('lentPositionImpactPoolAmount', 'getUint', prebuiltHashedKeys.lentPositionImpactPoolAmount);
    addCall('minCollateralFactor', 'getUint', prebuiltHashedKeys.minCollateralFactor);
    addCall('minCollateralFactorForLiquidation', 'getUint', prebuiltHashedKeys.minCollateralFactorForLiquidation);
    addCall('minCollateralFactorForOpenInterestLong', 'getUint', prebuiltHashedKeys.minCollateralFactorForOpenInterestLong);
    addCall('minCollateralFactorForOpenInterestShort', 'getUint', prebuiltHashedKeys.minCollateralFactorForOpenInterestShort);
    addCall('positionImpactExponentFactor', 'getUint', prebuiltHashedKeys.positionImpactExponentFactor);
    addCall('swapFeeFactorForBalanceWasImproved', 'getUint', prebuiltHashedKeys.swapFeeFactorForBalanceWasImproved);
    addCall('swapFeeFactorForBalanceWasNotImproved', 'getUint', prebuiltHashedKeys.swapFeeFactorForBalanceWasNotImproved);
    addCall('swapFeeFactorForPositiveImpact', 'getUint', prebuiltHashedKeys.swapFeeFactorForPositiveImpact);
    addCall('swapFeeFactorForNegativeImpact', 'getUint', prebuiltHashedKeys.swapFeeFactorForNegativeImpact);
    addCall('atomicSwapFeeFactor', 'getUint', prebuiltHashedKeys.atomicSwapFeeFactor);
    addCall('swapImpactFactorPositive', 'getUint', prebuiltHashedKeys.swapImpactFactorPositive);
    addCall('swapImpactFactorNegative', 'getUint', prebuiltHashedKeys.swapImpactFactorNegative);
    addCall('swapImpactExponentFactor', 'getUint', prebuiltHashedKeys.swapImpactExponentFactor);
    addCall('virtualMarketId', 'getBytes32', prebuiltHashedKeys.virtualMarketId);
    addCall('virtualShortTokenId', 'getBytes32', prebuiltHashedKeys.virtualShortTokenId);
    addCall('virtualLongTokenId', 'getBytes32', prebuiltHashedKeys.virtualLongTokenId);

    request[`${marketAddress}-dataStore`] = {
      contractAddress: dataStoreAddress,
      abiId: "DataStore",
      calls,
    } as any;
  }

  return request;
}
