import mongoose from "mongoose";

import models from "$models/index.js";

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

export const isCardExpired = (card, now = new Date()) => {
  if (!card?.expiresAt) {
    return true;
  }

  return new Date(card.expiresAt).getTime() < now.getTime();
};

export const generateUniqueCardNumbers = async (quantity) => {
  const required = Math.max(1, Number(quantity || 0));
  const generated = new Set();

  while (generated.size < required) {
    generated.add(createRandomCardNumber());
  }

  const candidateList = Array.from(generated);
  const existingCards = await models.paymentCard
    .find({
      cardNumber: { $in: candidateList },
    })
    .select("cardNumber")
    .lean();

  const existingNumbers = new Set(
    existingCards.map((card) => String(card.cardNumber || "")),
  );

  const uniqueNumbers = candidateList.filter(
    (cardNumber) => !existingNumbers.has(cardNumber),
  );

  if (uniqueNumbers.length >= required) {
    return uniqueNumbers.slice(0, required);
  }

  const extraNumbers = await generateUniqueCardNumbers(
    required - uniqueNumbers.length,
  );
  return [...uniqueNumbers, ...extraNumbers];
};

export const syncPaymentBatchStatus = async (batchId) => {
  if (!batchId) {
    return null;
  }

  const objectId =
    typeof batchId === "string" ? new mongoose.Types.ObjectId(batchId) : batchId;

  const [batch, stats] = await Promise.all([
    models.paymentBatchCard.findById(objectId),
    models.paymentCard.aggregate([
      {
        $match: {
          batchId: objectId,
        },
      },
      {
        $group: {
          _id: "$status",
          count: { $sum: 1 },
        },
      },
    ]),
  ]);

  if (!batch) {
    return null;
  }

  const counters = stats.reduce(
    (acc, item) => ({
      ...acc,
      [item._id]: item.count,
    }),
    {},
  );

  const total = Number(batch.quantity || 0);
  const activeCount = Number(counters.active || 0);
  const usedCount = Number(counters.used || 0);
  const cancelledCount = Number(counters.cancelled || 0);

  let nextStatus = "active";
  let cancelledAt = undefined;

  if (usedCount >= total && total > 0) {
    nextStatus = "used";
  } else if (cancelledCount >= total && total > 0) {
    nextStatus = "cancelled";
    cancelledAt = batch.cancelledAt || new Date();
  } else if (usedCount > 0) {
    nextStatus = "partially_used";
  } else if (activeCount === 0 && cancelledCount > 0) {
    nextStatus = "cancelled";
    cancelledAt = batch.cancelledAt || new Date();
  }

  const update = {
    $set: {
      status: nextStatus,
    },
  };

  if (cancelledAt) {
    update.$set.cancelledAt = cancelledAt;
  } else {
    update.$unset = { cancelledAt: 1 };
  }

  return models.paymentBatchCard.findByIdAndUpdate(objectId, update, {
    new: true,
  });
};

export const buildBatchExportCsv = ({ batch, cards = [] }) => {
  const rows = [
    [
      "شماره کارت",
      "مبلغ",
      "وضعیت",
      "تاریخ انقضا",
      "تاریخ استفاده",
      "عنوان بسته",
      "توضیحات",
    ],
    ...cards.map((card) => [
      card.cardNumber,
      String(card.amount || 0),
      card.status || "",
      formatDateTime(card.expiresAt),
      formatDateTime(card.usedAt),
      batch?.title || "",
      batch?.description || "",
    ]),
  ];

  return `\uFEFF${rows.map((row) => row.map(escapeCsvCell).join(",")).join("\n")}`;
};

const createRandomCardNumber = () => {
  const prefix = "6063";
  const body = Array.from({ length: 11 }, () =>
    String(Math.floor(Math.random() * 10)),
  ).join("");
  const partial = `${prefix}${body}`;
  return `${partial}${calculateLuhnDigit(partial)}`;
};

const calculateLuhnDigit = (value) => {
  const digits = String(value)
    .split("")
    .map((digit) => Number(digit));

  let sum = 0;
  let shouldDouble = true;

  for (let index = digits.length - 1; index >= 0; index -= 1) {
    let digit = digits[index];
    if (shouldDouble) {
      digit *= 2;
      if (digit > 9) {
        digit -= 9;
      }
    }
    sum += digit;
    shouldDouble = !shouldDouble;
  }

  return (10 - (sum % 10)) % 10;
};

const escapeCsvCell = (value) =>
  `"${String(value ?? "").replace(/"/g, '""')}"`;

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

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

  return date.toISOString();
};
