import { redisConnection } from "$connections/index.js";
import { servicesConfig } from "$config/index.js";
import { tadbirClient } from "$clients/index.js";
import { certificateHelpers, identityHelpers } from "$helpers/index.js";
import models from "$models/index.js";
import { mergePublicStaticFunds } from "../../data/publicStaticFunds.js";

const TADBIR_ACCESS_TOKEN_KEY = "tadbir:access_token";
const TADBIR_ACCESS_TOKEN_TTL = 50 * 60; // 50 minutes
const DEFAULT_REPORT_PAGE_SIZE = 20;
export const TADBIR_INTERMEDIARY_SEJAM_FUND_LIST = [
  "11141",
  "11343",
  "11383",
  "11222",
  "11197",
  "11339",
];

const buildAuthHeaders = (accessToken) => ({
  Authorization: `Bearer ${accessToken}`,
  "X-Tenant-ID": servicesConfig.tadbirConfig.key,
});

const getCurrentIsoDate = () => new Date().toISOString();

const normalizePaginatedResult = ({
  items,
  page,
  pageSize,
  totalData,
  fallbackKey,
}) => {
  const normalizedItems = Array.isArray(items)
    ? items
    : Array.isArray(items?.items)
      ? items.items
      : [];

  const resolvedTotalData =
    Number(totalData) ||
    Number(items?.totalItemCount) ||
    Number(items?.totalData) ||
    normalizedItems.length;
  const resolvedPageSize = Number(pageSize) || DEFAULT_REPORT_PAGE_SIZE;
  const resolvedPage = Number(page) || 1;

  return {
    [fallbackKey]: normalizedItems,
    totalData: resolvedTotalData,
    currentPage: resolvedPage,
    totalPage: Math.max(1, Math.ceil(resolvedTotalData / resolvedPageSize)),
  };
};

const normalizeFundsList = (fundsResponse) => {
  if (Array.isArray(fundsResponse)) {
    return fundsResponse;
  }

  if (Array.isArray(fundsResponse?.items)) {
    return fundsResponse.items;
  }

  if (Array.isArray(fundsResponse?.funds)) {
    return fundsResponse.funds;
  }

  if (Array.isArray(fundsResponse?.data)) {
    return fundsResponse.data;
  }

  return [];
};

const filterFundsByRegisterCode = (fundsResponse) => {
  const isAllowedFund = (fund) =>
    TADBIR_INTERMEDIARY_SEJAM_FUND_LIST.includes(
      String(fund?.fundRegisterCode || "").trim(),
    );

  if (Array.isArray(fundsResponse)) {
    return fundsResponse.filter(isAllowedFund);
  }

  for (const key of ["items", "funds", "data"]) {
    if (Array.isArray(fundsResponse?.[key])) {
      return {
        ...fundsResponse,
        [key]: fundsResponse[key].filter(isAllowedFund),
      };
    }
  }

  return fundsResponse;
};

const normalizePageNumber = (pageNumber) => {
  const parsedPageNumber = Number(pageNumber);
  return Number.isFinite(parsedPageNumber) && parsedPageNumber > 0
    ? Math.floor(parsedPageNumber)
    : 1;
};

const normalizePageSize = (pageSize) => {
  const parsedPageSize = Number(pageSize);
  return Number.isFinite(parsedPageSize) && parsedPageSize > 0
    ? Math.floor(parsedPageSize)
    : DEFAULT_REPORT_PAGE_SIZE;
};

const toTimestamp = (value) => {
  const parsedValue = new Date(value || 0).getTime();
  return Number.isNaN(parsedValue) ? 0 : parsedValue;
};

const FUND_ANNUAL_RETURN_OVERRIDES = {
  11343: 34.5,
  11383: 35,
  11222: 38,
};

const applyFundAnnualReturnOverrides = (fundsResponse) => {
  const overrideFund = (fund) => {
    const overrideValue =
      FUND_ANNUAL_RETURN_OVERRIDES[String(fund?.fundRegisterCode)];

    if (overrideValue === undefined) {
      return fund;
    }

    return {
      ...fund,
      annualReturn: overrideValue,
    };
  };

  if (Array.isArray(fundsResponse)) {
    return fundsResponse.map(overrideFund);
  }

  if (Array.isArray(fundsResponse?.items)) {
    return {
      ...fundsResponse,
      items: fundsResponse.items.map(overrideFund),
    };
  }

  if (Array.isArray(fundsResponse?.funds)) {
    return {
      ...fundsResponse,
      funds: fundsResponse.funds.map(overrideFund),
    };
  }

  if (Array.isArray(fundsResponse?.data)) {
    return {
      ...fundsResponse,
      data: fundsResponse.data.map(overrideFund),
    };
  }

  return fundsResponse;
};

