import models from "$models/index.js";
import { sepahBrokerServices, tadbirServices } from "$services/index.js";
import {
  accessGrantHelpers,
  identityHelpers,
  requestHelpers,
} from "$helpers/index.js";
import { buildRequestTableQuery } from "./request.query.js";
import {
  assertSepahBrokerFundRef,
  isSepahBrokerFund,
  SEPAH_BROKER_EXTERNAL_SOURCE,
} from "$services/sepah/sepah-broker.helper.js";
const parseNumericValue = (value) => {
  const parsedValue = Number(value);
  return Number.isFinite(parsedValue) ? parsedValue : 0;
};

const createOrderId = () =>
  `${new Date().toISOString().replace(/\D/g, "").slice(0, 14)}${String(
    Math.floor(Math.random() * 900) + 100,
  )}`;

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

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

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

  return "";
};

const resolveRedeemableUnits = (balanceItem) => {
  const remainingUnit = parseNumericValue(balanceItem?.remainingUnit);
  const remainingWithGuaranteeAndRequest = parseNumericValue(
    balanceItem?.remainingWithGuaranteeAndRequest,
  );

  if (remainingWithGuaranteeAndRequest > 0) {
    return Math.max(
      0,
      Math.min(remainingUnit, remainingWithGuaranteeAndRequest),
    );
  }

  return Math.max(0, remainingUnit);
};

const resolveSepahRedeemableUnits = (remainPayload) => {
  const adjustedRemain = parseNumericValue(remainPayload?.AdjustedRemain);
  const currentRemain = parseNumericValue(remainPayload?.CurrentRemain);
  const blockedRemain = parseNumericValue(remainPayload?.BlockedRemain);
  const netCurrentRemain = Math.max(0, currentRemain - blockedRemain);

  if (adjustedRemain > 0) {
    return Math.min(adjustedRemain, netCurrentRemain || adjustedRemain);
  }

  return netCurrentRemain;
};

const isOwner = (itemUser, requestUser) => {
  const itemUserId = itemUser?._id || itemUser;
  return itemUserId?.toString() === requestUser?._id?.toString();
};

const populateRequestRelations = (query) =>
  query
    .populate("investor", identityHelpers.USER_IDENTITY_PROJECTION)
    .populate("transaction")
    .populate("paymentCard")
    .populate({
      path: "paymentSlip",
      populate: {
        path: "receiptFile",
        select: "type extension createdAt",
      },
    });

const userHasPermission = (user, permissionKey) => {
  if (user?.role?.isSuperAdmin || user?.role?.value === "superAdmin") {
    return true;
  }

  return (user?.role?.permissions || []).some((permission) => {
    if (typeof permission === "string") {
      return permission === permissionKey;
    }

    return permission?.key === permissionKey;
  });
};

const withEmbeddedHistory = async (request) => {
  if (!request?._id) {
    return request;
  }

  const { history, historyCount } =
    await requestHelpers.getEmbeddedRequestHistory(request._id);

  const plainRequest =
    typeof request.toObject === "function" ? request.toObject() : request;

  return {
    ...plainRequest,
    history,
    historyCount,
  };
};

export const ALL = async (req, res) => {
  try {
    const {
      pageSize,
      currentPage,
      skip,
      filterQuery,
      sort,
      sortField,
      sortDirection,
    } = buildRequestTableQuery(req.query);
    const query = { ...filterQuery };
    const canAccessAllRequests = accessGrantHelpers.hasAllResourceAccess(
      req.user,
      "requests",
    );

    if (!canAccessAllRequests) {
      query.investor = req.user._id;
    }

    const [requests, totalData] = await Promise.all([
      populateRequestRelations(
        models.request.find(query).limit(pageSize).skip(skip).sort(sort).lean(),
      ),
      models.request.countDocuments(query),
    ]);
    const totalPage = pageSize ? Math.ceil(totalData / pageSize) : 0;

    res.status(200).send({
      requests,
      totalData,
      currentPage,
      totalPage,
      sortField,
      sortDirection,
    });
  } catch (error) {
    const status = error.status || 500;
    const message = error.message || "خطا در دریافت request";
    return res.status(status).send({ message });
  }
};

