import models from "$models/index.js";
import { identityHelpers } from "$helpers/index.js";

const INVEST_HISTORY_EMBED_LIMIT = 10;

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

const isObjectIdLike = (value) =>
  value &&
  typeof value === "object" &&
  (typeof value?.toString === "function" || value?._bsontype === "ObjectID");

const sanitizeHistoryValue = (value) => {
  if (value === undefined) {
    return undefined;
  }

  if (value === null) {
    return null;
  }

  if (value instanceof Date) {
    return value.toISOString();
  }

  if (isObjectIdLike(value)) {
    return value.toString();
  }

  if (Array.isArray(value)) {
    return value.map((item) => sanitizeHistoryValue(item));
  }

  if (typeof value === "object") {
    const plainObject =
      typeof value?.toObject === "function" ? value.toObject() : value;

    return Object.entries(plainObject).reduce((result, [key, currentValue]) => {
      const sanitizedValue = sanitizeHistoryValue(currentValue);
      if (sanitizedValue !== undefined) {
        result[key] = sanitizedValue;
      }
      return result;
    }, {});
  }

  return value;
};

const buildComparableString = (value) => JSON.stringify(sanitizeHistoryValue(value));

const buildChangedFields = (beforeSnapshot = {}, afterSnapshot = {}) => {
  const keys = new Set([
    ...Object.keys(beforeSnapshot || {}),
    ...Object.keys(afterSnapshot || {}),
  ]);

  return Array.from(keys).reduce((changes, key) => {
    const previousValue = beforeSnapshot?.[key];
    const nextValue = afterSnapshot?.[key];

    if (buildComparableString(previousValue) !== buildComparableString(nextValue)) {
      changes[key] = {
        from: previousValue === undefined ? null : previousValue,
        to: nextValue === undefined ? null : nextValue,
      };
    }

    return changes;
  }, {});
};

const populateInvestHistory = (query) =>
  query.populate("actorUser", identityHelpers.USER_IDENTITY_PROJECTION);

export const buildInvestSnapshot = ({
  fundSeoRegisterNo,
  fundName,
  reInvest,
  reInvestPercent,
} = {}) => ({
  fundSeoRegisterNo: normalizeString(fundSeoRegisterNo),
  fundName: normalizeString(fundName),
  reInvest: Boolean(reInvest),
  reInvestPercent: Number.isFinite(Number(reInvestPercent))
    ? Number(reInvestPercent)
    : 0,
});

export const resolveInvestHistoryEventType = ({
  eventType,
  beforeSnapshot,
  afterSnapshot,
} = {}) => {
  if (eventType) {
    return eventType;
  }

  const previousPercent = Number(beforeSnapshot?.reInvestPercent || 0);
  const nextPercent = Number(afterSnapshot?.reInvestPercent || 0);

  if (previousPercent <= 0 && nextPercent > 0) {
    return "activated";
  }

  if (previousPercent > 0 && nextPercent <= 0) {
    return "deactivated";
  }

  return "updated";
};

export const createInvestHistoryEntry = async ({
  investor,
  fundSeoRegisterNo,
  fundName = "",
  actorUser = null,
  actorType = "system",
  source = "tadbir",
  eventType = "updated",
  beforeSnapshot = {},
  afterSnapshot = {},
  notes = "",
} = {}) => {
  const changedFields = buildChangedFields(beforeSnapshot, afterSnapshot);

  if (!investor || !normalizeString(fundSeoRegisterNo)) {
    return null;
  }

  if (!Object.keys(changedFields).length && !notes) {
    return null;
  }

  return models.investHistory.create({
    investor,
    fundSeoRegisterNo: normalizeString(fundSeoRegisterNo),
    fundName: normalizeString(fundName || afterSnapshot?.fundName),
    actorType: actorUser ? "user" : actorType,
    actorUser: actorUser?._id || actorUser || null,
    source,
    eventType: resolveInvestHistoryEventType({
      eventType,
      beforeSnapshot,
      afterSnapshot,
    }),
    changedFields,
    snapshot: sanitizeHistoryValue(afterSnapshot),
    notes,
  });
};

export const getInvestHistory = async (
  investor,
  fundSeoRegisterNo,
  { page = 1, pageSize = 20, sort = "asc" } = {},
) => {
  const normalizedPageSize = Math.max(1, Number(pageSize) || 20);
  const normalizedPage = Math.max(1, Number(page) || 1);
  const skip = normalizedPageSize * (normalizedPage - 1);
  const sortDirection = String(sort).toLowerCase() === "desc" ? -1 : 1;
  const query = {
    investor,
    fundSeoRegisterNo: normalizeString(fundSeoRegisterNo),
  };

  const [history, totalData] = await Promise.all([
    populateInvestHistory(
      models.investHistory
        .find(query)
        .sort({ createdAt: sortDirection, _id: sortDirection })
        .skip(skip)
        .limit(normalizedPageSize),
    ),
    models.investHistory.countDocuments(query),
  ]);

  return {
    history,
    totalData,
    currentPage: normalizedPage,
    totalPage: Math.ceil(totalData / normalizedPageSize) || 0,
  };
};

export const getEmbeddedInvestHistory = async (
  investor,
  fundSeoRegisterNo,
  { limit = INVEST_HISTORY_EMBED_LIMIT } = {},
) => {
  const normalizedLimit = Math.max(1, Number(limit) || INVEST_HISTORY_EMBED_LIMIT);
  const query = {
    investor,
    fundSeoRegisterNo: normalizeString(fundSeoRegisterNo),
  };

  const [history, totalData] = await Promise.all([
    populateInvestHistory(
      models.investHistory
        .find(query)
        .sort({ createdAt: -1, _id: -1 })
        .limit(normalizedLimit),
    ),
    models.investHistory.countDocuments(query),
  ]);

  return {
    history,
    historyCount: totalData,
  };
};