const enrichBalancesWithFundInfo = (balances, funds) => {
  const fundItems = funds?.items || [];
  const fundsByRegisterCode = new Map(
    fundItems.map((fund) => [String(fund.fundRegisterCode), fund]),
  );

  return balances.map((balance) => {
    const fund = fundsByRegisterCode.get(String(balance.fundSeoRegisterNo));

    return {
      ...balance,
      fundName: fund?.fundName,
      fundTypeTitle: fund?.fundTypeTitle,
    };
  });
};

export const getAccessToken = async () => {
  try {
    const cachedToken = await redisConnection.get(TADBIR_ACCESS_TOKEN_KEY);
    if (cachedToken) {
      console.log("✅ Using cached tadbir access token");
      return { accessToken: cachedToken };
    }

    const response = await tadbirClient.post(
      "/User/signin",
      {
        username: servicesConfig.tadbirConfig.username,
        password: servicesConfig.tadbirConfig.password,
      },
      {
        headers: {
          "X-Tenant-ID": servicesConfig.tadbirConfig.key,
        },
      },
    );

    const accessToken = response?.data?.accessToken;

    if (!accessToken) {
      throw new Error("No access token received from tadbir");
    }

    await redisConnection.setex(
      TADBIR_ACCESS_TOKEN_KEY,
      TADBIR_ACCESS_TOKEN_TTL,
      accessToken,
    );

    return response.data;
  } catch (error) {
    throw error;
  }
};

export const getFunds = async () => {
  try {
    const tokenResponse = await getAccessToken();

    const response = await tadbirClient.get("/Fund", {
      headers: {
        Authorization: `Bearer ${tokenResponse.accessToken}`,
      },
    });

    return mergePublicStaticFunds(
      applyFundAnnualReturnOverrides(filterFundsByRegisterCode(response.data)),
    );
  } catch (error) {
    throw error;
  }
};

export const tadbirCreateUser = async (userOrId) => {
  try {
    const userId = userOrId?._id || userOrId;
    const user =
      userOrId?._id && identityHelpers.getUserIdentifier(userOrId)
        ? userOrId
        : await models.user.findById(userId).lean();
    const identifier = identityHelpers.getUserIdentifier(user);
    const identityQuery = identityHelpers.getUserIdentityQuery(user);

    if (!user?._id || !identifier || !identityQuery) {
      const error = new Error("اطلاعات کاربر برای ثبت در تدبیر کامل نیست.");
      error.status = 422;
      throw error;
    }

    const sejamRecord = await models.sejam
      .findOne({
        $or: [{ user: user._id }, identityQuery],
      })
      .lean();

    if (!sejamRecord?.data || typeof sejamRecord.data !== "object") {
      const error = new Error(
        "برای تکمیل ثبت‌نام در تدبیر، ابتدا باید اطلاعات سجام شما همگام‌سازی شود.",
      );
      error.status = 409;
      error.code = "SEJAM_RECORD_REQUIRED";
      throw error;
    }

    const tokenResponse = await getAccessToken();
    const payload = {
      FundSeoRegisterNoList: TADBIR_INTERMEDIARY_SEJAM_FUND_LIST,
      SejamData: {
        data: sejamRecord.data,
        meta: null,
        error: null,
      },
    };

    await tadbirClient.post("/Investor/IntermediarySejam", payload, {
      headers: buildAuthHeaders(tokenResponse.accessToken),
    });

    await models.user.findByIdAndUpdate(user._id, {
      $set: { isTadbirVerified: true },
    });

    return "OK";
  } catch (error) {
    if (error?.code === "SEJAM_RECORD_REQUIRED") {
      throw error;
    }

    const message =
      error?.response?.data?.errorMessage ||
      error?.response?.data?.message ||
      error?.message ||
      "ثبت کاربر در تدبیر ناموفق بود.";
    const normalizedError = new Error(message);
    normalizedError.status = error?.response?.status || error?.status || 422;
    normalizedError.code = error?.code || "TADBIR_VERIFICATION_FAILED";
    throw normalizedError;
  }
};

