import crypto from "node:crypto";

import { paymentConfig, servicesConfig } from "$config/index.js";
import {
  isCardExpired,
  normalizeCardNumber,
  syncPaymentBatchStatus,
} from "$helpers/payment/paymentCard.helper.js";
import models from "$models/index.js";
import { buildAdditionalData } from "$services/payment/manaIdentity.service.js";
import {
  confirmPayment,
  getMerchantByFundKey,
  redirectUrl,
  salePayment,
} from "$services/payment/parsianGateway.service.js";
import {
  createRequestWithHistory,
  deleteRequestWithHistory,
  submitTadbirIssuanceForRequest,
  updateRequestWithHistory,
} from "$helpers/request/request.helper.js";
import { getUserIdentifier } from "$helpers/identity/identity.helper.js";
import { sepahBrokerServices, tadbirServices } from "$services/index.js";
import {
  assertSepahBrokerFundRef,
  isSepahBrokerFund,
  SEPAH_BROKER_EXTERNAL_SOURCE,
} from "$services/sepah/sepah-broker.helper.js";

export const noStore = (res) => res.set("Cache-Control", "no-store");
const REQUEST_STATUS_WAIT_FOR_PAYMENT = "wait_for_payment";
const REQUEST_STATUS_REVIEWING = "reviewing";
const REQUEST_STATUS_APPROVED = "approved";
const REQUEST_STATUS_FAILED = "failed";
const MAX_ISSUANCE_AMOUNT_RIAL = 4000000000;

// TEMP FOR TESTING
import env from "$env/index.js";
const isGatewayBypassEnabled = env.TEST_SKIP_PARSIAN_GATEWAY === "true";

const normalizeRequestMeta = (body = {}) => {
  const meta =
    body.meta && typeof body.meta === "object" && !Array.isArray(body.meta)
      ? { ...body.meta }
      : {};

  const knownFields = {
    BankBillCode: body.BankBillCode ?? body.bankBillCode,
    FundBankAccountNumber:
      body.FundBankAccountNumber ??
      body.fundBankAccountNumber ??
      body.destinationAccountNumber,
    SettlementBranchId:
      body.SettlementBranchId ?? body.settlementBranchId ?? undefined,
    SettlementBranchCode:
      body.SettlementBranchCode ?? body.settlementBranchCode,
    FormSerial: body.FormSerial ?? body.formSerial,
    IsInstantPayment:
      body.IsInstantPayment ?? body.isInstantPayment ?? undefined,
    IsGuarantee: body.IsGuarantee ?? body.isGuarantee ?? undefined,
    PaymentMethod: body.PaymentMethod ?? body.paymentMethodCode ?? undefined,
  };

  for (const [key, value] of Object.entries(knownFields)) {
    if (value !== undefined && value !== null && value !== "") {
      meta[key] = value;
    }
  }

  return Object.keys(meta).length > 0 ? meta : undefined;
};

const parsePositiveNumber = (value) => {
  const parsedValue = Number(value);
  return Number.isFinite(parsedValue) ? parsedValue : 0;
};

const normalizeNationalIdentifier = (req, user) =>
  String(req?.identifier || getUserIdentifier(user) || "").trim();

const normalizeMobileNumber = (value) =>
  String(value || "")
    .replace(/[^\d]/g, "")
    .trim();

const safeParseJson = (value) => {
  if (typeof value !== "string") {
    return value;
  }

  try {
    return JSON.parse(value);
  } catch {
    return value;
  }
};

const resolveSepahGatewayPayload = (payload) => {
  const normalizedPayload = safeParseJson(payload);

  if (Array.isArray(normalizedPayload)) {
    return resolveSepahGatewayPayload(normalizedPayload[0]);
  }

  if (!normalizedPayload || typeof normalizedPayload !== "object") {
    return {};
  }

  if (Array.isArray(normalizedPayload.data)) {
    return resolveSepahGatewayPayload(normalizedPayload.data[0]);
  }

  if (Array.isArray(normalizedPayload.result)) {
    return resolveSepahGatewayPayload(normalizedPayload.result[0]);
  }

  if (typeof normalizedPayload.data === "string") {
    const parsedData = safeParseJson(normalizedPayload.data);
    if (parsedData !== normalizedPayload.data) {
      return resolveSepahGatewayPayload(parsedData);
    }
  }

  if (typeof normalizedPayload.result === "string") {
    const parsedResult = safeParseJson(normalizedPayload.result);
    if (parsedResult !== normalizedPayload.result) {
      return resolveSepahGatewayPayload(parsedResult);
    }
  }

  return normalizedPayload;
};

const isSepahEmptyArrayPayload = (payload) => {
  const normalizedPayload = safeParseJson(payload);
  return Array.isArray(normalizedPayload) && normalizedPayload.length === 0;
};

const currentIsoDate = () => new Date().toISOString().slice(0, 10);
const currentIsoDateTimeAtStartOfDay = () => `${currentIsoDate()}T00:00:00`;

const extractSepahGatewayToken = (payload = {}) => {
  const normalizedPayload = resolveSepahGatewayPayload(payload);
  const candidates = [
    normalizedPayload?.token,
    normalizedPayload?.Token,
    normalizedPayload?.data?.token,
    normalizedPayload?.data?.Token,
    normalizedPayload?.result?.token,
    normalizedPayload?.result?.Token,
  ];

  for (const candidate of candidates) {
    if (candidate !== undefined && candidate !== null && candidate !== "") {
      return String(candidate).trim();
    }
  }

  const redirectUrl = extractSepahRedirectUrl(normalizedPayload);

  if (redirectUrl) {
    try {
      return new URL(redirectUrl).searchParams.get("token") || "";
    } catch {
      return "";
    }
  }

  return "";
};

