import { test } from '@/manager/modules/invoice/invoice-fixtures';
import { getInvoiceMarkAsPaidData } from '@/manager/modules/invoice/invoice-factories';
import {
  toInvoiceCreateTotalsSummary,
  toInvoiceMarkAsPaidPaymentMethodLabel,
} from '@/manager/modules/invoice/invoice-mappers';
import { invoiceMarkAsPaidHappyTestCases } from '@/manager/modules/invoice/test-cases/invoice-mark-as-paid-test-cases';
import { invoicePaymentTableColumnTestIds } from '@/manager/modules/ui/data-table/data/data-table-column-test-ids';
import { expectSingleLocatorToHaveText } from '@/manager/shared/utils/expect-utils';
import { formatCurrency, formatDate } from '@/shared/utils/formatters';
import { expect } from '@/shared/utils/matchers';
import { pollOrSkip } from '@/shared/utils/poll-utils';

// The original charge lands asynchronously after creation and the cancellation lands asynchronously after the
// mark, so each case reload-polls twice — give the per-test budget headroom over the 30s default.
test.describe.configure({ mode: 'default', timeout: 120_000 });

for (const tc of invoiceMarkAsPaidHappyTestCases) {
  test(tc.description, async ({ setupInvoiceCreateData, createInvoice, invoiceDetailsPage }) => {
    const data = await setupInvoiceCreateData({ numberOfProducts: 1 });
    const invoice = await createInvoice(data);

    const markAsPaidData = getInvoiceMarkAsPaidData(tc.options);
    const currency = data.location.currency;
    const total = formatCurrency(toInvoiceCreateTotalsSummary(data).totalInclVat, currency);
    const amountDue = formatCurrency(0, currency);
    // The payment date is a calendar date picked in the dialog, so format it in the runner's own zone (the
    // basis the date picker selects the day on) rather than the location's — the two can disagree across midnight.
    const paymentDate = formatDate(markAsPaidData.paymentDate);
    const paymentMethod = toInvoiceMarkAsPaidPaymentMethodLabel(markAsPaidData);

    const payments = invoiceDetailsPage.paymentsCard.dataTable;

    await test.step('wait for the invoice charge to be recorded', async () => {
      // An open invoice's charge is recorded asynchronously a few seconds after creation, and the payments
      // card only fetches on mount — reload-poll until it lands so the mark-as-paid flow has a charge to cancel.
      await pollOrSkip(
        async () => {
          await invoiceDetailsPage.goto(invoice.id);
          await expect(payments.getRows()).toHaveCount(1);
        },
        { timeout: 60_000, reason: 'invoice charge was not recorded within 60s — test-env queue backlog (KIN-4692)' }
      );
    });

    await test.step('mark invoice as paid', async () => {
      const dialog = await invoiceDetailsPage.openInvoiceMarkAsPaidDialog();
      await dialog.mark(markAsPaidData);
    });

    await test.step('verify paid invoice on details page', async () => {
      const chargeCell = (status: string, columnId: string) =>
        payments.getRowColumnBy([{ columnId: invoicePaymentTableColumnTestIds.status, text: status }], columnId);

      // Marking an open invoice paid out of band cancels its original charge and records a new succeeded one
      // for the same total, dated and attributed to the dialog's payment selection. The cancellation lands
      // asynchronously a few seconds after the new charge, and the payments card only fetches on mount, so
      // reload-poll until the invoice is settled and both charges have landed.
      await pollOrSkip(
        async () => {
          await invoiceDetailsPage.reload();

          await expectSingleLocatorToHaveText(invoiceDetailsPage.baseStatus, 'paid');
          await expectSingleLocatorToHaveText(invoiceDetailsPage.generalCard.amountDue, amountDue);

          await expect(payments.getRows()).toHaveCount(2);
          await expectSingleLocatorToHaveText(
            chargeCell('Cancelled', invoicePaymentTableColumnTestIds.amountInclVat),
            total
          );
          await expectSingleLocatorToHaveText(
            chargeCell('Succeeded', invoicePaymentTableColumnTestIds.amountInclVat),
            total
          );
          await expectSingleLocatorToHaveText(
            chargeCell('Succeeded', invoicePaymentTableColumnTestIds.date),
            paymentDate
          );
          await expectSingleLocatorToHaveText(
            chargeCell('Succeeded', invoicePaymentTableColumnTestIds.paymentMethod),
            paymentMethod
          );
        },
        {
          timeout: 60_000,
          reason: 'mark-as-paid charges did not settle within 60s — test-env queue backlog (KIN-4692)',
        }
      );
    });
  });
}

test('rejects marking an invoice as paid without all required fields', async ({
  setupInvoiceCreateData,
  createInvoice,
  invoiceDetailsPage,
}) => {
  const data = await setupInvoiceCreateData({ numberOfProducts: 1 });
  const invoice = await createInvoice(data);

  await invoiceDetailsPage.goto(invoice.id);

  const dialog = await test.step('submit invalid invoice mark as paid form', async () => {
    const markAsPaidDialog = await invoiceDetailsPage.openInvoiceMarkAsPaidDialog();
    await markAsPaidDialog.submit();
    return markAsPaidDialog;
  });

  await test.step('verify errors on form', async () => {
    await expect(dialog.errors).toHaveCountGreaterThan(0);
  });
});