export const SINGLE = async (req, res) => {
  try {
    const { id } = req.params;
    let data = await populateRequestRelations(models.request.findById(id));

    if (!data) {
      return res.status(404).send({ message: "یافت نشد" });
    }

    if (
      !accessGrantHelpers.hasAllResourceAccess(req.user, "requests") &&
      !isOwner(data.investor, req.user)
    ) {
      return res.status(403).send({ message: "دسترسی محدود" });
    }

    if (data.externalSource === "tadbir" && data?.externalId !== "") {
      // await requestHelpers.syncTadbirRequest(id);
      data = await populateRequestRelations(models.request.findById(id));
    }

    if (userHasPermission(req.user, "requestHistories:view")) {
      return res.status(200).send(await withEmbeddedHistory(data));
    }

    return res.status(200).send(data);
  } catch (error) {
    const status = error.status || 500;
    const message = error.message || "خطا در دریافت request";
    return res.status(status).send({ message });
  }
};

export const CREATE = async (req, res) => {
  try {
    const data = req.body;
    data.investor = req.user._id;

    const newItem = await requestHelpers.createRequestWithHistory(data, {
      actorUser: req.user,
      source: "request_controller",
      eventType: "created",
    });
    const populated = await populateRequestRelations(
      models.request.findById(newItem._id),
    );

    res.status(201).send({
      message: "با موفقیت ایجاد شد",
      data: populated,
    });
  } catch (error) {
    const status = error.status || 500;
    const message = error.message || "خطا در ایجاد request";
    return res.status(status).send({ message });
  }
};