const extractSepahTraceCode = (payload = {}) => {
  const normalizedPayload = resolveSepahGatewayPayload(payload);
  const candidates = [
    normalizedPayload?.TrackingCode,
    normalizedPayload?.TraceCode,
    normalizedPayload?.trackingCode,
    normalizedPayload?.traceCode,
    normalizedPayload?.data?.TrackingCode,
    normalizedPayload?.data?.TraceCode,
    normalizedPayload?.data?.trackingCode,
    normalizedPayload?.data?.traceCode,
    normalizedPayload?.result?.TrackingCode,
    normalizedPayload?.result?.TraceCode,
    normalizedPayload?.result?.trackingCode,
    normalizedPayload?.result?.traceCode,
  ];

  for (const candidate of candidates) {
    if (candidate !== undefined && candidate !== null && candidate !== "") {
      return String(candidate).trim();
    }
  }

  return "";
};

const extractSepahGatewayStatus = (payload = {}) => {
  const normalizedPayload = resolveSepahGatewayPayload(payload);
  const candidates = [
    normalizedPayload?.status,
    normalizedPayload?.Status,
    normalizedPayload?.resultCode,
    normalizedPayload?.ResultCode,
    normalizedPayload?.code,
    normalizedPayload?.Code,
    normalizedPayload?.data?.status,
    normalizedPayload?.data?.Status,
    normalizedPayload?.data?.resultCode,
    normalizedPayload?.data?.ResultCode,
    normalizedPayload?.result?.status,
    normalizedPayload?.result?.Status,
    normalizedPayload?.result?.resultCode,
    normalizedPayload?.result?.ResultCode,
  ];

  for (const candidate of candidates) {
    if (candidate !== undefined && candidate !== null && candidate !== "") {
      return candidate;
    }
  }

  return null;
};

const isExplicitSepahFailure = (payload = {}) => {
  const normalizedPayload = resolveSepahGatewayPayload(payload);
  const statusCandidates = [
    normalizedPayload?.success,
    normalizedPayload?.isSuccess,
    normalizedPayload?.Success,
    normalizedPayload?.IsSuccess,
    normalizedPayload?.data?.success,
    normalizedPayload?.data?.isSuccess,
    normalizedPayload?.result?.success,
    normalizedPayload?.result?.isSuccess,
    extractSepahGatewayStatus(normalizedPayload),
  ];

  return statusCandidates.some((candidate) => {
    if (candidate === false) {
      return true;
    }

    const normalizedCandidate = String(candidate || "").trim().toLowerCase();
    return [
      "false",
      "failed",
      "failure",
      "error",
      "cancelled",
      "canceled",
      "rejected",
      "-1",
    ].includes(normalizedCandidate);
  });
};

const extractSepahRedirectUrl = (payload = {}) => {
  const normalizedPayload = resolveSepahGatewayPayload(payload);
  const candidates = [
    normalizedPayload?.URL,
    normalizedPayload?.redirectUrl,
    normalizedPayload?.paymentUrl,
    normalizedPayload?.paymentLink,
    normalizedPayload?.link,
    normalizedPayload?.url,
    normalizedPayload?.data?.URL,
    normalizedPayload?.data?.redirectUrl,
    normalizedPayload?.data?.paymentUrl,
    normalizedPayload?.data?.paymentLink,
    normalizedPayload?.data?.link,
    normalizedPayload?.data?.url,
    normalizedPayload?.result?.URL,
    normalizedPayload?.result?.redirectUrl,
    normalizedPayload?.result?.paymentUrl,
    normalizedPayload?.result?.paymentLink,
    normalizedPayload?.result?.link,
    normalizedPayload?.result?.url,
  ];

  for (const candidate of candidates) {
    if (candidate !== undefined && candidate !== null && candidate !== "") {
      return String(candidate).trim();
    }
  }

  return "";
};

const buildSepahGatewaySummary = ({
  request,
  orderId,
  token,
  gatewayStatus,
  rrn = null,
  rawResponse,
}) => ({
  ...(request?.paymentGateway || {}),
  gateway: SEPAH_BROKER_EXTERNAL_SOURCE,
  orderId: orderId || request?.paymentGateway?.orderId || request?.trackId || "",
  token: token || request?.paymentGateway?.token || "",
  rrn: rrn || request?.paymentGateway?.rrn || null,
  gatewayStatus:
    gatewayStatus ?? request?.paymentGateway?.gatewayStatus ?? null,
  raw: rawResponse || request?.paymentGateway?.raw,
});

const resolveFundIssuanceSettings = async ({
  fundId,
  fundSeoRegisterNo,
  fundRegisterCode,
}) => {
  const fundsResponse = await tadbirServices.getFunds();
  const funds = Array.isArray(fundsResponse?.items) ? fundsResponse.items : [];
  const normalizedCandidates = [
    String(fundId || "").trim(),
    String(fundSeoRegisterNo || "").trim(),
    String(fundRegisterCode || "").trim(),
  ].filter(Boolean);

  return (
    funds.find((fund) =>
      normalizedCandidates.some(
        (candidate) =>
          candidate === String(fund?.fundRegisterCode || "").trim() ||
          candidate === String(fund?.nav?.seoRegisterNo || "").trim(),
      ),
    ) || null
  );
};

const validateIssuanceRequestPayload = async ({
  fundId,
  fundSeoRegisterNo,
  fundRegisterCode,
  amountRial,
  units,
}) => {
  if (!Number.isSafeInteger(amountRial) || amountRial < 1000) {
    const error = new Error("مبلغ نهایی صدور باید معتبر باشد.");
    error.status = 422;
    throw error;
  }

  if (amountRial > MAX_ISSUANCE_AMOUNT_RIAL) {
    const error = new Error(
      `حداکثر مبلغ مجاز برای صدور ${MAX_ISSUANCE_AMOUNT_RIAL.toLocaleString("fa-IR")} ریال است.`,
    );
    error.status = 422;
    throw error;
  }

  const normalizedUnits = parsePositiveNumber(units);
  const fund = await resolveFundIssuanceSettings({
    fundId,
    fundSeoRegisterNo,
    fundRegisterCode,
  });

  if (!fund) {
    const error = new Error("اطلاعات صندوق برای ثبت درخواست صدور کامل نیست.");
    error.status = 422;
    throw error;
  }

  const minUnits = parsePositiveNumber(fund.minimumCapitalUnit);
  const maxUnits = parsePositiveNumber(fund.maximumCapitalUnit);
  const remainedUnits = parsePositiveNumber(fund.remainedUnit);

  if (!Number.isFinite(normalizedUnits)) {
    const error = new Error("تعداد واحد صدور باید معتبر باشد.");
    error.status = 422;
    throw error;
  }

  if (remainedUnits <= 0) {
    const error = new Error(
      "در حال حاضر واحد قابل صدور برای این صندوق باقی نمانده است.",
    );
    error.status = 422;
    throw error;
  }

  if (minUnits > 0 && normalizedUnits < minUnits) {
    const error = new Error(
      `حداقل تعداد واحد قابل صدور ${minUnits.toLocaleString("fa-IR")} واحد است.`,
    );
    error.status = 422;
    throw error;
  }

  if (maxUnits > 0 && normalizedUnits > maxUnits) {
    const error = new Error(
      `حداکثر تعداد واحد مجاز برای این صندوق ${maxUnits.toLocaleString("fa-IR")} واحد است.`,
    );
    error.status = 422;
    throw error;
  }

  if (normalizedUnits > remainedUnits) {
    const error = new Error(
      `در حال حاضر حداکثر ${remainedUnits.toLocaleString("fa-IR")} واحد برای صدور این صندوق باقی مانده است.`,
    );
    error.status = 422;
    throw error;
  }

  if (normalizedUnits <= 0) {
    const error = new Error("تعداد واحد صدور باید معتبر باشد.");
    error.status = 422;
    throw error;
  }
};