export const getBalances = async (nationalId) => {
  try {
    const tokenResponse = await getAccessToken();

    const [balancesResponse, fundsResponse] = await Promise.all([
      tadbirClient.get(`/Fund/investor/${nationalId}/investment-profile`, {
        headers: {
          Authorization: `Bearer ${tokenResponse.accessToken}`,
          "X-Tenant-ID": servicesConfig.tadbirConfig.key,
        },
      }),
      tadbirClient.get("/Fund", {
        headers: {
          Authorization: `Bearer ${tokenResponse.accessToken}`,
        },
      }),
    ]);

    return enrichBalancesWithFundInfo(balancesResponse.data, fundsResponse);
  } catch (error) {
    throw error;
  }
};

export const getInvestmentProfile = async (nationalId) => {
  try {
    const tokenResponse = await getAccessToken();

    const response = await tadbirClient.get(
      `/Fund/investor/${nationalId}/investment-profile`,
      {
        headers: {
          Authorization: `Bearer ${tokenResponse.accessToken}`,
          "X-Tenant-ID": servicesConfig.tadbirConfig.key,
        },
      },
    );

    return response.data;
  } catch (error) {
    throw error;
  }
};

export const getInvestorFunds = async (nationalCode) => {
  try {
    const tokenResponse = await getAccessToken();

    const response = await tadbirClient.post(
      `/Fund/investor/${nationalCode}`,
      {},
      {
        headers: buildAuthHeaders(tokenResponse.accessToken),
      },
    );

    return Array.isArray(response.data) ? response.data : [];
  } catch (error) {
    throw error;
  }
};

export const getExternalRequests = async ({
  nationalCode,
  pageNumber = 1,
  pageSize = DEFAULT_REPORT_PAGE_SIZE,
  fromDate,
  toDate,
  fundSeoRegisterNoList = [],
  type = [],
  status = [],
  sortFields = {},
  BankBillCode,
} = {}) => {
  try {
    const tokenResponse = await getAccessToken();
    const normalizedFundSeoRegisterNoList = (
      Array.isArray(fundSeoRegisterNoList)
        ? fundSeoRegisterNoList
        : [fundSeoRegisterNoList]
    )
      .map((value) => Number(value))
      .filter((value) => Number.isFinite(value) && value > 0);

    const payload = {
      pageNumber: String(pageNumber),
      pageSize: String(pageSize),
      type,
      status,
      sortFields,
      nationalCode,
    };
    if (fromDate) payload.fromDate = fromDate;
    if (toDate) {
      payload.toDate = toDate;
    } else {
      payload.toDate = getCurrentIsoDate();
    }
    if (normalizedFundSeoRegisterNoList.length > 0) {
      payload.fundSeoRegisterNoList = normalizedFundSeoRegisterNoList;
    }
    if (BankBillCode) payload.BankBillCode = BankBillCode;

    const response = await tadbirClient.post("/Request", payload, {
      headers: { Authorization: `Bearer ${tokenResponse.accessToken}` },
    });
    return normalizePaginatedResult({
      items: response.data,
      page: pageNumber,
      pageSize,
      fallbackKey: "externalRequests",
    });
  } catch (error) {
    throw error;
  }
};

export const createIssuanceRequest = async ({ fundSeoRegisterNo, payload }) => {
  try {
    const tokenResponse = await getAccessToken();
    const response = await tadbirClient.post(
      `/fund/${fundSeoRegisterNo}/Request/issuance`,
      payload,
      {
        headers: buildAuthHeaders(tokenResponse.accessToken),
      },
    );

    return response.data;
  } catch (error) {
    throw error;
  }
};

export const createRedemptionRequest = async ({
  fundSeoRegisterNo,
  payload,
}) => {
  try {
    const tokenResponse = await getAccessToken();
    const response = await tadbirClient.post(
      `/fund/${fundSeoRegisterNo}/Request/redemption`,
      payload,
      {
        headers: buildAuthHeaders(tokenResponse.accessToken),
      },
    );

    return response.data;
  } catch (error) {
    throw error;
  }
};

export const getRequestById = async ({ fundSeoRegisterNo, requestId }) => {
  try {
    const tokenResponse = await getAccessToken();
    const response = await tadbirClient.get(
      `/Fund/${fundSeoRegisterNo}/Request/${requestId}`,
      {
        headers: buildAuthHeaders(tokenResponse.accessToken),
      },
    );

    return response.data;
  } catch (error) {
    throw error;
  }
};

export const getIssuanceReceiptData = async ({
  fundSeoRegisterNo,
  requestId,
}) => {
  try {
    const tokenResponse = await getAccessToken();
    const response = await tadbirClient.get(
      `/Fund/${fundSeoRegisterNo}/Request/${requestId}/IssuanceReceiptData`,
      {
        headers: buildAuthHeaders(tokenResponse.accessToken),
      },
    );

    return response.data;
  } catch (error) {
    throw error;
  }
};

