import { andThen, cond, filter, map, pick, pipeWith, reduceBy } from "ramda";
import deepMerge from "deepmerge";
import { getBlockHeight, updateBlockHeight } from "~/off-chain-storage/chainMetadata";
import { fetchRequestsByChainId, removeRequest, saveRequests, updateRequest } from "~/off-chain-storage/requests";
import { Status } from "~/on-chain-api/foreign-chain/entities";
import * as P from "~/shared/promise";

const asyncPipe = pipeWith((f, res) => andThen(f, P.resolve(res)));

const mergeOnChainOntoOffChain = ([offChainArbitration, onChainArbitration]) =>
  deepMerge(offChainArbitration, onChainArbitration);

export default async function checkRequestedArbitrations({ foreignChainApi }) {
  const chainId = await foreignChainApi.getChainId();

  await checkUntrackedArbitrationRequests();
  await processArbitrationRequests();

  async function checkUntrackedArbitrationRequests() {
    const [fromBlock, toBlock] = await P.all([
      getBlockHeight("ACCEPTED_ARBITRATION_REQUESTS"),
      foreignChainApi.getBlockNumber(),
    ]);

    const untrackedArbitrationRequests = filter(
      ({ status }) => status !== status.None,
      await foreignChainApi.getRequestedArbitrations({ fromBlock, toBlock })
    );

    await saveRequests(untrackedArbitrationRequests);

    const blockHeight = toBlock + 1;
    await updateBlockHeight({ key: "ACCEPTED_ARBITRATION_REQUESTS", blockHeight });
    console.info({ blockHeight }, "Set ACCEPTED_ARBITRATION_REQUESTS block height");

    const stats = {
      data: map(pick(["questionId", "requester", "chainId", "status"]), untrackedArbitrationRequests),
      fromBlock,
      toBlock,
    };

    console.info(stats, "Found new notified arbitration requests");

    return stats;
  }

  async function processArbitrationRequests() {
    const requestRemovedOrRuled = ([_, onChainArbitration]) =>
      [Status.None, Status.Ruled].includes(onChainArbitration.status);
    const removeOffChainRequest = asyncPipe([
      mergeOnChainOntoOffChain,
      removeRequest,
      (arbitration) => ({
        action: "ARBITRATION_REQUEST_REMOVED",
        payload: arbitration,
      }),
    ]);

    const disputeCreationFailed = ([_, onChainArbitration]) => onChainArbitration.status === Status.Failed;
    const handleFailedDisputeCreation = asyncPipe([
      mergeOnChainOntoOffChain,
      foreignChainApi.handleFailedDisputeCreation,
      (arbitration) => ({
        action: "FAILED_DISPUTE_CREATION_HANDLED",
        payload: arbitration,
      }),
    ]);

    const requestStatusChanged = ([offChainArbitration, onChainArbitration]) =>
      offChainArbitration.status != onChainArbitration.status;
    const updateOffChainRequest = asyncPipe([
      mergeOnChainOntoOffChain,
      updateRequest,
      (arbitration) => ({
        action: "STATUS_CHANGED",
        payload: arbitration,
      }),
    ]);

    const noop = asyncPipe([
      mergeOnChainOntoOffChain,
      (arbtration) => ({
        action: "NO_OP",
        payload: arbtration,
      }),
    ]);

    const pipeline = asyncPipe([
      fetchOnChainCounterpart,
      cond([
        [requestRemovedOrRuled, removeOffChainRequest],
        [disputeCreationFailed, handleFailedDisputeCreation],
        [requestStatusChanged, updateOffChainRequest],
        [() => true, noop],
      ]),
    ]);

    const requestedArbitrations = await fetchRequestsByChainId({ chainId });

    console.info(
      { data: map(pick(["questionId", "requester"]), requestedArbitrations) },
      "Fetched requested arbitrations"
    );

    const results = await P.allSettled(map(pipeline, requestedArbitrations));

    const groupQuestionsOrErrorMessage = (acc, r) => {
      if (r.status === "rejected") {
        return [...acc, r.reason?.message];
      }

      const questionId = r.value?.payload?.questionId ?? "<not set>";
      const requester = r.value?.payload?.requester ?? "<not set>";
      return [...acc, { questionId, requester }];
    };
    const toTag = (r) => (r.status === "rejected" ? "FAILURE" : r.value?.action);
    const stats = reduceBy(groupQuestionsOrErrorMessage, [], toTag, results);

    console.info(stats, "Processed requested arbitrations");

    return stats;
  }

  async function fetchOnChainCounterpart(offChainArbitration) {
    const { questionId, requester } = offChainArbitration;

    const onChainArbitration = await foreignChainApi.getArbitrationRequest({ questionId, requester });

    return [offChainArbitration, onChainArbitration];
  }
}