export const startPayment = async ({ body = {}, params = {}, user, req }) => {
  const paymentMethod =
    String(body.paymentMethod || body.method || "gateway").trim() === "card"
      ? "card"
      : "gateway";
  const fundId = String(params.fundId || body.fundId || "").trim();

  if (isSepahBrokerFund(body.fundRegisterCode || fundId)) {
    return startSepahPayment({ body, params, user, req, paymentMethod });
  }

  if (paymentMethod === "card") {
    return startCardPayment({ body, params, user, req });
  }

  let tx;
  let paymentRequest;

  try {
    if (body.confirm !== true && body.confirm !== "true") {
      const error = new Error("Confirmation is required.");
      error.status = 422;
      throw error;
    }

    const fundId = String(params.fundId || body.fundId || "").trim();
    const fundKey = normalizeFundKey(body.fundKey || body.fund?.key || fundId);
    if (!fundId) {
      const error = new Error("fundId is required.");
      error.status = 422;
      throw error;
    }

    const merchant = getMerchantByFundKey(fundKey);

    if (!fundKey || !merchant?.login) {
      const error = new Error(
        "Gateway merchant is not configured for this fund.",
      );
      error.status = 422;
      throw error;
    }

    // TEMP FOR TESTING - SHOULD BE REMOVED OR REVISED
    const amountRial = Math.max(
      1000,
      Number(body.amountRial || body.payable || 0),
    );
    // const amountRial = 10000;
    const units = normalizeUnits(body.units);
    const unitPrice = Number(body.unitPrice || body.unit_price || 0);

    if (!Number.isSafeInteger(amountRial) || amountRial <= 0) {
      const error = new Error("Invalid amount.");
      error.status = 422;
      throw error;
    }

    if (!Number.isFinite(units) || units <= 0 || !Number.isFinite(unitPrice)) {
      const error = new Error("Invalid units or unit price.");
      error.status = 422;
      throw error;
    }

    await validateIssuanceRequestPayload({
      fundId,
      fundSeoRegisterNo: body.fundSeoRegisterNo,
      fundRegisterCode: body.fundRegisterCode || fundId,
      amountRial,
      units,
    });

    const orderId = createOrderId();
    const receiptToken = createReceiptToken();
    const additionalData = buildAdditionalData({
      personality: body.payerType === "corporate" ? "1" : "0",
      identifier: req.identifier,
      salt: orderId,
    });

    paymentRequest = await createGatewayRequest({
      user,
      body,
      orderId,
      amountRial,
      units,
      unitPrice,
    });

    // TEMP FOR TESTING
    if (isGatewayBypassEnabled) {
      tx = await models.transaction.create({
        request: paymentRequest._id,
        fundKey,
        amountRial,
        status: "paid",
        gateway: "parsian",
        orderId,
        receiptToken,
        rrn: orderId,
        gatewayRequest: {
          bypassed: true,
          additionalData,
        },
        gatewayResponse: {
          bypassed: true,
          message: "Parsian gateway bypassed for testing.",
        },
        confirmResponse: {
          bypassed: true,
          status: 0,
          rrn: orderId,
        },
        clientIp: req.ip,
        clientUa: req.get("user-agent"),
      });
      await linkRequestToTransaction(paymentRequest, tx._id);
      await updateRequestFromTransaction(tx, {
        request: paymentRequest,
        status: resolveRequestStatusFromTransaction(tx),
      });
      await submitTadbirIssuanceForRequest(paymentRequest._id);

      return {
        ok: true,
        requestId: String(paymentRequest._id),
        orderId,
        receiptToken,
        redirectUrl: buildPaymentResultUrl({
          requestId: paymentRequest._id,
        }),
      };
    }

    tx = await models.transaction.create({
      request: paymentRequest._id,
      fundKey,
      amountRial,
      status: "initiated",
      gateway: "parsian",
      orderId,
      receiptToken,
      terminalNo: merchant.terminalNo,
      clientIp: req.ip,
      clientUa: req.get("user-agent"),
    });
    await linkRequestToTransaction(paymentRequest, tx._id);
    await updateRequestFromTransaction(tx, { request: paymentRequest });

    const requestData = {
      LoginAccount: merchant.login,
      OrderId: orderId,
      Amount: amountRial,
      CallBackUrl: merchant.callbackUrl,
      AdditionalData: additionalData,
      Originator: orderId,
    };

    const sale = await salePayment(requestData);
    tx.gatewayRequest = requestData;
    tx.gatewayResponse = sale;
    tx.gatewayStatus = sale.status;
    if (sale.status === 0 && sale.token) {
      tx.status = "token_received";
      tx.token = sale.token;
    } else {
      tx.status = "failed";
    }
    await tx.save();
    await updateRequestFromTransaction(tx, {
      request: paymentRequest,
      status: resolveRequestStatusFromTransaction(tx),
    });

    if (sale.status !== 0 || !sale.token) {
      const error = new Error("Gateway token request failed.");
      error.status = 502;
      error.gatewayStatus = sale.status;
      throw error;
    }

    return {
      ok: true,
      requestId: String(paymentRequest._id),
      orderId,
      token: sale.token,
      receiptToken,
      redirectUrl: redirectUrl(sale.token),
    };
  } catch (error) {
    if (tx) {
      tx.status = "failed";
      tx.gatewayResponse = tx.gatewayResponse || {
        error: error.message || "sale_payment_failed",
      };
      await tx.save();
      await updateRequestFromTransaction(tx, {
        request: paymentRequest,
        status: "failed",
      });
    } else if (paymentRequest?._id) {
      await updateRequestWithHistory(
        paymentRequest._id,
        {
          status: "failed",
        },
        {
          actorType: "system",
          source: "payment_gateway",
          eventType: "payment_failed",
        },
      );
    }
    throw error;
  }
};

