import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";

import moment from "moment-jalaali";

import { mongoConnection } from "$connections/index.js";
import { paymentCardHelpers } from "$helpers/index.js";
import models from "$models/index.js";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const DEFAULT_PAYMENT_CARDS_INPUT_PATH = path.resolve(
  __dirname,
  "../documents/payment-cards.json",
);
const DEFAULT_OWNER_NATIONAL_ID = "0019431171";
moment.loadPersian({ dialect: "persian-modern" });

const parseArgs = (argv = process.argv.slice(2)) => {
  const options = {
    inputPath: DEFAULT_PAYMENT_CARDS_INPUT_PATH,
    ownerNationalId: DEFAULT_OWNER_NATIONAL_ID,
    ownerUserId: "",
    fundName: process.env.PAYMENT_CARD_FUND_NAME || "",
  };

  argv.forEach((arg) => {
    const [flag, ...rawParts] = arg.split("=");
    const rawValue = rawParts.join("=");

    if (!rawValue) {
      return;
    }

    if (flag === "--input") {
      options.inputPath = rawValue;
      return;
    }

    if (flag === "--owner-national-id") {
      options.ownerNationalId = rawValue;
      return;
    }

    if (flag === "--owner-user-id") {
      options.ownerUserId = rawValue;
      return;
    }

    if (flag === "--fund-name") {
      options.fundName = rawValue;
    }
  });

  return options;
};

const formatErrorDetails = (error) => {
  if (!error) {
    return "Unknown error";
  }

  if (error.stack) {
    return error.stack;
  }

  if (error.message) {
    return error.message;
  }

  return String(error);
};

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

const parseJalaliDateToUtc = (value) => {
  const normalizedValue = String(value || "").trim();
  if (!normalizedValue) {
    return null;
  }

  const parsed = moment(normalizedValue, "jYYYY/jMM/jDD", true);
  if (!parsed.isValid()) {
    return null;
  }

  return parsed.endOf("day").toDate();
};

const readPaymentCardsInput = (filePath) => {
  if (!fs.existsSync(filePath)) {
    throw new Error(`Payment cards input file was not found: ${filePath}`);
  }

  const rawContent = fs.readFileSync(filePath, "utf8").replace(/^\uFEFF/, "");
  let parsed;

  try {
    parsed = JSON.parse(rawContent);
  } catch (error) {
    throw new Error(
      `Payment cards input must be a valid JSON array: ${filePath}`,
    );
  }

  if (!Array.isArray(parsed)) {
    throw new Error("Payment cards input must be exactly an array of objects");
  }

  return parsed;
};

const normalizePaymentCards = (
  items = [],
  fallbackFundName = "",
) =>
  items.map((item, index) => {
    const cardNumber = normalizeCardNumber(item?.cardNumber);
    const amount = Number(String(item?.amount || "").replace(/[^\d]/g, ""));
    const fundSeoRegisterNo = String(item?.fundSeoRegisterNo || "").trim();
    const expiresAt = parseJalaliDateToUtc(item?.expiresAt);
    const description = String(item?.description || "").trim();
    const fundName =
      String(
        item?.fundName ||
          fallbackFundName ||
          "",
      ).trim();

    if (!cardNumber || cardNumber.length < 8) {
      throw new Error(`Invalid cardNumber at item ${index + 1}`);
    }

    if (!amount) {
      throw new Error(`Invalid amount at item ${index + 1}`);
    }

    if (!fundSeoRegisterNo) {
      throw new Error(`Invalid fundSeoRegisterNo at item ${index + 1}`);
    }

    if (!expiresAt) {
      throw new Error(`Invalid expiresAt at item ${index + 1}`);
    }

    if (!description) {
      throw new Error(`Invalid description at item ${index + 1}`);
    }

    if (!fundName) {
      throw new Error(
        `Fund name could not be resolved for fundSeoRegisterNo "${fundSeoRegisterNo}" at item ${index + 1}`,
      );
    }

    return {
      cardNumber,
      amount,
      fundSeoRegisterNo,
      fundName,
      expiresAt,
      description,
      status: "active",
      usedAt: null,
    };
  });

const buildPaymentBatchSeedData = (cards = []) => {
  const groupedBatches = new Map();

  for (const card of cards) {
    const key = [
      card.description,
      card.amount,
      card.fundSeoRegisterNo,
      card.fundName,
      card.expiresAt.toISOString(),
    ].join("|");

    if (!groupedBatches.has(key)) {
      groupedBatches.set(key, {
        title: card.description,
        description: card.description,
        createMode: "batch",
        amount: card.amount,
        fundSeoRegisterNo: card.fundSeoRegisterNo,
        fundName: card.fundName,
        expiresAt: card.expiresAt,
        cards: [],
      });
    }

    groupedBatches.get(key).cards.push(card);
  }

  return Array.from(groupedBatches.values()).map((batch) => ({
    ...batch,
    quantity: batch.cards.length,
    totalAmount: batch.amount * batch.cards.length,
  }));
};

