import { isSwapOperationPending } from "./";
import { getMultipleStatus } from "./getStatus";
import type { TokenAccount, Account, SwapOperation, Operation } from "@ledgerhq/types-live";
import type { SwapStatus, SwapStatusRequest, UpdateAccountSwapStatus } from "./types";
import { log } from "@ledgerhq/logs";

const PROVIDERS_REQUIRING_OPERATION_ID = ["thorswap", "lifi", "nearintents"];

const maybeGetUpdatedSwapHistory = async (
  swapHistory: SwapOperation[] | null | undefined,
  operations: Operation[] | null | undefined,
): Promise<SwapOperation[] | null | undefined> => {
  const pendingSwapIds: SwapStatusRequest[] = [];
  const atomicSwapIds: SwapStatus[] = [];
  let accountNeedsUpdating = false;
  let consolidatedSwapHistory: SwapOperation[] = [];

  if (swapHistory) {
    for (const swap of swapHistory) {
      const { provider, swapId, status, operationId } = swap;
      const updatedSwap: SwapOperation = { ...swap };

      if (isSwapOperationPending(status)) {
        // if swapId is in operationId, then we can get the status from the operation
        // it means DEX swap like Uniswap
        if (operationId && swapId && operationId.includes(swapId)) {
          const operation = operations?.find(o => o.id.includes(operationId));
          if (operation) {
            let newStatus;
            if (operation.blockHeight) {
              newStatus = operation.hasFailed ? "refunded" : "finished";
            } else {
              newStatus = "pending";
            }
            if (newStatus !== swap.status) {
              accountNeedsUpdating = true;
              updatedSwap.status = newStatus;
              atomicSwapIds.push({ provider, swapId, status: newStatus });
            }
          }
        } else {
          // Collect all others swaps that need status update via getMultipleStatus
          const requiresOperationId = PROVIDERS_REQUIRING_OPERATION_ID.includes(provider);
          const relatedTransactionId = requiresOperationId
            ? operations?.find(op => op.id.includes(operationId))?.hash
            : undefined;
          pendingSwapIds.push({
            provider,
            swapId,
            transactionId: relatedTransactionId,
            ...(requiresOperationId && { operationId }),
          });
        }
      }
    }
    if (pendingSwapIds.length || atomicSwapIds.length) {
      const uniquePendingSwapIdsMap = new Map<string, SwapStatusRequest>();
      for (const item of pendingSwapIds) {
        if (item.provider === "uniswap") {
          continue;
        }
        const existingItem = uniquePendingSwapIdsMap.get(item.swapId);
        if (!existingItem) {
          uniquePendingSwapIdsMap.set(item.swapId, item);
        } else if (item.transactionId && !existingItem.transactionId) {
          uniquePendingSwapIdsMap.set(item.swapId, item);
        }
      }
      const uniquePendingSwapIds = Array.from(uniquePendingSwapIdsMap.values());

      if (uniquePendingSwapIds.length !== pendingSwapIds.length) {
        log(
          "error",
          "swap: duplicate ids inside app.json, number",
          pendingSwapIds.length - uniquePendingSwapIds.length,
        );
      }

      const newStatusList = pendingSwapIds.length
        ? await getMultipleStatus(uniquePendingSwapIds)
        : [];
      newStatusList.push(...atomicSwapIds);
      consolidatedSwapHistory = swapHistory.map<SwapOperation>((swap: SwapOperation) => {
        const newStatus = newStatusList.find(s => s.swapId === swap.swapId);

        if (newStatus && newStatus.status !== swap.status) {
          accountNeedsUpdating = true;
          return { ...swap, status: newStatus.status };
        }

        return swap;
      });
    }

    if (accountNeedsUpdating) {
      return consolidatedSwapHistory;
    }
  }
};

const updateAccountSwapStatus: UpdateAccountSwapStatus = async (account: Account) => {
  const swapHistoryUpdated = await maybeGetUpdatedSwapHistory(
    account.swapHistory,
    account.operations,
  );
  let subAccountSwapHistoryUpdated = false;
  let subAccounts: TokenAccount[] = [];

  if (account.type === "Account" && account.subAccounts?.length) {
    subAccounts = await Promise.all(
      account.subAccounts.map(async (subAccount: TokenAccount): Promise<TokenAccount> => {
        const updatedSwapHistory = await maybeGetUpdatedSwapHistory(
          subAccount.swapHistory,
          subAccount.operations,
        );
        //As soon as we get one update, we will need to update the parent account
        if (updatedSwapHistory) subAccountSwapHistoryUpdated = true;
        return {
          ...subAccount,
          swapHistory: updatedSwapHistory || subAccount.swapHistory,
        };
      }),
    );
  }

  if (swapHistoryUpdated || subAccountSwapHistoryUpdated) {
    return {
      ...account,
      swapHistory: swapHistoryUpdated || account.swapHistory,
      subAccounts,
    };
  }
};

export default updateAccountSwapStatus;