export const CREATE_REDEMPTION = async (req, res) => {
  try {
    const nationalId = req.identifier;

    if (!req.user?._id || !nationalId) {
      return res.status(400).send({
        message: "شناسه هویتی کاربر برای ثبت درخواست ابطال کامل نیست.",
      });
    }

    const {
      fundKey,
      fundId,
      fundSeoRegisterNo,
      fundName,
      units,
      unitPrice,
      amountRial,
      meta,
    } = req.body || {};

    const normalizedFundId = String(fundId || "").trim();
    const normalizedFundKey = String(fundKey || "")
      .trim()
      .toUpperCase();
    const normalizedFundSeoRegisterNo = String(
      fundSeoRegisterNo || normalizedFundId,
    ).trim();
    const normalizedFundName = String(fundName || "").trim();
    const requestedUnits = parseNumericValue(units);
    const requestedUnitPrice = parseNumericValue(unitPrice);
    const requestedAmountRial = parseNumericValue(amountRial);

    if (
      !normalizedFundId ||
      !normalizedFundKey ||
      !normalizedFundSeoRegisterNo ||
      !normalizedFundName
    ) {
      return res.status(422).send({
        message: "اطلاعات صندوق برای ثبت درخواست ابطال کامل نیست.",
      });
    }

    if (
      requestedUnits <= 0 ||
      requestedUnitPrice <= 0 ||
      requestedAmountRial <= 0
    ) {
      return res.status(422).send({
        message: "تعداد واحد، NAV و مبلغ درخواست ابطال باید معتبر باشند.",
      });
    }

    if (isSepahBrokerFund(normalizedFundId)) {
      const fundRef = assertSepahBrokerFundRef(normalizedFundId);
      const customerRemain =
        await sepahBrokerServices.getCustomerRemain(nationalId);
      const redeemableUnits = resolveSepahRedeemableUnits(customerRemain);

      if (redeemableUnits <= 0) {
        return res.status(422).send({
          message:
            "در حال حاضر مانده قابل فروشی برای این صندوق در کارگزاری سپه ندارید.",
        });
      }

      if (requestedUnits > redeemableUnits) {
        return res.status(422).send({
          message: `حداکثر واحد قابل فروش در این لحظه ${redeemableUnits.toLocaleString("fa-IR")} واحد است.`,
        });
      }

      const sellOrderResponse = await sepahBrokerServices.addSellOfflineOrder({
        identifier: nationalId,
        quantity: requestedUnits,
        dateCondition: currentIsoDateTimeAtStartOfDay(),
        fundRef,
      });
      const traceCode = extractTraceCode(sellOrderResponse);
      const isSellOrderApproved = Boolean(traceCode);

      const newItem = await requestHelpers.createRequestWithHistory(
        {
          investor: req.user._id,
          fundSeoRegisterNo: normalizedFundSeoRegisterNo,
          fundName: normalizedFundName,
          trackId: traceCode || createOrderId(),
          operationType: "redemption",
          paymentMethod: "gateway",
          amountRial: Math.round(requestedAmountRial),
          units: requestedUnits,
          unitPrice: Math.round(requestedUnitPrice),
          status: isSellOrderApproved ? "approved" : "failed",
          externalSource: SEPAH_BROKER_EXTERNAL_SOURCE,
          externalId: traceCode || "",
          externalStatus: isSellOrderApproved
            ? "approved_by_sepah_broker"
            : "failed_in_sepah_broker",
          externalLastResponse: sellOrderResponse,
          externalLastSyncedAt: new Date(),
          meta: {
            ...(meta && typeof meta === "object" ? meta : {}),
            sepahBroker: {
              fundRef,
              fundRegisterCode: normalizedFundId,
              customerRemain,
            },
          },
        },
        {
          actorUser: req.user,
          source: "request_controller",
          eventType: "created",
        },
      );

      const populated = await populateRequestRelations(
        models.request.findById(newItem._id),
      );

      return res.status(201).send({
        message: isSellOrderApproved
          ? "درخواست فروش در کارگزاری سپه با موفقیت ثبت شد."
          : "ثبت درخواست فروش در کارگزاری سپه ناموفق بود.",
        data: populated,
        externalSubmissionSucceeded: isSellOrderApproved,
      });
    }

    const investmentProfile =
      await tadbirServices.getInvestmentProfile(nationalId);
    const matchedBalance = (investmentProfile || []).find(
      (item) =>
        String(item?.fundSeoRegisterNo || "").trim() ===
        normalizedFundSeoRegisterNo,
    );

    if (!matchedBalance) {
      return res.status(404).send({
        message: "موجودی این صندوق برای کاربر یافت نشد.",
      });
    }

    const redeemableUnits = resolveRedeemableUnits(matchedBalance);

    if (redeemableUnits <= 0) {
      return res.status(422).send({
        message: "در حال حاضر مانده قابل ابطالی برای این صندوق ندارید.",
      });
    }

    if (requestedUnits > redeemableUnits) {
      return res.status(422).send({
        message: `حداکثر واحد قابل ابطال در این لحظه ${redeemableUnits.toLocaleString("fa-IR")} واحد است.`,
      });
    }

    const newItem = await requestHelpers.createRequestWithHistory(
      {
        investor: req.user._id,
        fundSeoRegisterNo: normalizedFundSeoRegisterNo,
        fundName: normalizedFundName,
        trackId: createOrderId(),
        operationType: "redemption",
        paymentMethod: "gateway",
        amountRial: Math.round(requestedAmountRial),
        units: requestedUnits,
        unitPrice: Math.round(requestedUnitPrice),
        status: "reviewing",
        meta: meta && typeof meta === "object" ? meta : undefined,
      },
      {
        actorUser: req.user,
        source: "request_controller",
        eventType: "created",
      },
    );

    const syncedRequest = await requestHelpers.submitTadbirRedemptionForRequest(
      newItem._id,
    );
    const populated = await populateRequestRelations(
      models.request.findById(syncedRequest?._id || newItem._id),
    );
    const tadbirCreateFailed = populated?.status === "failed";

    res.status(201).send({
      message: tadbirCreateFailed
        ? "درخواست ابطال ثبت شد، اما ارسال آن به تدبیر ناموفق بود."
        : "درخواست ابطال با موفقیت ثبت شد.",
      data: populated,
      externalSubmissionSucceeded: !tadbirCreateFailed,
    });
  } catch (error) {
    const status = error.status || 500;
    const message = error.message || "خطا در ثبت درخواست ابطال";
    return res.status(status).send({ message });
  }
};

export const UPDATE = async (req, res) => {
  try {
    const { id } = req.params;
    const updateData = req.body;

    delete updateData.investor;
    delete updateData._id;
    delete updateData.createdAt;
    delete updateData.updatedAt;

    const existingItem = await models.request.findById(id);
    if (!existingItem) {
      return res.status(404).send({ message: "یافت نشد" });
    }

    if (
      !accessGrantHelpers.hasAllResourceAccess(req.user, "requests") &&
      !isOwner(existingItem.investor, req.user)
    ) {
      return res.status(403).send({ message: "دسترسی محدود" });
    }

    await requestHelpers.updateRequestWithHistory(id, updateData, {
      actorUser: req.user,
      source: "request_controller",
    });
    const updated = await populateRequestRelations(models.request.findById(id));

    res.status(200).send({
      message: "با موفقیت ویرایش شد",
      data: updated,
    });
  } catch (error) {
    const status = error.status || 500;
    const message = error.message || "خطا در ویرایش request";
    return res.status(status).send({ message });
  }
};