const resolveOwnerUser = async ({ ownerUserId, ownerNationalId }) => {
  if (ownerUserId) {
    const user = await models.user.findById(ownerUserId).lean();
    if (!user) {
      throw new Error(`Owner user was not found for id: ${ownerUserId}`);
    }

    return user;
  }

  const user = await models.user
    .findOne({ nationalId: ownerNationalId })
    .lean();
  if (!user) {
    throw new Error(
      `Owner user was not found for nationalId: ${ownerNationalId}`,
    );
  }

  return user;
};

const filterAppendOnlyCards = async (cards = []) => {
  if (!cards.length) {
    return {
      newCards: [],
      skippedCards: [],
    };
  }

  const existingCards = await models.paymentCard
    .find({
      cardNumber: {
        $in: cards.map((card) => card.cardNumber),
      },
    })
    .select("cardNumber")
    .lean();

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

  const newCards = [];
  const skippedCards = [];

  for (const card of cards) {
    if (existingCardNumbers.has(card.cardNumber)) {
      skippedCards.push(card);
      continue;
    }

    newCards.push(card);
  }

  return {
    newCards,
    skippedCards,
  };
};

async function seedPaymentCards({
  inputPath = DEFAULT_PAYMENT_CARDS_INPUT_PATH,
  ownerNationalId = DEFAULT_OWNER_NATIONAL_ID,
  ownerUserId = "",
  fundName = "",
} = {}) {
  console.log("🎁 PAYMENT CARD SEED");
  console.log(`📄 Input: ${inputPath}`);

  const ownerUser = await resolveOwnerUser({ ownerUserId, ownerNationalId });
  console.log(
    `👤 Owner: ${ownerUser.fullName || "-"} (${ownerUser.nationalId || ownerUser._id})`,
  );

  const inputItems = readPaymentCardsInput(inputPath);
  console.log(`📥 Loaded ${inputItems.length} payment card input rows`);
  console.log("✅ Using local input only, no fund lookup step");

  const normalizedCards = normalizePaymentCards(
    inputItems,
    fundName,
  );
  console.log(`✅ Normalized ${normalizedCards.length} payment card rows`);
  const { newCards, skippedCards } = await filterAppendOnlyCards(normalizedCards);

  console.log(
    `🔎 Append check complete: ${newCards.length} new, ${skippedCards.length} existing`,
  );

  if (!newCards.length) {
    console.log("✅ No new payment cards to append");
    return {
      batches: 0,
      cards: 0,
      skipped: skippedCards.length,
    };
  }

  const batches = buildPaymentBatchSeedData(newCards);
  let createdBatchCount = 0;
  let createdCardCount = 0;

  for (const batchSeed of batches) {
    const batch = await models.paymentBatchCard.create({
      title: batchSeed.title,
      description: batchSeed.description,
      createMode: batchSeed.createMode,
      quantity: batchSeed.quantity,
      amount: batchSeed.amount,
      totalAmount: batchSeed.totalAmount,
      status: "active",
      user: ownerUser._id,
      expiresAt: batchSeed.expiresAt,
    });

    await models.paymentCard.insertMany(
      batchSeed.cards.map((card) => ({
        batchId: batch._id,
        cardNumber: card.cardNumber,
        amount: card.amount,
        fundSeoRegisterNo: card.fundSeoRegisterNo,
        fundName: card.fundName,
        status: card.status,
        usedAt: card.usedAt,
        expiresAt: card.expiresAt,
      })),
    );

    await paymentCardHelpers.syncPaymentBatchStatus(batch._id);

    createdBatchCount += 1;
    createdCardCount += batchSeed.cards.length;
    console.log(
      `   ✅ Batch "${batch.title}" created with ${batchSeed.cards.length} cards`,
    );
  }

  console.log(
    `✅ Appended ${createdBatchCount} payment batches and ${createdCardCount} payment cards`,
  );

  if (skippedCards.length) {
    console.log(`⚠️ Skipped ${skippedCards.length} existing cards`);
  }

  return {
    batches: createdBatchCount,
    cards: createdCardCount,
    skipped: skippedCards.length,
  };
}

async function run() {
  const options = parseArgs();

  return new Promise((resolve, reject) => {
    mongoConnection.once("open", async () => {
      try {
        console.log("✅ Connected to database successfully\n");
        const startedAt = Date.now();
        const result = await seedPaymentCards(options);
        const duration = ((Date.now() - startedAt) / 1000).toFixed(2);

        console.log(`⏱️  Duration: ${duration}s`);
        console.log(`📦 Batches: ${result.batches}`);
        console.log(`💳 Cards: ${result.cards}`);
        console.log(`⏭️ Skipped: ${result.skipped}`);
        process.exit(0);
      } catch (error) {
        console.error("\n❌ Payment card seed failed:");
        console.error(formatErrorDetails(error));
        process.exit(1);
      }
    });

    mongoConnection.once("error", (error) => {
      console.error("❌ Database connection error:");
      console.error(formatErrorDetails(error));
      process.exit(1);
    });
  });
}

run();
