import db from "../models/index.js";
import WebhookService from "../services/webhookService.js";

/**
 * REUSABLE STALE BALANCE RECONCILIATION SCRIPT.
 *
 * For payouts confirmed via 9PSB bank statement that were never actually debited locally
 * because of the since-fixed ambiguous-outcome bug (the old payout code rolled the attempt
 * back — silently erasing the record — on anything less than an instant "SUCCESS", even
 * though the money had already left via 9PSB). Pass in the specific case each time.
 *
 * Does NOT credit the merchant's wallet — this money left the platform to the recipient's
 * bank account, exactly like a normal successful payout, so only the customer's balance moves.
 *
 * Safety:
 *  - Defaults to a DRY RUN: prints exactly what would happen without touching the database or
 *    sending any webhook. Pass --confirm to actually apply.
 *  - Verifies the customer's account number matches before touching anything.
 *  - Idempotent: keyed to the 9PSB reference (extracted from the end of the narration) as the
 *    Transaction's transaction_id — skipped if a Transaction with that reference already
 *    exists. Safe to re-run.
 *  - Does not block on insufficient/negative balance — reports it clearly, doesn't hide it.
 *  - Properly awaits webhook delivery before closing the database connection. (The first,
 *    one-off version of this script fired the webhook without awaiting it, then closed the DB
 *    pool immediately — causing "ConnectionManager.getConnection was called after the
 *    connection manager was closed!" while the webhook logic was still mid-flight. Fixed here.)
 *
 * Usage:
 *   node src/scripts/reconcileStaleBalance.js \
 *     --customerId=<uuid> \
 *     --accountNumber=<account number on file> \
 *     --narration="AIRPERO LTD/.../Payout/NAME /9PSB..." \
 *     --amount=<naira amount> \
 *     [--confirm]
 */

const { Customer, Transaction, sequelize } = db;

function parseArgs() {
  const args = {};
  for (const arg of process.argv.slice(2)) {
    if (arg === "--confirm") {
      args.confirm = true;
      continue;
    }
    const match = arg.match(/^--([^=]+)=([\s\S]*)$/);
    if (match) {
      args[match[1]] = match[2];
    }
  }
  return args;
}

async function run() {
  const args = parseArgs();
  const { customerId, accountNumber, narration, amount } = args;
  const DRY_RUN = !args.confirm;

  const missing = ["customerId", "accountNumber", "narration", "amount"].filter((k) => !args[k]);
  if (missing.length > 0) {
    console.error(`❌ Missing required argument(s): ${missing.join(", ")}`);
    console.error("\nUsage:");
    console.error('  node src/scripts/reconcileStaleBalance.js --customerId=<uuid> --accountNumber=<acct> --narration="<full bank statement line>" --amount=<naira> [--confirm]');
    process.exitCode = 1;
    return;
  }

  const numAmount = parseFloat(amount);
  if (!numAmount || numAmount <= 0) {
    console.error(`❌ Invalid amount: "${amount}"`);
    process.exitCode = 1;
    return;
  }

  const reference = narration.trim().split("/").pop();
  if (!reference) {
    console.error(`❌ Could not extract a reference from narration: "${narration}"`);
    process.exitCode = 1;
    return;
  }

  console.log("🔧 Stale Balance Reconciliation");
  console.log(DRY_RUN
    ? "🧪 DRY RUN — no database changes or webhooks will be made. Pass --confirm to actually apply.\n"
    : "⚠️  LIVE RUN — this will commit a real balance change and send a real webhook.\n");

  const customer = await Customer.findByPk(customerId);
  if (!customer) {
    console.error(`❌ Customer ${customerId} not found. Aborting — nothing was changed.`);
    return;
  }

  if (customer.accountNumber !== accountNumber) {
    console.error(
      `❌ Safety check failed: customer's account number on file is "${customer.accountNumber}", expected "${accountNumber}". Aborting — nothing was changed.`
    );
    return;
  }

  console.log(`Customer: ${customer.fullName} (${customer.id})`);
  console.log(`Merchant: ${customer.merchantId}`);
  console.log(`Reference: ${reference}`);
  console.log(`Narration: ${narration}`);
  console.log(`Balance before correction: NGN ${parseFloat(customer.balance).toLocaleString()}\n`);

  const existing = await Transaction.findOne({ where: { transaction_id: reference } });
  if (existing) {
    console.log(`⏭  Skipping — a Transaction with reference "${reference}" already exists (already corrected).`);
    return;
  }

  if (DRY_RUN) {
    const projectedAfter = parseFloat(customer.balance) - numAmount;
    console.log(
      `🧪 Would debit NGN ${numAmount.toLocaleString()}. Projected balance: NGN ${parseFloat(customer.balance).toLocaleString()} -> NGN ${projectedAfter.toLocaleString()}` +
      (projectedAfter < 0 ? "  ⚠️  would go negative" : "")
    );
    console.log(`   Would notify merchant ${customer.merchantId} via webhook (event: CUSTOMER_STALE_BALANCE).`);
    console.log("\nNothing was changed. Re-run with --confirm to actually apply.");
    return;
  }

  const dbTxn = await sequelize.transaction();
  let savedTx;
  try {
    const freshCustomer = await Customer.findByPk(customerId, { transaction: dbTxn, lock: true });
    const balanceBefore = parseFloat(freshCustomer.balance);
    const balanceAfter = balanceBefore - numAmount;

    await freshCustomer.decrement("balance", { by: numAmount, transaction: dbTxn });

    savedTx = await Transaction.create({
      merchantId: freshCustomer.merchantId,
      customerId: freshCustomer.id,
      transaction_id: reference,
      type: "debit",
      class: "payout",
      amount: numAmount,
      fee: 0,
      stampDuty: 0,
      total_amount: numAmount,
      status: "success",
      info: `Manual reconciliation: payout confirmed via 9PSB bank statement (pre-fix ambiguous-outcome bug). Narration: ${narration}`,
      currency: "NGN",
    }, { transaction: dbTxn });

    await dbTxn.commit();

    console.log(
      `✅ ${reference}: debited NGN ${numAmount.toLocaleString()}. Balance: NGN ${balanceBefore.toLocaleString()} -> NGN ${balanceAfter.toLocaleString()}` +
      (balanceAfter < 0 ? "  ⚠️  BALANCE IS NOW NEGATIVE" : "")
    );
  } catch (err) {
    await dbTxn.rollback();
    console.error("❌ Failed to apply correction:", err.message);
    return;
  }

  // Notify the merchant — AWAITED this time, so the DB connection can't close while this is
  // still in-flight (that's exactly what went wrong in the previous one-off script).
  try {
    await WebhookService.notifyMerchant(
      savedTx.merchantId,
      "CUSTOMER_STALE_BALANCE",
      {
        transactionId: savedTx.transaction_id,
        customerId: savedTx.customerId,
        customerName: customer.fullName,
        amount: numAmount,
        currency: "NGN",
        reason: "A payout confirmed via bank statement that was not properly recorded due to a since-fixed system issue.",
        reference: narration,
      },
      savedTx.customerId
    );
    console.log("📡 Webhook notification attempt complete (check MerchantWebhookLog for delivery status).");
  } catch (err) {
    console.error("⚠️ Webhook notification failed:", err.message);
  }

  const finalCustomer = await Customer.findByPk(customerId);
  console.log(`\nFinal balance: NGN ${parseFloat(finalCustomer.balance).toLocaleString()}`);
}

run()
  .catch((err) => {
    console.error("❌ Script error:", err);
    process.exitCode = 1;
  })
  .finally(() => {
    db.sequelize.close();
  });