const createSepahRequest = ({
  user,
  body = {},
  orderId,
  amountRial,
  units,
  unitPrice,
  fundRef,
}) =>
  createRequestWithHistory(
    {
      investor: user._id,
      fundSeoRegisterNo: body.fundSeoRegisterNo || body.fundRegisterCode || body.fundId,
      fundName: body.fundName || body.fund?.name,
      trackId: orderId,
      operationType: "issuance",
      paymentMethod: "gateway",
      amountRial,
      units,
      unitPrice,
      status: REQUEST_STATUS_WAIT_FOR_PAYMENT,
      externalSource: SEPAH_BROKER_EXTERNAL_SOURCE,
      meta: {
        ...normalizeRequestMeta(body),
        sepahBroker: {
          fundRef,
          fundRegisterCode: body.fundRegisterCode || body.fundId || "",
          requestNumber: orderId,
        },
      },
    },
    {
      actorUser: user,
      source: "payment_gateway",
      eventType: "created",
    },
  );

const startSepahPayment = async ({
  body = {},
  params = {},
  user,
  req,
  paymentMethod,
}) => {
  let paymentRequest;

  try {
    if (paymentMethod !== "gateway") {
      const error = new Error(
        "برای این صندوق فقط پرداخت از طریق درگاه آنلاین فعال است.",
      );
      error.status = 422;
      throw error;
    }

    if (body.confirm !== true && body.confirm !== "true") {
      const error = new Error("Confirmation is required.");
      error.status = 422;
      throw error;
    }

    const fundId = String(params.fundId || body.fundId || "").trim();
    const amountRial = Math.max(
      1000,
      Number(body.amountRial || body.payable || 0),
    );
    const units = normalizeUnits(body.units);
    const unitPrice = Number(body.unitPrice || body.unit_price || 0);
    const fundRegisterCode = String(
      body.fundRegisterCode || fundId || "",
    ).trim();
    const fundRef = assertSepahBrokerFundRef(fundRegisterCode);
    const nationalId = normalizeNationalIdentifier(req, user);
    const mobileNumber = normalizeMobileNumber(user?.mobile);

    if (!fundId) {
      const error = new Error("fundId is required.");
      error.status = 422;
      throw error;
    }

    if (!nationalId) {
      const error = new Error("شناسه هویتی کاربر برای پرداخت سپه کامل نیست.");
      error.status = 422;
      throw error;
    }

    if (!mobileNumber) {
      const error = new Error("شماره موبایل کاربر برای پرداخت سپه کامل نیست.");
      error.status = 422;
      throw error;
    }

    if (!Number.isSafeInteger(amountRial) || amountRial <= 0) {
      const error = new Error("Invalid amount.");
      error.status = 422;
      throw error;
    }

    if (!Number.isFinite(units) || units <= 0 || !Number.isFinite(unitPrice)) {
      const error = new Error("Invalid units or unit price.");
      error.status = 422;
      throw error;
    }

    await validateIssuanceRequestPayload({
      fundId,
      fundSeoRegisterNo: body.fundSeoRegisterNo,
      fundRegisterCode,
      amountRial,
      units,
    });

    const orderId = createOrderId();

    paymentRequest = await createSepahRequest({
      user,
      body,
      orderId,
      amountRial,
      units,
      unitPrice,
      fundRef,
    });

    const createPaymentResponse = await sepahBrokerServices.createPaymentRequest({
      identifier: nationalId,
      amount: amountRial,
      description: body.fundName || body.fund?.name || `صدور صندوق ${fundId}`,
      requestNumber: orderId,
      mobileNumber,
      fundRef,
      callBackUrl: servicesConfig.sepahBrokerConfig.callbackUrl,
    });

    const token = extractSepahGatewayToken(createPaymentResponse);
    const redirectUrl = extractSepahRedirectUrl(createPaymentResponse);

    if (!token) {
      const error = new Error("توکن پرداخت کارگزاری سپه دریافت نشد.");
      error.status = 502;
      throw error;
    }

    if (!redirectUrl) {
      const error = new Error(
        "آدرس هدایت به درگاه سپه در پاسخ PayRequest برنگشته است.",
      );
      error.status = 502;
      throw error;
    }

    await updateRequestWithHistory(
      paymentRequest,
      {
        paymentGateway: buildSepahGatewaySummary({
          request: paymentRequest,
          orderId,
          token,
          gatewayStatus: extractSepahGatewayStatus(createPaymentResponse),
          rawResponse: createPaymentResponse,
        }),
        externalLastResponse: createPaymentResponse,
      },
      {
        actorType: "system",
        source: "payment_gateway",
        eventType: "payment_submitted",
      },
    );

    return {
      ok: true,
      requestId: String(paymentRequest._id),
      orderId,
      token,
      redirectUrl,
    };
  } catch (error) {
    if (paymentRequest?._id) {
      await updateRequestWithHistory(
        paymentRequest,
        {
          status: REQUEST_STATUS_FAILED,
          externalLastResponse: {
            error: true,
            message: error?.message || "Sepah payment initialization failed.",
            status: error?.status || error?.response?.status || null,
            data: error?.response?.data || null,
            at: new Date().toISOString(),
          },
        },
        {
          actorType: "system",
          source: "payment_gateway",
          eventType: "payment_failed",
        },
      );
    }

    throw error;
  }
};

