import db from "../models/index.js";
import { calculateSaaSFee } from "../utils/feeCalculator.js";

/**
 * Creates a fake "pending" payout Transaction, aged so it's immediately eligible for the
 * next reconcilePendingPayouts run — purely to test that the PAYOUT_SUCCESS/PAYOUT_FAILED
 * webhook actually reaches a merchant end-to-end. Debits the merchant's (or customer's)
 * balance exactly like a real payout would, so the refund-on-failure path can also be
 * verified by checking the balance before and after.
 *
 * IMPORTANT — how to actually get a resolution quickly:
 *   9PSB's wallet_requery will only ever return SUCCESS for a reference that genuinely
 *   succeeded on their side. A made-up --reference will come back as "unrecognized" on every
 *   check, which normally just counts as one strike and stays pending (up to 4 checks,
 *   ~40 minutes at the default 10-minute cron interval) before finally resolving to FAILED.
 *
 *   To test PAYOUT_FAILED (and the refund) on the very next cron tick:
 *     Use a made-up --reference and leave --attempts at its default of 3 — the next check
 *     becomes strike #4 and resolves immediately regardless of what 9PSB says about an
 *     unrecognized reference.
 *
 *   To test PAYOUT_SUCCESS on the very next cron tick:
 *     You need a --reference that 9PSB will genuinely confirm as SUCCESS if requeried right
 *     now — e.g. the transaction_id of a real payout you know actually succeeded. --attempts
 *     doesn't matter in this case; a confirmed SUCCESS resolves immediately regardless.
 *
 * Safety:
 *  - Defaults to a DRY RUN: looks up the merchant/customer and prints exactly what would be
 *    created and debited, without writing anything. Pass --confirm to actually create it.
 *  - Verifies the merchant (and customer, if given) exist before doing anything.
 *  - Debits the real balance exactly like a live payout would — this is a real balance
 *    change on a real record, not a mock. Keep --amount small.
 *
 * Usage:
 *   node src/scripts/simulatePendingPayout.js --merchantId=<uuid> --reference=<ref>
 *   node src/scripts/simulatePendingPayout.js --merchantId=<uuid> --reference=<ref> --confirm
 *   node src/scripts/simulatePendingPayout.js --merchantId=<uuid> --customerId=<uuid> --reference=<ref> --amount=50 --confirm
 *   node src/scripts/simulatePendingPayout.js --merchantId=<uuid> --reference=<known-real-success-ref> --attempts=0 --confirm
 */

const { Merchant, Customer, Transaction, sequelize } = db;

function parseArgs() {
  const args = { amount: 100, attempts: 3 };
  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 DRY_RUN = !args.confirm;

  if (!args.merchantId || !args.reference) {
    console.error("❌ Usage: node src/scripts/simulatePendingPayout.js --merchantId=<uuid> --reference=<ref> [--customerId=<uuid>] [--amount=100] [--attempts=3] [--confirm]");
    process.exitCode = 1;
    return;
  }

  const amount = parseFloat(args.amount);
  const attempts = parseInt(args.attempts, 10);

  console.log("🔧 Simulate Pending Payout (for testing reconciliation + webhook delivery)");
  console.log(DRY_RUN
    ? "🧪 DRY RUN — nothing will be created or debited. Pass --confirm to actually create it.\n"
    : "⚠️  LIVE RUN — this will create a real Transaction and debit a real balance.\n");

  const merchant = await Merchant.findByPk(args.merchantId);
  if (!merchant) {
    console.error(`❌ Merchant ${args.merchantId} not found. Aborting.`);
    return;
  }

  let customer = null;
  if (args.customerId) {
    customer = await Customer.findOne({ where: { id: args.customerId, merchantId: merchant.id } });
    if (!customer) {
      console.error(`❌ Customer ${args.customerId} not found under merchant ${merchant.id}. Aborting.`);
      return;
    }
  }

  const existing = await Transaction.findOne({ where: { transaction_id: args.reference } });
  if (existing) {
    console.error(`❌ A Transaction with reference "${args.reference}" already exists (status: ${existing.status}). Use a different --reference.`);
    return;
  }

  const { fee, stampDuty } = await calculateSaaSFee("payout", amount, merchant.id);

  // Backdated so it's already past the 2-minute minimum age the reconciliation job requires
  // before it will even look at a pending payout — makes it eligible on the very next run
  // instead of needing to wait around after creating it.
  const backdatedCreatedAt = new Date(Date.now() - 5 * 60 * 1000);

  console.log(`Merchant: ${merchant.businessName} (${merchant.id})`);
  if (customer) console.log(`Customer: ${customer.fullName} (${customer.id})`);
  console.log(`Reference: ${args.reference}`);
  console.log(`Amount: NGN ${amount.toLocaleString()} | Fee: NGN ${fee} | Stamp Duty: NGN ${stampDuty}`);
  console.log(`Starting reconciliationAttempts: ${attempts} (next check = strike ${attempts + 1}${attempts + 1 >= 4 ? " — will resolve immediately, whatever 9PSB says" : ""})`);
  console.log(`Backdated createdAt: ${backdatedCreatedAt.toISOString()} (eligible on the next cron run)\n`);

  if (customer) {
    console.log(`Would debit customer balance by NGN ${amount.toLocaleString()}, merchant balance by NGN ${(fee + stampDuty).toLocaleString()} (fee + stamp duty).`);
  } else {
    console.log(`Would debit merchant balance by NGN ${(amount + fee + stampDuty).toLocaleString()} (amount + fee + stamp duty).`);
  }

  if (DRY_RUN) {
    console.log("\n🧪 Dry run complete. Nothing was created. Re-run with --confirm to actually create it.");
    return;
  }

  const t = await sequelize.transaction();
  try {
    if (customer) {
      const freshCustomer = await Customer.findByPk(customer.id, { transaction: t, lock: true });
      await freshCustomer.decrement("balance", { by: amount, transaction: t });

      const freshMerchant = await Merchant.findByPk(merchant.id, { transaction: t, lock: true });
      if (fee + stampDuty > 0) {
        await freshMerchant.decrement("balance", { by: fee + stampDuty, transaction: t });
      }
    } else {
      const freshMerchant = await Merchant.findByPk(merchant.id, { transaction: t, lock: true });
      await freshMerchant.decrement("balance", { by: amount + fee + stampDuty, transaction: t });
    }

    const savedTx = await Transaction.create({
      merchantId: merchant.id,
      customerId: customer ? customer.id : null,
      transaction_id: args.reference,
      type: "debit",
      class: "payout",
      amount,
      fee,
      stampDuty,
      total_amount: customer ? amount : amount + fee + stampDuty,
      status: "pending",
      reconciliationAttempts: attempts,
      info: `[SIMULATED] Test payout created for reconciliation/webhook verification.`,
      currency: "NGN",
      createdAt: backdatedCreatedAt,
    }, { transaction: t });

    await t.commit();

    console.log(`\n✅ Created. Transaction id: ${savedTx.id}, reference: ${args.reference}, status: pending, reconciliationAttempts: ${attempts}.`);
    console.log("This will be picked up on the next scheduled reconciliation run (every 10 minutes) — watch the server logs for:");
    console.log(`   [CRON] Found 1 pending payout(s) to reconcile.`);
    console.log(`   and then either a PAYOUT_SUCCESS or PAYOUT_FAILED webhook log entry for merchant ${merchant.id}.`);
  } catch (err) {
    await t.rollback();
    console.error("❌ Failed to create simulated payout:", err.message);
  }
}

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