import { sepahBrokerServices, tadbirServices } from "$services/index.js";
import {
  buildInvestSnapshot,
  createInvestHistoryEntry,
  getEmbeddedInvestHistory,
  getInvestHistory,
} from "$helpers/investHistory/investHistory.helper.js";
const CHART_RANGE_IN_DAYS = {
  "1w": 7,
  "1m": 30,
  "1y": 365,
};

const extractErrorMessage = (error, fallbackMessage) =>
  error?.response?.data?.errorMessage ||
  error?.response?.data?.message ||
  error?.message ||
  fallbackMessage;

const parseNumericValue = (value) => {
  if (typeof value === "number") {
    return Number.isFinite(value) ? value : 0;
  }

  if (typeof value === "string") {
    const normalizedValue = value.replaceAll(",", "").trim();
    const parsedValue = Number(normalizedValue);
    return Number.isFinite(parsedValue) ? parsedValue : 0;
  }

  return 0;
};

const normalizeCardexSummary = (cardex) => {
  const summary = cardex?.summary;

  if (Array.isArray(summary)) {
    return summary[0] || {};
  }

  if (summary && typeof summary === "object") {
    return summary;
  }

  return {};
};

const getFundSeoRegisterNoFromInvestorFund = (item) =>
  String(
    item?.fundSeoRegisterNo ||
      item?.FundSeoRegisterNo ||
      item?.fundRegisterCode ||
      item?.FundRegisterCode ||
      item?.seoRegisterNo ||
      item?.SeoRegisterNo ||
      "",
  ).trim();

const normalizeInvestorFundReinvestMeta = (item) => ({
  reInvest: item?.reInvest ?? false,
  reInvestPercent: parseNumericValue(item?.reInvestPercent),
});

const findInvestorFundBySeoRegisterNo = (
  profile = [],
  fundSeoRegisterNo = "",
) =>
  (Array.isArray(profile) ? profile : []).find(
    (item) =>
      getFundSeoRegisterNoFromInvestorFund(item) ===
      String(fundSeoRegisterNo || "").trim(),
  );

const normalizeRange = (range) =>
  Object.prototype.hasOwnProperty.call(CHART_RANGE_IN_DAYS, range)
    ? range
    : "1m";

const buildRangeDates = (range) => {
  const normalizedRange = normalizeRange(range);
  const rangeDays = CHART_RANGE_IN_DAYS[normalizedRange];
  const toDate = new Date();
  const fromDate = new Date();
  fromDate.setDate(fromDate.getDate() - rangeDays);

  return {
    normalizedRange,
    fromDate: fromDate.toISOString(),
    toDate: toDate.toISOString(),
  };
};

const normalizeDateKey = (value) => {
  if (!value) return "";

  const parsedValue = new Date(value);
  if (Number.isNaN(parsedValue.getTime())) {
    return "";
  }

  return parsedValue.toISOString().slice(0, 10);
};

const formatDateLabel = (value) => {
  if (!value) return "—";
  return new Date(value).toLocaleDateString("fa-IR");
};

const parseRequestFundName = (requestItem) =>
  requestItem?.fund?.title ||
  requestItem?.Fund?.Title ||
  requestItem?.fundTitle ||
  requestItem?.FundTitle ||
  "صندوق";

const parseRequestTypeName = (requestItem) =>
  String(
    requestItem?.typeName ||
      requestItem?.TypeName ||
      requestItem?.type?.name ||
      requestItem?.Type?.Name ||
      "",
  ).trim();

const isIssuanceRequest = (typeName) =>
  /صدور|subscription|issue|issuance/i.test(String(typeName || ""));

const isRedemptionRequest = (typeName) =>
  /ابطال|redemption|cancel/i.test(String(typeName || ""));

const fetchAllTurnoverRows = async ({
  nationalCode,
  fundSeoRegisterNo,
  fromDate,
  toDate,
}) => {
  const pageSize = 200;
  let pageNumber = 1;
  let totalPage = 1;
  const rows = [];

  while (pageNumber <= totalPage) {
    const response = await tadbirServices.getTurnoverAccount({
      nationalCode,
      fundSeoRegisterNo,
      fromDate,
      toDate,
      pageNumber,
      pageSize,
    });

    rows.push(...(response?.turnoverAccount || []));
    totalPage = Number(response?.totalPage) || 1;
    pageNumber += 1;
  }

  return rows;
};