export const confirmSepahPayment = async ({ requestId, user }) => {
  const request = await models.request
    .findById(requestId)
    .populate("investor", "nationalId businessID isPrivatePerson mobile");

  if (!request) {
    const error = new Error("Request not found.");
    error.status = 404;
    throw error;
  }

  if (request.externalSource !== SEPAH_BROKER_EXTERNAL_SOURCE) {
    const error = new Error("This request is not a Sepah broker payment.");
    error.status = 422;
    throw error;
  }

  const requestOwnerId = request.investor?._id || request.investor;
  const isOwner =
    String(requestOwnerId || "") === String(user?._id || "");
  const isPrivilegedUser =
    user?.role?.isSuperAdmin === true || user?.role?.value === "superAdmin";

  if (!isOwner && !isPrivilegedUser) {
    const error = new Error("دسترسی محدود");
    error.status = 403;
    throw error;
  }

  if (
    request.operationType !== "issuance" ||
    request.paymentMethod !== "gateway"
  ) {
    return {
      succeeded: true,
      message: "این درخواست نیاز به تایید پرداخت سپه ندارد.",
      data: request,
    };
  }

  if (request.status !== REQUEST_STATUS_WAIT_FOR_PAYMENT && request.externalId) {
    return {
      succeeded: request.status !== REQUEST_STATUS_FAILED,
      message: "وضعیت پرداخت سپه قبلاً بررسی شده است.",
      data: request,
    };
  }

  const nationalId = normalizeNationalIdentifier(
    { identifier: getUserIdentifier(request.investor || user) },
    request.investor || user,
  );
  const fundRegisterCode =
    request?.meta?.sepahBroker?.fundRegisterCode || request.fundSeoRegisterNo;
  const fundRef =
    request?.meta?.sepahBroker?.fundRef ||
    assertSepahBrokerFundRef(fundRegisterCode);
  const token = String(request?.paymentGateway?.token || "").trim();

  if (!nationalId || !token || !fundRef) {
    await updateRequestWithHistory(
      request,
      {
        status: REQUEST_STATUS_FAILED,
        externalLastResponse: {
          error: true,
          message: "اطلاعات لازم برای تایید پرداخت سپه کامل نیست.",
          at: new Date().toISOString(),
        },
      },
      {
        actorType: "system",
        source: "payment_gateway",
        eventType: "payment_failed",
      },
    );

    const failedRequest = await models.request.findById(requestId);
    return {
      succeeded: false,
      message: "اطلاعات لازم برای تایید پرداخت سپه کامل نیست.",
      data: failedRequest,
    };
  }

  try {
    const checkPaymentResponse = await sepahBrokerServices.checkPayment({
      nationalId,
      token,
      fundRef,
    });

    if (isExplicitSepahFailure(checkPaymentResponse)) {
      await updateRequestWithHistory(
        request,
        {
          status: REQUEST_STATUS_FAILED,
          paymentGateway: buildSepahGatewaySummary({
            request,
            orderId: request.trackId,
            token,
            gatewayStatus: extractSepahGatewayStatus(checkPaymentResponse),
            rawResponse: checkPaymentResponse,
          }),
          externalLastResponse: checkPaymentResponse,
          externalLastSyncedAt: new Date(),
        },
        {
          actorType: "system",
          source: "payment_gateway",
          eventType: "payment_failed",
        },
      );

      const failedRequest = await models.request.findById(requestId);
      return {
        succeeded: false,
        message: "تایید پرداخت سپه ناموفق بود.",
        data: failedRequest,
      };
    }

    const buyOrderResponse = await sepahBrokerServices.addBuyOfflineOrder({
      identifier: nationalId,
      amount: Math.round(Number(request.amountRial || 0)),
      dateCondition: currentIsoDate(),
      fundRef,
    });
    const traceCode = extractSepahTraceCode(buyOrderResponse);
    const isBuyOrderApproved =
      Boolean(traceCode) && isSepahEmptyArrayPayload(checkPaymentResponse);

    await updateRequestWithHistory(
      request,
      {
        status: isBuyOrderApproved
          ? REQUEST_STATUS_APPROVED
          : REQUEST_STATUS_FAILED,
        externalId: traceCode || request.externalId || request.trackId,
        externalStatus: isBuyOrderApproved
          ? "approved_by_sepah_broker"
          : "failed_in_sepah_broker",
        externalLastResponse: {
          checkPayment: checkPaymentResponse,
          buyOrder: buyOrderResponse,
        },
        externalLastSyncedAt: new Date(),
        paymentGateway: buildSepahGatewaySummary({
          request,
          orderId: request.trackId,
          token,
          gatewayStatus: extractSepahGatewayStatus(checkPaymentResponse),
          rawResponse: checkPaymentResponse,
        }),
      },
      {
        actorType: "system",
        source: "payment_gateway",
        eventType: isBuyOrderApproved ? "payment_confirmed" : "payment_failed",
      },
    );

    const updatedRequest = await models.request.findById(requestId);
    return {
      succeeded: isBuyOrderApproved,
      message: isBuyOrderApproved
        ? "پرداخت سپه تایید شد و درخواست خرید ثبت گردید."
        : "پرداخت سپه تایید شد اما ثبت درخواست خرید در کارگزاری سپه ناموفق بود.",
      data: updatedRequest,
    };
  } catch (error) {
    await updateRequestWithHistory(
      request,
      {
        status: REQUEST_STATUS_FAILED,
        externalLastResponse: {
          error: true,
          message: error?.message || "Sepah payment confirmation failed.",
          status: error?.status || error?.response?.status || null,
          data: error?.response?.data || null,
          at: new Date().toISOString(),
        },
        externalLastSyncedAt: new Date(),
      },
      {
        actorType: "system",
        source: "payment_gateway",
        eventType: "payment_failed",
      },
    );

    const failedRequest = await models.request.findById(requestId);
    return {
      succeeded: false,
      message:
        error?.message || "تایید پرداخت سپه انجام نشد و درخواست خرید ثبت نشد.",
      data: failedRequest,
    };
  }
};

