import models from "$models/index.js";
import { tadbirServices } from "$services/index.js";

import {
  buildErrorSnapshot,
  buildIssuancePayload,
  buildRedemptionPayload,
  ensureLoadedRequest,
  EXTERNAL_SYSTEM_TADBIR,
  mapExternalStateToLocalStatus,
  persistRequestUpdate,
} from "./request.helper.internal.js";

const shouldKeepRequestReviewingOnSubmissionFailure = (
  externalLastResponse,
) => {
  const responseData = externalLastResponse?.data;
  const firstErrorCode = responseData?.[0]?.code;

  if (firstErrorCode === "40500") {
    return false;
  }

  return (
    Array.isArray(responseData) &&
    responseData.length === 1 &&
    firstErrorCode === "40075"
  );
};

const markSubmissionFailure = async (requestOrId, error) => {
  const request = await ensureLoadedRequest(requestOrId);
  if (!request) {
    throw error;
  }

  const externalLastResponse = buildErrorSnapshot(error);

  await persistRequestUpdate(
    request,
    {
      externalSource: EXTERNAL_SYSTEM_TADBIR,
      externalLastResponse,
      status: shouldKeepRequestReviewingOnSubmissionFailure(
        externalLastResponse,
      )
        ? "reviewing"
        : "failed",
    },
    {
      actorType: "system",
      source: "tadbir_submit",
      eventType: "tadbir_submission_failed",
    },
  );

  return request;
};

export const syncTadbirRequest = async (requestOrId) => {
  const request = await ensureLoadedRequest(requestOrId);

  try {
    const getRequestPayload = {
      fundSeoRegisterNo: request.fundSeoRegisterNo,
      requestId: request.externalId,
    };

    const externalRequest =
      await tadbirServices.getRequestById(getRequestPayload);
    const externalStatus = externalRequest?.stateKey;
    const mappedStatus = mapExternalStateToLocalStatus(externalStatus);

    return persistRequestUpdate(
      request,
      {
        externalStatus,
        externalLastResponse: externalRequest,
        externalLastSyncedAt: new Date(),
        status: mappedStatus,
      },
      {
        actorType: "system",
        source: "tadbir_sync",
        eventType: "tadbir_status_synced",
      },
    );
  } catch (error) {
    const externalLastResponse = buildErrorSnapshot(error);
    await persistRequestUpdate(
      request,
      {
        externalLastResponse,
        status: shouldKeepRequestReviewingOnSubmissionFailure(
          externalLastResponse,
        )
          ? "reviewing"
          : "failed",
      },
      {
        actorType: "system",
        source: "tadbir_sync",
        eventType: "updated",
      },
    );
    return request;
  }
};

export const submitTadbirIssuanceForRequest = async (requestOrId) => {
  const request = await ensureLoadedRequest(requestOrId);
  if (!request) {
    return null;
  }

  try {
    const payload = buildIssuancePayload(request);
    const response = await tadbirServices.createIssuanceRequest({
      fundSeoRegisterNo: request.fundSeoRegisterNo,
      payload,
    });

    await persistRequestUpdate(
      request,
      {
        externalSource: EXTERNAL_SYSTEM_TADBIR,
        externalId: response?.requestId || "UNKNOWN_REQUEST_ID",
        externalLastResponse: response,
      },
      {
        actorType: "system",
        source: "tadbir_submit",
        eventType: "tadbir_submission_succeeded",
      },
    );
  } catch (error) {
    return markSubmissionFailure(request, error);
  }
};

export const submitTadbirRedemptionForRequest = async (requestOrId) => {
  const request = await ensureLoadedRequest(requestOrId);
  if (!request) {
    return null;
  }

  try {
    const payload = buildRedemptionPayload(request);
    const response = await tadbirServices.createRedemptionRequest({
      fundSeoRegisterNo: request.fundSeoRegisterNo,
      payload,
    });

    await persistRequestUpdate(
      request,
      {
        externalSource: EXTERNAL_SYSTEM_TADBIR,
        externalId: response?.requestId || "UNKNOWN_REQUEST_ID",
        externalLastResponse: response,
      },
      {
        actorType: "system",
        source: "tadbir_submit",
        eventType: "tadbir_submission_succeeded",
      },
    );
  } catch (error) {
    return markSubmissionFailure(request, error);
  }
};