const fetchAllExternalRequests = async ({ nationalCode, fromDate, toDate }) => {
  const pageSize = 200;
  let pageNumber = 1;
  let totalPage = 1;
  const rows = [];

  while (pageNumber <= totalPage) {
    const response = await tadbirServices.getExternalRequests({
      nationalCode,
      fromDate,
      toDate,
      pageNumber,
      pageSize,
    });

    rows.push(...(response?.externalRequests || []));
    totalPage = Number(response?.totalPage) || 1;
    pageNumber += 1;
  }

  return rows;
};

export const FUNDS = async (req, res) => {
  try {
    const funds = await tadbirServices.getFunds();
    res.send({
      message: "DONE !",
      funds,
    });
  } catch (error) {
    const errorMessage =
      error?.response?.data?.message ||
      error?.message ||
      "دریافت لیست صندوق‌ها با خطا مواجه شد.";
    const statusCode = error.response?.status || 400;

    res.status(statusCode).send({ message: errorMessage });
  }
};

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

    if (!nationalId) {
      return res.status(400).send({
        message: "شناسه هویتی کاربر برای دریافت موجودی‌ها یافت نشد.",
      });
    }

    const [balances, data] = await Promise.all([
      tadbirServices.getBalances(nationalId),
      sepahBrokerServices.getCustomerRemain(nationalId),
    ]);

    res.send({ balances, data });

    // const balances = await tadbirServices.getBalances(nationalId);
    // res.send({
    //   message: "DONE !",
    //   balances,
    // });
  } catch (error) {
    const errorMessage =
      error?.response?.data?.message ||
      error?.message ||
      "دریافت موجودی‌ها با خطا مواجه شد.";
    const statusCode = error.response?.status || 400;

    res.status(statusCode).send({ message: errorMessage });
  }
};

export const EXTERNAL_REQUESTS = async (req, res) => {
  try {
    const nationalCode = req.identifier;

    if (!nationalCode) {
      return res.status(400).send({
        message: "شناسه هویتی کاربر برای دریافت کلیه درخواست‌ها یافت نشد.",
      });
    }

    const externalRequests = await tadbirServices.getExternalRequests({
      nationalCode,
      ...req.query,
    });

    res.send({
      message: "DONE !",
      ...externalRequests,
    });
  } catch (error) {
    const statusCode = error.response?.status || 400;
    res.status(statusCode).send({
      message: extractErrorMessage(
        error,
        "دریافت لیست کلیه درخواست‌ها با خطا مواجه شد.",
      ),
    });
  }
};

export const INVEST_CARDEX = async (req, res) => {
  try {
    const nationalCode = req.identifier;
    const { fundSeoRegisterNo } = req.query;

    if (!nationalCode) {
      return res.status(400).send({
        message: "شناسه هویتی کاربر برای دریافت کاردکس سرمایه‌گذار یافت نشد.",
      });
    }

    if (!fundSeoRegisterNo) {
      return res.status(400).send({
        message: "شناسه ثبت صندوق برای دریافت کاردکس الزامی است.",
      });
    }

    const investCardex = await tadbirServices.getInvestorCardex({
      nationalCode,
      fundSeoRegisterNo,
    });

    res.send({
      message: "DONE !",
      investCardex,
    });
  } catch (error) {
    const statusCode = error.response?.status || 400;
    res.status(statusCode).send({
      message: extractErrorMessage(
        error,
        "دریافت کاردکس سرمایه‌گذار با خطا مواجه شد.",
      ),
    });
  }
};

