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

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

const mergeOnChainOntoOffChain = ([offChainRequest, onChainRequest]) => deepMerge(offChainRequest, onChainRequest);

export default async function checkArbitratorAnswers({ homeChainApi }) {
  const chainId = await homeChainApi.getChainId();

  await checkUntrackedArbitratorAnswers();
  await checkCurrentArbitratorAnswers();

  async function checkUntrackedArbitratorAnswers() {
    const [fromBlock, toBlock] = await P.all([getBlockHeight("RULED_REQUESTS"), homeChainApi.getBlockNumber()]);

    const untrackedNotifiedRequests = filter(
      ({ status }) => [Status.AwaitingRuling, Status.Ruled].includes(status),
      await homeChainApi.getNotifiedRequests({ fromBlock, toBlock })
    );

    await saveRequests(untrackedNotifiedRequests);

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

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

    console.info(stats, "Found new requests with arbitrator answers");

    return stats;
  }

  async function checkCurrentArbitratorAnswers() {
    const requestFinishedOrCanceled = ([_, onChainRequest]) =>
      [Status.None, Status.Finished].includes(onChainRequest.status);
    const removeOffChainRequest = asyncPipe([
      mergeOnChainOntoOffChain,
      removeRequest,
      (request) => ({
        action: "OFF_CHAIN_REQUEST_REMOVED",
        payload: request,
      }),
    ]);

    const requestRuled = ([_, onChainArbitration]) => onChainArbitration.status === Status.Ruled;
    const reportArbitrationAnswer = asyncPipe([
      mergeOnChainOntoOffChain,
      homeChainApi.reportArbitrationAnswer,
      (request) => ({
        action: "ARBITRATION_ANSWER_REPORTED",
        payload: request,
      }),
    ]);

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

    const requestsWithRuling = await fetchRequestsByChainId({ status: Status.Ruled, chainId });

    console.info(
      { data: map(pick(["questionId", "requester"]), requestsWithRuling) },
      "Fetched requests which received the arbitration ruling"
    );

    const pipeline = asyncPipe([
      fetchOnChainCounterpart,
      cond([
        [requestFinishedOrCanceled, removeOffChainRequest],
        [requestRuled, reportArbitrationAnswer],
        [() => true, noop],
      ]),
    ]);

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

    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 requests with ruling");
  }

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

    const onChainRequest = await homeChainApi.getRequest({ questionId, requester });

    return [offChainRequest, onChainRequest];
  }
}