import { ERG_TOKEN_ID } from "@/constants/ergo";
import { ErgoBox } from "@/types/connector";
import { explorerBoxMapper } from "@/types/explorer";
import { difference, find, isEmpty, sortBy, unionBy } from "lodash";
import { addressesDbService } from "../database/addressesDbService";
import { assestsDbService } from "../database/assetsDbService";
import { utxosDbService } from "../database/utxosDbService";
import { explorerService } from "../explorer/explorerService";

export async function fetchBoxes(
  walletId: number,
  options: { tokenId?: string; useAllAddressesAsFallback: boolean; includeUnconfirmed: boolean } = {
    tokenId: ERG_TOKEN_ID,
    useAllAddressesAsFallback: true,
    includeUnconfirmed: true
  }
): Promise<ErgoBox[]> {
  const addresses = await assestsDbService.getAddressesByTokenId(
    walletId,
    options.tokenId ?? ERG_TOKEN_ID
  );
  const pendingBoxes = options.includeUnconfirmed
    ? await utxosDbService.getByWalletId(walletId)
    : [];

  let boxes = await fetchBoxesFromExplorer(addresses);

  if (
    options.useAllAddressesAsFallback &&
    isEmpty(boxes) &&
    !find(pendingBoxes, (b) => !b.locked && b.content)
  ) {
    boxes = await fetchBoxesFromExplorer(difference(await getAllAddresses(walletId), addresses));
  }
  if (!isEmpty(pendingBoxes)) {
    const lockedIds = pendingBoxes.filter((x) => x.locked).map((x) => x.id);
    const unconfirmed = pendingBoxes.filter((b) => !b.locked && b.content).map((b) => b.content!);

    if (!isEmpty(lockedIds)) {
      boxes = boxes.filter((b) => !lockedIds.includes(b.boxId));
    }
    if (!isEmpty(unconfirmed)) {
      boxes = unionBy(boxes, unconfirmed, (b) => b.boxId);
    }
  }

  return sortBy(boxes, (x) => x.creationHeight).reverse();
}

async function fetchBoxesFromExplorer(addresses: string[]): Promise<ErgoBox[]> {
  if (isEmpty(addresses)) {
    return [];
  }

  const boxes = await explorerService.getUnspentBoxes(addresses);
  return boxes
    .map((a) => a.data)
    .flat()
    .map(explorerBoxMapper({ asConfirmed: true }));
}

async function getAllAddresses(walletId: number): Promise<string[]> {
  const addresses = await addressesDbService.getByWalletId(walletId);
  return addresses.map((a) => a.script);
}