export const handleParsianCallback = async (body = {}) => {
  const payload = normalizeCallbackPayload(body);
  const tx = await models.transaction
    .findOne({
      gateway: "parsian",
      orderId: payload.orderId,
    })
    .populate("request");

  if (!tx || tx.status === "paid") {
    if (tx?.status === "paid") {
      await updateRequestFromTransaction(tx, {
        request: tx.request,
        status: resolveRequestStatusFromTransaction(tx),
      });
    }

    return {
      orderId: payload.orderId,
      status: tx?.status || "not_found",
      receiptToken: tx?.receiptToken,
    };
  }

  tx.callbackPayload = payload;
  tx.gatewayStatus = payload.status;
  tx.rrn = payload.rrn;
  tx.terminalNo = payload.terminalNo || tx.terminalNo;
  await tx.save();

  if (payload.status === -138) {
    tx.status = "cancelled";
    await tx.save();
    await updateRequestFromTransaction(tx, {
      request: tx.request,
      status: resolveRequestStatusFromTransaction(tx),
    });
    return callbackResult(tx);
  }

  if (!isValidSuccessfulCallback(payload, tx)) {
    tx.status = "failed";
    await tx.save();
    await updateRequestFromTransaction(tx, {
      request: tx.request,
      status: resolveRequestStatusFromTransaction(tx),
    });
    return callbackResult(tx);
  }

  const merchant = getMerchantByFundKey(tx.fundKey);
  if (!merchant?.login) {
    tx.status = "failed";
    await tx.save();
    await updateRequestFromTransaction(tx, {
      request: tx.request,
      status: resolveRequestStatusFromTransaction(tx),
    });
    return callbackResult(tx);
  }

  const confirm = await confirmPayment({
    loginAccount: merchant.login,
    token: payload.token,
  });

  tx.confirmResponse = confirm;
  tx.gatewayStatus = confirm.status;

  if (confirm.status !== 0) {
    tx.status = "failed";
    await tx.save();
    await updateRequestFromTransaction(tx, {
      request: tx.request,
      status: resolveRequestStatusFromTransaction(tx),
    });
    return callbackResult(tx);
  }

  tx.status = "paid";
  tx.rrn = confirm.rrn || tx.rrn;
  tx.cardMask = confirm.card || tx.cardMask;
  await tx.save();

  await updateRequestFromTransaction(tx, {
    request: tx.request,
    status: resolveRequestStatusFromTransaction(tx),
  });
  await submitTadbirIssuanceForRequest(tx.request?._id || tx.request);

  return callbackResult(tx);
};

export const getReceipt = async ({ orderId, receiptToken }) => {
  if (!isValidOrderId(orderId)) {
    const error = new Error("Invalid receipt request.");
    error.status = 400;
    throw error;
  }

  const tx = await models.transaction
    .findOne({ orderId })
    .select(
      "request gateway orderId status rrn cardMask updatedAt amountRial fundKey receiptToken",
    )
    .populate(
      "request",
      "trackId fundName fundSeoRegisterNo paymentMethod amountRial paymentGateway",
    )
    .lean();

  if (!tx) {
    const error = new Error("Receipt is not available.");
    error.status = 404;
    throw error;
  }

  if (!isReceiptAuthorized(receiptToken, tx)) {
    const error = new Error("Receipt is not available.");
    error.status = 403;
    throw error;
  }

  return formatReceipt(tx);
};

export const finishCallback = (req, res, { requestId = "" } = {}) => {
  if (shouldSendPlainCallbackResponse(req)) {
    return noStore(res).status(200).send("OK");
  }

  return noStore(res).redirect(303, buildPaymentResultUrl({ requestId }));
};

const createGatewayRequest = ({
  user,
  body = {},
  orderId,
  amountRial,
  units,
  unitPrice,
}) =>
  createRequestWithHistory(
    {
      investor: user._id,
      fundSeoRegisterNo: body.fundSeoRegisterNo,
      fundName: body.fundName || body.fund?.name,
      trackId: orderId,
      operationType: "issuance",
      paymentMethod: "gateway",
      amountRial,
      units,
      unitPrice,
      status: REQUEST_STATUS_WAIT_FOR_PAYMENT,
      meta: normalizeRequestMeta(body),
    },
    {
      actorUser: user,
      source: "payment_gateway",
      eventType: "created",
    },
  );

const createCardRequest = ({
  user,
  body = {},
  orderId,
  amountRial,
  units,
  unitPrice,
  paymentCard,
}) =>
  createRequestWithHistory(
    {
      investor: user._id,
      fundSeoRegisterNo: body.fundSeoRegisterNo,
      fundName: body.fundName || body.fund?.name,
      trackId: orderId,
      operationType: "issuance",
      paymentMethod: "card",
      paymentCard,
      amountRial,
      units,
      unitPrice,
      status: REQUEST_STATUS_WAIT_FOR_PAYMENT,
      meta: normalizeRequestMeta(body),
    },
    {
      actorUser: user,
      source: "payment_card",
      eventType: "created",
    },
  );

const callbackResult = (tx) => ({
  requestId: String(tx.request?._id || tx.request || ""),
  orderId: tx.orderId,
  status: tx.status,
  receiptToken: tx.receiptToken,
});