export const getRedemptionReceiptData = async ({
  fundSeoRegisterNo,
  requestId,
}) => {
  try {
    const tokenResponse = await getAccessToken();
    const response = await tadbirClient.get(
      `/Fund/${fundSeoRegisterNo}/Request/${requestId}/RedemptionReceiptData`,
      {
        headers: buildAuthHeaders(tokenResponse.accessToken),
      },
    );

    return response.data;
  } catch (error) {
    throw error;
  }
};

export const getCertificateEvidenceData = async ({
  fundSeoRegisterNo,
  nationalCode,
}) => {
  try {
    const tokenResponse = await getAccessToken();
    const response = await tadbirClient.get(
      `/Fund/${fundSeoRegisterNo}/Request/${nationalCode}/EvidenceData`,
      {
        headers: buildAuthHeaders(tokenResponse.accessToken),
      },
    );

    return response.data;
  } catch (error) {
    throw error;
  }
};

export const getCertificateEvidence = async ({
  fundSeoRegisterNo,
  requestId,
}) => {
  try {
    const tokenResponse = await getAccessToken();
    const response = await tadbirClient.get(
      `/Fund/${fundSeoRegisterNo}/Request/${requestId}/Evidence`,
      {
        headers: buildAuthHeaders(tokenResponse.accessToken),
        responseType: "arraybuffer",
      },
    );

    return {
      data: response.data,
      headers: response.headers,
      status: response.status,
    };
  } catch (error) {
    throw error;
  }
};

export const generateCertificate = async ({
  fundSeoRegisterNo,
  nationalCode,
  certificateTrackId,
  createdAt,
}) => {
  try {
    const [evidenceData, fundsResponse] = await Promise.all([
      getCertificateEvidenceData({
        fundSeoRegisterNo,
        nationalCode,
      }),
      getFunds(),
    ]);

    const normalizedEvidenceData =
      certificateHelpers.normalizeCertificateEvidenceData(evidenceData || {});
    const funds = normalizeFundsList(fundsResponse);

    const fundMeta = funds.find((item) => {
      const candidates = [
        item?.fundRegisterCode,
        item?.seoRegisterNo,
        item?.nav?.seoRegisterNo,
        item?.fundSeoRegisterNo,
      ]
        .map((value) => String(value || "").trim())
        .filter(Boolean);

      return (
        candidates.includes(String(fundSeoRegisterNo || "").trim()) ||
        candidates.includes(
          String(normalizedEvidenceData.fundRegisterNO || "").trim(),
        )
      );
    });

    const resolvedCertificate = {
      ...normalizedEvidenceData,
      fundRegisterNO:
        normalizedEvidenceData.fundRegisterNO ||
        String(fundSeoRegisterNo || "").trim(),
      fundTitle: normalizedEvidenceData.fundTitle || fundMeta?.fundName || "",
      createdAt,
    };

    const normalizedCount = Number(
      String(resolvedCertificate.count || "0")
        .replaceAll(",", "")
        .trim(),
    );

    if (!Number.isFinite(normalizedCount) || normalizedCount <= 0) {
      const error = new Error(
        "شما در این صندوق هیچ تعدادی ندارید و امکان تولید فایل گواهی‌نامه وجود ندارد.",
      );
      error.status = 400;
      throw error;
    }

    const { pdfBytes, fileName, renderedText } =
      await certificateHelpers.generateCertificatePdf({
        certificate: resolvedCertificate,
        certificateTrackId,
      });

    const resolvedCertificateTrackId =
      certificateTrackId ||
      resolvedCertificate.serialNumber ||
      resolvedCertificate.nationalCode ||
      nationalCode;

    return {
      certificateTrackId: String(resolvedCertificateTrackId),
      payload: resolvedCertificate,
      evidenceData: resolvedCertificate,
      renderedText,
      downloadMeta: {
        fileName,
        contentType: "application/pdf",
        bodyBase64: Buffer.from(pdfBytes).toString("base64"),
      },
      fundMeta,
    };
  } catch (error) {
    throw error;
  }
};

export const getInvestorCardex = async ({
  fundSeoRegisterNo,
  nationalCode,
}) => {
  try {
    const tokenResponse = await getAccessToken();
    const response = await tadbirClient.get(
      `/Fund/${fundSeoRegisterNo}/Investor/${nationalCode}/Cardex`,
      {
        headers: buildAuthHeaders(tokenResponse.accessToken),
      },
    );

    return response.data;
  } catch (error) {
    throw error;
  }
};