export const PROFILE = async (req, res) => {
  try {
    const nationalCode = req.identifier;

    if (!nationalCode) {
      return res.status(400).send({
        message: "شناسه هویتی کاربر برای دریافت پروفایل سرمایه‌گذاری یافت نشد.",
      });
    }

    const [fundsResponse, investorFunds, cardexEntries] = await Promise.all([
      tadbirServices.getFunds(),
      tadbirServices.getInvestorFunds(nationalCode),
      Promise.all(
        tadbirServices.TADBIR_INTERMEDIARY_SEJAM_FUND_LIST.map(
          async (fundSeoRegisterNo) => {
            const cardex = await tadbirServices.getInvestorCardex({
              nationalCode,
              fundSeoRegisterNo,
            });

            return [fundSeoRegisterNo, cardex];
          },
        ),
      ),
    ]);

    const fundsByRegisterCode = new Map(
      (fundsResponse?.items || []).map((fund) => [
        String(fund.fundRegisterCode),
        fund,
      ]),
    );
    const investorFundsBySeoRegisterNo = new Map(
      (investorFunds || [])
        .map((item) => [getFundSeoRegisterNoFromInvestorFund(item), item])
        .filter(([key]) => Boolean(key)),
    );

    const funds = [];
    const all = {
      totalAsset: 0,
      totalAssetPercent: 0,
      totalProfit: 0,
      totalUnits: 0,
    };

    for (const [fundSeoRegisterNo, cardex] of cardexEntries) {
      const summary = normalizeCardexSummary(cardex);
      const totalAsset = parseNumericValue(summary.totalValue);
      const totalProfit = parseNumericValue(summary.totalProfit);
      const totalUnits = parseNumericValue(summary.count);
      const fund = fundsByRegisterCode.get(String(fundSeoRegisterNo)) || null;
      const investorFundMeta = normalizeInvestorFundReinvestMeta(
        investorFundsBySeoRegisterNo.get(String(fundSeoRegisterNo)),
      );

      all.totalAsset += totalAsset;
      all.totalProfit += totalProfit;
      all.totalUnits += totalUnits;

      funds.push({
        ...fund,
        ...investorFundMeta,
        stats: {
          totalAsset,
          totalAssetPercent: 0,
          totalProfit,
          totalUnits,
          summary,
        },
      });
    }

    all.totalAssetPercent = all.totalAsset > 0 ? 100 : 0;

    for (const fund of funds) {
      fund.stats.totalAssetPercent =
        all.totalAsset > 0
          ? Number(((fund.stats.totalAsset / all.totalAsset) * 100).toFixed(2))
          : 0;
    }

    res.send({
      message: "DONE !",
      profile: {
        all,
        funds,
      },
    });
  } catch (error) {
    const statusCode = error.response?.status || 400;
    res.status(statusCode).send({
      message: extractErrorMessage(
        error,
        "دریافت پروفایل سرمایه‌گذاری با خطا مواجه شد.",
      ),
    });
  }
};

export const TURNOVER_ACCOUNT = async (req, res) => {
  try {
    const nationalCode = req.identifier;
    const {
      fundSeoRegisterNo,
      fromDate,
      toDate,
      pageNumber,
      pageSize,
      basketId,
      sortField,
      sortDirection,
    } = req.query;

    if (!nationalCode) {
      return res.status(400).send({
        message: "شناسه هویتی کاربر برای دریافت گردش حساب یافت نشد.",
      });
    }

    if (!fundSeoRegisterNo) {
      return res.status(400).send({
        message: "شناسه صندوق برای دریافت گردش حساب الزامی است.",
      });
    }

    const turnoverAccount = await tadbirServices.getTurnoverAccount({
      nationalCode,
      fundSeoRegisterNo,
      fromDate,
      toDate,
      pageNumber,
      pageSize,
      basketId,
      sortField,
      sortDirection,
    });

    res.send({
      message: "DONE !",
      ...turnoverAccount,
    });
  } catch (error) {
    const statusCode = error.response?.status || 400;
    res.status(statusCode).send({
      message: extractErrorMessage(error, "دریافت گردش حساب با خطا مواجه شد."),
    });
  }
};