const startCardPayment = async ({ body = {}, params = {}, user, req }) => {
  const fundId = String(params.fundId || body.fundId || "").trim();
  const fundKey = normalizeFundKey(body.fundKey || body.fund?.key || fundId);
  const fundSeoRegisterNo = String(
    body.fundSeoRegisterNo || body.fund?.seoRegisterNo || fundId,
  ).trim();
  const unitPrice = Number(body.unitPrice || body.unit_price || 0);
  const units = normalizeUnits(body.units);
  const cardNumber = normalizeCardNumber(
    body.cardNumber || body.paymentCardNumber,
  );
  const amountRial = Number(body.amountRial || body.payable || 0);

  if (!fundId) {
    const error = new Error("fundId is required.");
    error.status = 422;
    throw error;
  }

  if (!/^\d{16}$/.test(cardNumber)) {
    const error = new Error("Invalid payment card number.");
    error.status = 422;
    throw error;
  }

  if (!Number.isSafeInteger(amountRial) || amountRial <= 0) {
    const error = new Error("Invalid amount.");
    error.status = 422;
    throw error;
  }

  if (!Number.isFinite(units) || units <= 0 || !Number.isFinite(unitPrice)) {
    const error = new Error("Invalid units or unit price.");
    error.status = 422;
    throw error;
  }

  await validateIssuanceRequestPayload({
    fundId,
    fundSeoRegisterNo,
    fundRegisterCode: body.fundRegisterCode || fundId,
    amountRial,
    units,
  });

  if (!fundSeoRegisterNo) {
    const error = new Error("fundSeoRegisterNo is required.");
    error.status = 422;
    throw error;
  }

  const paymentCard = await models.paymentCard
    .findOne({ cardNumber })
    .populate("batchId");

  if (!paymentCard) {
    const error = new Error("Payment card not found.");
    error.status = 404;
    throw error;
  }

  if (paymentCard.status !== "active") {
    const error = new Error("Payment card is not available.");
    error.status = 422;
    throw error;
  }

  if (isCardExpired(paymentCard)) {
    const error = new Error("Payment card has expired.");
    error.status = 422;
    throw error;
  }

  if (Number(paymentCard.amount) !== amountRial) {
    const error = new Error(
      "Payment amount must be exactly equal to the card amount.",
    );
    error.status = 422;
    throw error;
  }

  if (
    String(paymentCard.fundSeoRegisterNo || "").trim() !== fundSeoRegisterNo
  ) {
    const error = new Error(
      "This payment card can only be used for its assigned fund.",
    );
    error.status = 422;
    throw error;
  }

  const consumeFilter = {
    _id: paymentCard._id,
    status: "active",
    expiresAt: { $gt: new Date() },
  };
  const orderId = createOrderId();

  const consumedCard = await models.paymentCard.findOneAndUpdate(
    consumeFilter,
    {
      $set: {
        status: "used",
        usedAt: new Date(),
      },
    },
    { new: true },
  );

  if (!consumedCard) {
    const error = new Error(
      "This payment card has already been used or cancelled.",
    );
    error.status = 409;
    throw error;
  }

  let tx;
  let paymentRequest;
  const receiptToken = createReceiptToken();

  try {
    paymentRequest = await createCardRequest({
      user,
      body,
      orderId,
      amountRial,
      units,
      unitPrice,
      paymentCard: consumedCard._id,
    });

    tx = await models.transaction.create({
      request: paymentRequest._id,
      fundKey,
      amountRial,
      status: "paid",
      gateway: "payment_card",
      orderId,
      receiptToken,
      rrn: orderId,
      cardMask: maskCardNumber(consumedCard.cardNumber),
      clientIp: req.ip,
      clientUa: req.get("user-agent"),
    });
    await linkRequestToTransaction(paymentRequest, tx._id);
    await updateRequestFromTransaction(tx, {
      request: paymentRequest,
      status: resolveRequestStatusFromTransaction(tx),
    });

    await syncPaymentBatchStatus(
      consumedCard.batchId?._id || consumedCard.batchId,
    );
    await submitTadbirIssuanceForRequest(paymentRequest._id);

    return {
      ok: true,
      requestId: String(paymentRequest._id),
      orderId: tx.orderId,
      receiptToken: tx.receiptToken,
      redirectUrl: buildPaymentResultUrl({
        requestId: paymentRequest._id,
      }),
    };
  } catch (error) {
    await models.paymentCard.updateOne(
      {
        _id: consumedCard._id,
        transaction: { $exists: false },
      },
      {
        $set: {
          status: "active",
        },
        $unset: {
          usedAt: 1,
        },
      },
    );

    if (paymentRequest?._id) {
      await deleteRequestWithHistory(paymentRequest._id, {
        actorUser: user,
        source: "payment_card",
        eventType: "deleted",
      });
    }

    if (tx) {
      await models.transaction.deleteOne({ _id: tx._id });
    }

    await syncPaymentBatchStatus(
      consumedCard.batchId?._id || consumedCard.batchId,
    );
    throw error;
  }
};

const normalizeCallbackPayload = (body = {}) => {
  const amountRial = parseParsianAmount(body.Amount || body.amount);
  const swAmountRial = parseParsianAmount(body.SwAmount || body.swAmount);

  return {
    raw: body,
    orderId: String(body.OrderId || body.orderId || "").trim(),
    token: String(body.Token || body.token || "").trim(),
    terminalNo: parseParsianInteger(body.TerminalNo || body.terminalNo),
    rrn: parseParsianInteger(body.RRN || body.rrn),
    status: Number(toEnglishDigits(body.status ?? body.Status ?? -1)),
    amountRial,
    swAmountRial,
    effectiveAmountRial: swAmountRial || amountRial,
    hashCardNumber: String(
      body.HashCardNumber || body.hashCardNumber || "",
    ).trim(),
  };
};

const isValidSuccessfulCallback = (payload, tx) =>
  payload.status === 0 &&
  payload.token &&
  payload.rrn &&
  payload.rrn > 0 &&
  String(tx.token) === payload.token &&
  payload.orderId === String(tx.orderId) &&
  isCallbackAmountValid(payload, tx) &&
  isCallbackTerminalValid(payload, tx);

const isCallbackAmountValid = (payload, tx) => {
  if (!payload.effectiveAmountRial) {
    return true;
  }

  return Number(payload.effectiveAmountRial) === Number(tx.amountRial);
};

const isCallbackTerminalValid = (payload, tx) => {
  if (!payload.terminalNo || !tx.terminalNo) {
    return true;
  }

  return Number(payload.terminalNo) === Number(tx.terminalNo);
};

const parseParsianAmount = (value) => parseParsianInteger(value);

const parseParsianInteger = (value) => {
  const digits = toEnglishDigits(value).replace(/[^\d]/g, "");
  return digits ? Number(digits) : null;
};

const toEnglishDigits = (value) =>
  String(value ?? "")
    .replace(/[۰-۹]/g, (digit) => String("۰۱۲۳۴۵۶۷۸۹".indexOf(digit)))
    .replace(/[٠-٩]/g, (digit) => String("٠١٢٣٤٥٦٧٨٩".indexOf(digit)));