export const getTurnoverAccount = async ({
  fundSeoRegisterNo,
  nationalCode,
  fromDate,
  toDate,
  pageNumber = 1,
  pageSize = DEFAULT_REPORT_PAGE_SIZE,
  basketId,
  sortField,
  sortDirection = "asc",
}) => {
  try {
    const tokenResponse = await getAccessToken();
    const normalizedRequestedPage = normalizePageNumber(pageNumber);
    const normalizedPageSize = normalizePageSize(pageSize);
    const shouldSortByVoucherDate =
      sortField === "voucherDate" &&
      String(sortDirection).toLowerCase() === "desc";

    const fetchTurnoverPage = async (targetPageNumber) => {
      const queryParams = new URLSearchParams({
        pageNumber: String(targetPageNumber),
        pageSize: String(normalizedPageSize),
      });

      if (fromDate) {
        queryParams.set("fromDate", fromDate);
      }

      if (toDate) {
        queryParams.set("toDate", toDate);
      }

      if (basketId !== undefined && basketId !== null && basketId !== "") {
        queryParams.set("basketId", String(basketId));
      }

      const response = await tadbirClient.get(
        `/Fund/${fundSeoRegisterNo}/Investor/${nationalCode}/TurnoverAccount?${queryParams.toString()}`,
        {
          headers: buildAuthHeaders(tokenResponse.accessToken),
        },
      );

      return normalizePaginatedResult({
        items: response.data,
        page: targetPageNumber,
        pageSize: normalizedPageSize,
        fallbackKey: "turnoverAccount",
      });
    };

    if (!shouldSortByVoucherDate) {
      return fetchTurnoverPage(normalizedRequestedPage);
    }

    const firstPageResponse = await fetchTurnoverPage(1);
    const totalData = Number(firstPageResponse?.totalData) || 0;
    const totalPage = Math.max(Number(firstPageResponse?.totalPage) || 1, 1);
    const requestedStartIndex =
      (normalizedRequestedPage - 1) * normalizedPageSize;

    if (requestedStartIndex >= totalData) {
      return {
        ...firstPageResponse,
        turnoverAccount: [],
        currentPage: normalizedRequestedPage,
        totalData,
        totalPage,
      };
    }

    const requestedEndIndex = Math.min(
      requestedStartIndex + normalizedPageSize,
      totalData,
    );
    const ascStartIndex = totalData - requestedEndIndex;
    const ascEndIndex = totalData - requestedStartIndex - 1;
    const startPage = Math.floor(ascStartIndex / normalizedPageSize) + 1;
    const endPage = Math.floor(ascEndIndex / normalizedPageSize) + 1;

    const pagesToFetch = Array.from(new Set([startPage, endPage]));
    const pageResponses = await Promise.all(
      pagesToFetch.map(async (targetPageNumber) => ({
        pageNumber: targetPageNumber,
        response:
          targetPageNumber === 1
            ? firstPageResponse
            : await fetchTurnoverPage(targetPageNumber),
      })),
    );

    const combinedRows = pageResponses.flatMap(({ pageNumber, response }) => {
      const pageStartIndex = (pageNumber - 1) * normalizedPageSize;

      return (response?.turnoverAccount || []).map((row, rowIndex) => ({
        row,
        globalIndex: pageStartIndex + rowIndex,
      }));
    });

    const turnoverAccount = combinedRows
      .filter(
        ({ globalIndex }) =>
          globalIndex >= ascStartIndex && globalIndex <= ascEndIndex,
      )
      .sort((left, right) => left.globalIndex - right.globalIndex)
      .map(({ row }) => row)
      .reverse()
      .sort(
        (left, right) =>
          toTimestamp(right?.voucherDate) - toTimestamp(left?.voucherDate),
      );

    return {
      ...firstPageResponse,
      turnoverAccount,
      currentPage: normalizedRequestedPage,
      totalData,
      totalPage,
    };
  } catch (error) {
    throw error;
  }
};

export const updateInvestorReinvest = async ({
  nationalCode,
  reInvestPercent,
  FundSeoRegisterNo,
}) => {
  try {
    const tokenResponse = await getAccessToken();
    const response = await tadbirClient.post(
      `/Investor/${nationalCode}/ReInvest`,
      {
        nationalCode,
        reInvestPercent,
        FundSeoRegisterNo,
        ReInvest: Number(reInvestPercent) > 0,
      },
      {
        headers: buildAuthHeaders(tokenResponse.accessToken),
      },
    );

    return response.data;
  } catch (error) {
    throw error;
  }
};