export const DASHBOARD_CHARTS = async (req, res) => {
  try {
    const nationalCode = req.identifier;

    if (!nationalCode) {
      return res.status(400).send({
        message: "شناسه هویتی کاربر برای دریافت نمودارهای داشبورد یافت نشد.",
      });
    }

    const { normalizedRange, fromDate, toDate } = buildRangeDates(
      req.query?.range,
    );
    const balances = await tadbirServices.getBalances(nationalCode);
    const uniqueFunds = Array.from(
      new Map(
        (balances || [])
          .filter((item) => item?.fundSeoRegisterNo)
          .map((item) => [
            String(item.fundSeoRegisterNo),
            {
              fundSeoRegisterNo: String(item.fundSeoRegisterNo),
              fundName:
                item.fundName ||
                item.groupName ||
                `صندوق ${String(item.fundSeoRegisterNo)}`,
            },
          ]),
      ).values(),
    );

    const [turnoverEntries, externalRequests] = await Promise.all([
      Promise.all(
        uniqueFunds.map(async (fund) => ({
          ...fund,
          rows: await fetchAllTurnoverRows({
            nationalCode,
            fundSeoRegisterNo: fund.fundSeoRegisterNo,
            fromDate,
            toDate,
          }),
        })),
      ),
      fetchAllExternalRequests({ nationalCode, fromDate, toDate }),
    ]);

    const balanceDateKeys = Array.from(
      new Set(
        turnoverEntries.flatMap((fund) =>
          (fund.rows || [])
            .map((row) => normalizeDateKey(row?.voucherDate))
            .filter(Boolean),
        ),
      ),
    ).sort();

    const balanceDatasets = turnoverEntries
      .map((fund) => {
        const balanceByDate = new Map();

        (fund.rows || [])
          .slice()
          .sort(
            (left, right) =>
              new Date(left?.voucherDate || 0).getTime() -
              new Date(right?.voucherDate || 0).getTime(),
          )
          .forEach((row) => {
            const dateKey = normalizeDateKey(row?.voucherDate);
            if (!dateKey) return;

            balanceByDate.set(dateKey, parseNumericValue(row?.accountBalance));
          });

        return {
          label: fund.fundName,
          fundSeoRegisterNo: fund.fundSeoRegisterNo,
          data: balanceDateKeys.map((dateKey) =>
            balanceByDate.has(dateKey) ? balanceByDate.get(dateKey) : null,
          ),
        };
      })
      .filter((dataset) => dataset.data.some((value) => value !== null));

    const requestsByFund = new Map();

    for (const requestItem of externalRequests) {
      const fundName = parseRequestFundName(requestItem);
      const typeName = parseRequestTypeName(requestItem);
      const currentEntry = requestsByFund.get(fundName) || {
        fundName,
        issuanceAmount: 0,
        issuanceCount: 0,
        redemptionAmount: 0,
        redemptionCount: 0,
      };

      const amount = parseNumericValue(
        requestItem?.amount ?? requestItem?.Amount,
      );
      const count = parseNumericValue(requestItem?.count ?? requestItem?.Count);

      if (isIssuanceRequest(typeName)) {
        currentEntry.issuanceAmount += amount;
        currentEntry.issuanceCount += count;
      } else if (isRedemptionRequest(typeName)) {
        currentEntry.redemptionAmount += amount;
        currentEntry.redemptionCount += count;
      } else {
        continue;
      }

      requestsByFund.set(fundName, currentEntry);
    }

    const requestRows = Array.from(requestsByFund.values()).sort(
      (left, right) =>
        right.issuanceAmount +
        right.redemptionAmount -
        (left.issuanceAmount + left.redemptionAmount),
    );

    res.send({
      message: "DONE !",
      range: normalizedRange,
      meta: {
        fromDate,
        toDate,
      },
      balanceChart: {
        source: "TurnoverAccount",
        labels: balanceDateKeys.map(formatDateLabel),
        rawDates: balanceDateKeys,
        datasets: balanceDatasets,
      },
      requestChart: {
        source: "Request",
        labels: requestRows.map((item) => item.fundName),
        issuanceAmounts: requestRows.map((item) => item.issuanceAmount),
        redemptionAmounts: requestRows.map((item) => item.redemptionAmount),
        issuanceCounts: requestRows.map((item) => item.issuanceCount),
        redemptionCounts: requestRows.map((item) => item.redemptionCount),
      },
    });
  } catch (error) {
    const statusCode = error.response?.status || 400;
    res.status(statusCode).send({
      message: extractErrorMessage(
        error,
        "دریافت نمودارهای داشبورد با خطا مواجه شد.",
      ),
    });
  }
};

