/**
 * Backfill `timeline[].payments[]` for legacy CustomerPayment documents where
 * a stage has `paid > 0` but no payment entry rows (so history/edit and
 * Record Payment sums stay consistent).
 *
 * Run (development DB from .env.dev):
 *   npm run migrate:backfill:customer-payment-entries
 *
 * Dry-run (no writes):
 *   npm run migrate:backfill:customer-payment-entries -- --dry-run
 *
 * Limit documents (e.g. smoke test):
 *   npm run migrate:backfill:customer-payment-entries -- --limit=10
 *
 * Production: set NODE_ENV=production so `.env` is used, then run the same npm script.
 */

/* eslint-disable no-console */
import connectToDatabase from '@/shared/config/dbConfig';
import {
  calculateOverallStatus,
  calculateStageStatus,
} from '@/modules/customer/payment/payment.helper';
import { CustomerPayment } from '@/modules/customer/payment/payment.model';

function parseArgs() {
  const dryRun = process.argv.includes('--dry-run');
  let limit: number | undefined;
  for (const a of process.argv) {
    if (a.startsWith('--limit=')) {
      const n = Number(a.slice('--limit='.length));
      if (Number.isFinite(n) && n > 0) limit = Math.floor(n);
    }
  }
  return { dryRun, limit };
}

function sumPaymentAmounts(payments: unknown): number {
  if (!Array.isArray(payments)) return 0;
  return payments.reduce(
    (s: number, p: { amount?: number }) => s + Number(p?.amount ?? 0),
    0,
  );
}

/** Legacy bug: booking row stored same value for paid and dueAmount (gross duplicate). */
function isBookingAmountStage(label: unknown): boolean {
  return String(label ?? '').trim().toLowerCase() === 'booking amount';
}

async function run() {
  const { dryRun, limit } = parseArgs();

  try {
    console.info('Connecting to MongoDB...');
    await connectToDatabase();

    const cursor = CustomerPayment.find().cursor();
    let scanned = 0;
    let modifiedDocs = 0;
    let stagesBackfilled = 0;
    let stagesBookingDueFixed = 0;
    let mismatchWarnings = 0;

    const processDoc = async (doc: InstanceType<typeof CustomerPayment>) => {
      scanned += 1;
      let changed = false;
      const timeline = doc.timeline;
      if (!Array.isArray(timeline)) return;

      for (let i = 0; i < timeline.length; i++) {
        const st = timeline[i] as any;
        const paid = Number(st?.paid ?? 0);
        let due = Number(st?.dueAmount ?? 0);

        // Legacy: booking stage had dueAmount === paid while fully collected; remaining should be 0.
        if (
          isBookingAmountStage(st?.label) &&
          paid > 0 &&
          Math.round(due) === Math.round(paid)
        ) {
          st.dueAmount = 0;
          due = 0;
          const dd = st.dueDate ? new Date(st.dueDate) : new Date();
          st.status = calculateStageStatus(paid, 0, dd);
          changed = true;
          stagesBookingDueFixed += 1;
        }

        const sum = sumPaymentAmounts(st?.payments);

        if (paid > 0 && sum > 0 && Math.abs(paid - sum) > 0.01) {
          mismatchWarnings += 1;
          console.warn(
            `[skip] customerPayment=${doc._id} stage[${i}] "${st?.label}" paid=${paid} sum(entries)=${sum} — manual review`,
          );
          continue;
        }

        if (paid > 0 && sum === 0) {
          stagesBackfilled += 1;
          changed = true;
          if (!Array.isArray(st.payments)) st.payments = [];

          const raw = st.lastPaidAt || st.dueDate;
          const receivedAt =
            raw instanceof Date ? raw : raw ? new Date(raw) : new Date();

          const entry: Record<string, unknown> = {
            amount: paid,
            receivedAt,
          };
          if (st.bankAccountId) entry.bankAccountId = st.bankAccountId;

          st.payments.push(entry);
        }
      }

      if (changed && !dryRun) {
        doc.markModified('timeline');
        const tl = doc.timeline as any[];
        doc.paidAmount = tl.reduce(
          (s, st) => s + Number(st?.paid ?? 0),
          0,
        );
        doc.overallStatus = calculateOverallStatus(tl) as typeof doc.overallStatus;
        doc.markModified('paidAmount');
        doc.markModified('overallStatus');
        await doc.save();
        modifiedDocs += 1;
      } else if (changed && dryRun) {
        modifiedDocs += 1;
      }
    };

    let count = 0;
    for await (const doc of cursor) {
      await processDoc(doc as InstanceType<typeof CustomerPayment>);
      count += 1;
      if (limit != null && count >= limit) break;
    }

    console.info('---');
    console.info(`Scanned documents: ${scanned}`);
    console.info(
      `Documents ${dryRun ? 'that would be ' : ''}updated: ${modifiedDocs}`,
    );
    console.info(
      `Stages ${dryRun ? 'that would get ' : 'with '}backfilled payment row(s): ${stagesBackfilled}`,
    );
    if (stagesBookingDueFixed > 0)
      console.info(
        `Booking stages ${dryRun ? 'that would have ' : 'with '}dueAmount cleared (was equal to paid): ${stagesBookingDueFixed}`,
      );
    if (mismatchWarnings > 0)
      console.info(
        `Skipped stages (paid vs entries mismatch): ${mismatchWarnings}`,
      );
    if (dryRun) console.info('Dry-run: no writes performed.');
    process.exit(0);
  } catch (err) {
    console.error('Migration failed:', err);
    process.exit(1);
  }
}

run();