export const DELETE = async (req, res) => {
  try {
    const { id } = req.params;
    const existingItem = await models.request.findById(id);

    if (!existingItem) {
      return res.status(404).send({ message: "یافت نشد" });
    }

    if (
      !accessGrantHelpers.hasAllResourceAccess(req.user, "requests") &&
      !isOwner(existingItem.investor, req.user)
    ) {
      return res.status(403).send({ message: "دسترسی محدود" });
    }

    await requestHelpers.deleteRequestWithHistory(id, {
      actorUser: req.user,
      source: "request_controller",
      eventType: "deleted",
    });

    res.status(200).send({
      message: "با موفقیت حذف شد",
    });
  } catch (error) {
    const status = error.status || 500;
    const message = error.message || "خطا در حذف request";
    return res.status(status).send({ message });
  }
};

export const HISTORY = async (req, res) => {
  try {
    const { id } = req.params;
    const data = await models.request.findById(id).select("investor");

    if (!data) {
      return res.status(404).send({ message: "یافت نشد" });
    }

    if (
      !accessGrantHelpers.hasAllResourceAccess(req.user, "requests") &&
      !isOwner(data.investor, req.user)
    ) {
      return res.status(403).send({ message: "دسترسی محدود" });
    }

    const result = await requestHelpers.getRequestHistory(id, {
      page: req.query.page,
      pageSize: req.query.pageSize,
      sort: req.query.sort,
    });

    return res.status(200).send(result);
  } catch (error) {
    const status = error.status || 500;
    const message = error.message || "خطا در دریافت تاریخچه request";
    return res.status(status).send({ message });
  }
};

export const RECONCILE_TADBIR_SINGLE = async (req, res) => {
  try {
    const { id } = req.params;
    const data = await models.request.findById(id).select("investor");

    if (!data) {
      return res.status(404).send({ message: "یافت نشد" });
    }

    if (
      !accessGrantHelpers.hasAllResourceAccess(req.user, "requests") &&
      !isOwner(data.investor, req.user)
    ) {
      return res.status(403).send({ message: "دسترسی محدود" });
    }

    const request = await requestHelpers.reconcileSingleTadbirRequest(id);
    const populated = await populateRequestRelations(
      models.request.findById(request?._id || id),
    );

    return res.status(200).send({
      message: "همگام‌سازی درخواست با تدبیر انجام شد.",
      data: userHasPermission(req.user, "requestHistories:view")
        ? await withEmbeddedHistory(populated)
        : populated,
    });
  } catch (error) {
    const status = error.status || 500;
    const message = error.message || "خطا در همگام‌سازی درخواست با تدبیر";
    return res.status(status).send({ message });
  }
};

export const RETRY_TADBIR_ISSUANCE_SINGLE = async (req, res) => {
  try {
    const { id } = req.params;
    const data = await models.request.findById(id).select("investor");

    if (!data) {
      return res.status(404).send({ message: "یافت نشد" });
    }

    if (
      !accessGrantHelpers.hasAllResourceAccess(req.user, "requests") &&
      !isOwner(data.investor, req.user)
    ) {
      return res.status(403).send({ message: "دسترسی محدود" });
    }

    const request = await requestHelpers.retrySingleTadbirIssuanceRequest(id);
    const populated = await populateRequestRelations(
      models.request.findById(request?._id || id),
    );

    return res.status(200).send({
      message: "تلاش مجدد ارسال صدور به تدبیر انجام شد.",
      data: userHasPermission(req.user, "requestHistories:view")
        ? await withEmbeddedHistory(populated)
        : populated,
    });
  } catch (error) {
    const status = error.status || 500;
    const message = error.message || "خطا در تلاش مجدد ارسال صدور به تدبیر";
    return res.status(status).send({ message });
  }
};