export const UPDATE_REINVEST = async (req, res) => {
  try {
    const nationalCode = req.identifier;
    const { reInvestPercent, FundSeoRegisterNo } = req.body || {};
    const normalizedFundSeoRegisterNo = String(FundSeoRegisterNo || "").trim();

    if (!nationalCode) {
      return res.status(400).send({
        message:
          "شناسه هویتی کاربر برای به‌روزرسانی درصد سرمایه‌گذاری مجدد یافت نشد.",
      });
    }

    if (!normalizedFundSeoRegisterNo) {
      return res.status(400).send({
        message: "شناسه صندوق برای به‌روزرسانی سرمایه‌گذاری مجدد الزامی است.",
      });
    }

    const reinvestPercentNumber = Number(reInvestPercent);

    if (
      !Number.isFinite(reinvestPercentNumber) ||
      reinvestPercentNumber < 0 ||
      reinvestPercentNumber > 100
    ) {
      return res.status(400).send({
        message: "درصد سرمایه‌گذاری مجدد باید عددی بین ۰ تا ۱۰۰ باشد.",
      });
    }

    const beforeProfile =
      await tadbirServices.getInvestmentProfile(nationalCode);
    const beforeFund = findInvestorFundBySeoRegisterNo(
      beforeProfile,
      normalizedFundSeoRegisterNo,
    );
    const beforeSnapshot = buildInvestSnapshot({
      fundSeoRegisterNo: normalizedFundSeoRegisterNo,
      fundName: beforeFund?.fundName || beforeFund?.FundName,
      ...normalizeInvestorFundReinvestMeta(beforeFund),
    });

    const payload = await tadbirServices.updateInvestorReinvest({
      nationalCode,
      reInvestPercent: reinvestPercentNumber,
      FundSeoRegisterNo: normalizedFundSeoRegisterNo,
    });

    const afterProfile =
      await tadbirServices.getInvestmentProfile(nationalCode);
    const afterFund = findInvestorFundBySeoRegisterNo(
      afterProfile,
      normalizedFundSeoRegisterNo,
    );
    const afterSnapshot = buildInvestSnapshot({
      fundSeoRegisterNo: normalizedFundSeoRegisterNo,
      fundName:
        afterFund?.fundName || afterFund?.FundName || beforeSnapshot?.fundName,
      ...normalizeInvestorFundReinvestMeta(afterFund),
      reInvest:
        afterFund?.reInvest ?? afterFund?.ReInvest ?? reinvestPercentNumber > 0,
      reInvestPercent:
        afterFund?.reInvestPercent ??
        afterFund?.ReInvestPercent ??
        reinvestPercentNumber,
    });

    await createInvestHistoryEntry({
      investor: req.user?._id,
      fundSeoRegisterNo: normalizedFundSeoRegisterNo,
      fundName: afterSnapshot.fundName || beforeSnapshot.fundName,
      actorUser: req.user,
      source: "reinvest_modal",
      beforeSnapshot,
      afterSnapshot,
      notes:
        reinvestPercentNumber === 0
          ? "سرمایه‌گذاری مجدد غیرفعال شد."
          : `درصد سرمایه‌گذاری مجدد به ${reinvestPercentNumber} تنظیم شد.`,
    });

    res.send({
      message: "DONE !",
      payload,
      reInvest: afterSnapshot.reInvest,
      reInvestPercent: afterSnapshot.reInvestPercent,
    });
  } catch (error) {
    const statusCode = error.response?.status || 400;
    res.status(statusCode).send({
      message: extractErrorMessage(
        error,
        "به‌روزرسانی درصد سرمایه‌گذاری مجدد با خطا مواجه شد.",
      ),
    });
  }
};

export const REINVEST_HISTORY = async (req, res) => {
  try {
    const fundSeoRegisterNo = String(req.query.fundSeoRegisterNo || "").trim();

    if (!req.user?._id || !fundSeoRegisterNo) {
      return res.status(400).send({
        message:
          "شناسه صندوق برای دریافت تاریخچه سرمایه‌گذاری مجدد الزامی است.",
      });
    }

    const result = await getInvestHistory(req.user._id, fundSeoRegisterNo, {
      page: req.query.page,
      pageSize: req.query.pageSize,
      sort: req.query.sort,
    });

    return res.status(200).send(result);
  } catch (error) {
    const statusCode = error.response?.status || 400;
    res.status(statusCode).send({
      message: extractErrorMessage(
        error,
        "دریافت تاریخچه سرمایه‌گذاری مجدد با خطا مواجه شد.",
      ),
    });
  }
};

export const REINVEST_HISTORY_EMBEDDED = async (req, res) => {
  try {
    const fundSeoRegisterNo = String(req.query.fundSeoRegisterNo || "").trim();

    if (!req.user?._id || !fundSeoRegisterNo) {
      return res.status(400).send({
        message:
          "شناسه صندوق برای دریافت تاریخچه فشرده سرمایه‌گذاری مجدد الزامی است.",
      });
    }

    const result = await getEmbeddedInvestHistory(
      req.user._id,
      fundSeoRegisterNo,
    );

    return res.status(200).send(result);
  } catch (error) {
    const statusCode = error.response?.status || 400;
    res.status(statusCode).send({
      message: extractErrorMessage(
        error,
        "دریافت تاریخچه فشرده سرمایه‌گذاری مجدد با خطا مواجه شد.",
      ),
    });
  }
};