export const reconcilePendingTadbirRequests = async ({ limit = 25 } = {}) => {
  try {
    const requests = await models.request
      .find({
        externalSource: EXTERNAL_SYSTEM_TADBIR,
        externalId: { $exists: true, $ne: "" },
        status: "reviewing",
      })
      .sort({
        externalLastSyncedAt: 1,
        updatedAt: 1,
      })
      .limit(Math.max(1, Number(limit) || 25));

    for (const request of requests) {
      await syncTadbirRequest(request);
    }

    return requests.length;
  } catch (error) {
    console.log("ERROR ON reconcilePendingTadbirRequests", error);
  }
};

export const reconcileSingleTadbirRequest = async (requestOrId) => {
  const request = await ensureLoadedRequest(requestOrId);
  if (!request) {
    const error = new Error("Request not found.");
    error.status = 404;
    throw error;
  }

  if (
    request.externalSource !== EXTERNAL_SYSTEM_TADBIR ||
    !String(request.externalId || "").trim()
  ) {
    const error = new Error(
      "This request is not ready for Tadbir reconciliation.",
    );
    error.status = 422;
    throw error;
  }

  return syncTadbirRequest(request);
};

export const retryPendingTadbirIssuanceRequests = async ({
  limit = 25,
} = {}) => {
  try {
    const requests = await models.request
      .find({
        externalSource: EXTERNAL_SYSTEM_TADBIR,
        externalId: { $exists: true, $eq: "" },
        status: "reviewing",
      })
      .sort({
        updatedAt: 1,
        createdAt: 1,
      })
      .select("_id")
      .limit(Math.max(1, Number(limit) || 25));

    for (const requestItem of requests) {
      try {
        const request = await ensureLoadedRequest(requestItem?._id);
        const payload = buildIssuancePayload(request);
        const response = await tadbirServices.createIssuanceRequest({
          fundSeoRegisterNo: request.fundSeoRegisterNo,
          payload,
        });

        await persistRequestUpdate(
          request,
          {
            externalSource: EXTERNAL_SYSTEM_TADBIR,
            externalId: response?.requestId || "UNKNOWN_REQUEST_ID",
            externalLastResponse: response,
          },
          {
            actorType: "system",
            source: "tadbir_retry",
            eventType: "tadbir_submission_succeeded",
          },
        );
      } catch (error) {
        await markSubmissionFailure(requestItem, error);
      }
    }

    return requests.length;
  } catch (error) {
    console.log("ERROR ON retryPendingTadbirIssuanceRequests", error);
  }
};

export const retrySingleTadbirIssuanceRequest = async (requestOrId) => {
  const request = await ensureLoadedRequest(requestOrId);
  if (!request) {
    const error = new Error("Request not found.");
    error.status = 404;
    throw error;
  }

  if (request.operationType !== "issuance") {
    const error = new Error("Only issuance requests can be retried.");
    error.status = 422;
    throw error;
  }

  if (
    request.externalSource !== EXTERNAL_SYSTEM_TADBIR ||
    String(request.externalId || "").trim() !== "" ||
    request.status !== "reviewing"
  ) {
    const error = new Error(
      "This request is not eligible for Tadbir issuance retry.",
    );
    error.status = 422;
    throw error;
  }

  try {
    const payload = buildIssuancePayload(request);
    const response = await tadbirServices.createIssuanceRequest({
      fundSeoRegisterNo: request.fundSeoRegisterNo,
      payload,
    });

    await persistRequestUpdate(
      request,
      {
        externalSource: EXTERNAL_SYSTEM_TADBIR,
        externalId: response?.requestId || "UNKNOWN_REQUEST_ID",
        externalLastResponse: response,
      },
      {
        actorType: "system",
        source: "tadbir_retry",
        eventType: "tadbir_submission_succeeded",
      },
    );

    return request;
  } catch (error) {
    return markSubmissionFailure(request, error);
  }
};