const shouldSendPlainCallbackResponse = (req) => {
  const accept = String(req.get("accept") || "").toLowerCase();
  const requestedWith = String(req.get("x-requested-with") || "").toLowerCase();

  return (
    accept.includes("application/json") ||
    requestedWith === "xmlhttprequest" ||
    req.query.response === "ok"
  );
};

const buildPaymentResultUrl = ({ requestId = "" } = {}) => {
  const baseUrl = String(paymentConfig.parsianConfig.resultUrl || "").replace(
    /\/+$/,
    "",
  );
  const normalizedRequestId = String(requestId || "").trim();

  if (!normalizedRequestId) {
    return baseUrl;
  }

  return `${baseUrl}/${encodeURIComponent(normalizedRequestId)}`;
};

const isReceiptAuthorized = (receiptToken, tx) => {
  if (!receiptToken || !tx.receiptToken) {
    return false;
  }

  return timingSafeStringEqual(receiptToken, tx.receiptToken);
};

const timingSafeStringEqual = (left, right) => {
  const leftBuffer = Buffer.from(String(left));
  const rightBuffer = Buffer.from(String(right));

  return (
    leftBuffer.length === rightBuffer.length &&
    crypto.timingSafeEqual(leftBuffer, rightBuffer)
  );
};

const isValidOrderId = (orderId) => /^\d{17}$/.test(String(orderId || ""));

const normalizeFundKey = (value) =>
  String(value || "")
    .trim()
    .toUpperCase();

const normalizeUnits = (units) => Number(Number(units || 0).toFixed(4));

const createOrderId = () =>
  `${new Date().toISOString().replace(/\D/g, "").slice(0, 14)}${random3()}`;

const random3 = () => String(Math.floor(Math.random() * 900) + 100);

const createReceiptToken = () => crypto.randomBytes(32).toString("hex");

const maskCardNumber = (value = "") => {
  const cardNumber = normalizeCardNumber(value);
  if (cardNumber.length !== 16) {
    return cardNumber;
  }

  return `${cardNumber.slice(0, 6)}******${cardNumber.slice(-4)}`;
};

const formatReceipt = (tx) => {
  const request = tx.request || null;
  const gatewaySummary = request?.paymentGateway || {};

  return {
    success: tx.status === "paid",
    title:
      tx.status === "paid"
        ? "Payment successful"
        : tx.status === "cancelled"
          ? "Payment cancelled"
          : "Payment failed",
    orderId: tx.orderId,
    rrn: tx.rrn || null,
    card: tx.cardMask || gatewaySummary.cardMask || null,
    cardMask: tx.cardMask || gatewaySummary.cardMask || null,
    time: tx.updatedAt,
    trackId: request?.trackId || tx.orderId,
    paymentMethod:
      request?.paymentMethod || resolvePaymentMethodFromTransaction(tx),
    amountRial: request?.amountRial ?? tx.amountRial,
    fundKey: tx.fundKey || request?.fundSeoRegisterNo,
    fundSeoRegisterNo: request?.fundSeoRegisterNo || null,
    fundName: request?.fundName || null,
  };
};

const buildRequestGatewaySummary = (tx) => ({
  gateway: tx.gateway,
  orderId: tx.orderId,
  rrn: tx.rrn,
  cardMask: tx.cardMask,
  terminalNo: tx.terminalNo,
  gatewayStatus: tx.gatewayStatus,
});

const resolveRequestStatusFromTransaction = (tx) => {
  if (tx.status === "paid") {
    return REQUEST_STATUS_REVIEWING;
  }

  if (tx.status === "failed" || tx.status === "cancelled") {
    return REQUEST_STATUS_FAILED;
  }

  return undefined;
};

const resolvePaymentMethodFromTransaction = (tx) =>
  tx.gateway === "payment_card" ? "card" : "gateway";

const linkRequestToTransaction = async (request, transactionId) => {
  if (!request?._id || !transactionId) {
    return null;
  }

  if (String(request.transaction || "") === String(transactionId)) {
    return request;
  }

  return updateRequestWithHistory(
    request,
    { transaction: transactionId },
    {
      actorType: "system",
      source:
        request?.paymentMethod === "card" ? "payment_card" : "payment_gateway",
      eventType: "transaction_linked",
    },
  );
};

const ensureRequestForTransaction = async (tx) => {
  const linkedRequestId = tx.request?._id || tx.request;
  if (linkedRequestId) {
    const request =
      tx.request?._id && typeof tx.request.save === "function"
        ? tx.request
        : await models.request.findById(linkedRequestId);

    if (request) {
      await linkRequestToTransaction(request, tx._id);
      if (String(tx.request?._id || tx.request) !== String(request._id)) {
        tx.request = request._id;
        await tx.save();
      }
      return request;
    }
  }

  const requestByTransaction = await models.request.findOne({
    transaction: tx._id,
  });
  if (requestByTransaction) {
    if (String(tx.request || "") !== String(requestByTransaction._id)) {
      tx.request = requestByTransaction._id;
      await tx.save();
    }
    return requestByTransaction;
  }

  return null;
};

const updateRequestFromTransaction = async (tx, { request, status } = {}) => {
  const linkedRequest = request || (await ensureRequestForTransaction(tx));
  if (!linkedRequest) {
    return null;
  }

  const updateData = {};

  if (String(linkedRequest.transaction || "") !== String(tx._id)) {
    updateData.transaction = tx._id;
  }

  if (
    linkedRequest.paymentMethod === "gateway" ||
    resolvePaymentMethodFromTransaction(tx) === "gateway"
  ) {
    updateData.paymentGateway = buildRequestGatewaySummary(tx);
  }

  if (status) {
    updateData.status = status;
  }

  return updateRequestWithHistory(linkedRequest, updateData, {
    actorType: "system",
    source: tx.gateway === "payment_card" ? "payment_card" : "payment_gateway",
    eventType:
      tx.status === "paid"
        ? "payment_confirmed"
        : tx.status === "failed"
          ? "payment_failed"
          : tx.status === "cancelled"
            ? "payment_cancelled"
            : "payment_submitted",
  });
};
