import { getDepositCreateData } from '@/manager/modules/deposit/deposit-factories';
import {
  verifyDiscountBillingPlanTable,
  verifyDiscountProductTable,
} from '@/manager/modules/discount/discount-assertions';
import { getDiscountCreateData } from '@/manager/modules/discount/discount-factories';
import { test } from '@/manager/modules/discount/discount-fixtures';
import { formatDiscountRedemptions } from '@/manager/modules/discount/discount-utils';
import { discountCreateHappyTestCases } from '@/manager/modules/discount/test-cases/discount-create-happy-test-cases';
import { getProductOneTimeCreateData } from '@/manager/modules/product/product-factories';
import { expectDataTableTextColumnToHaveText } from '@/manager/modules/ui/data-table/data-table-assertions';
import { discountTableColumnTestIds } from '@/manager/modules/ui/data-table/data/data-table-column-test-ids';
import {
  getDefaultUnitTypeCreateMonthlyBookingPlans,
  getUnitTypeCreateData,
} from '@/manager/modules/unit-type/unit-type-factories';
import { expectSingleLocatorToHaveText } from '@/manager/shared/utils/expect-utils';
import { locations } from '@/shared/data/seed-locations';
import { formatCurrency, formatDateTime, formatDiscount, formatPercentage } from '@/shared/utils/formatters';
import { expect } from '@/shared/utils/matchers';

for (const tc of discountCreateHappyTestCases) {
  test(tc.description, async ({ createProductOneTime, discountCreateDialog, trackDiscountForTeardown }) => {
    const product = await createProductOneTime(getProductOneTimeCreateData({ location: tc.options?.location }));
    const data = getDiscountCreateData({ ...tc.options, location: product.location, products: [product] });

    const currency = data.location.currency;
    const formattedValue =
      data.type === 'percentage' ? formatPercentage(data.value) : formatCurrency(data.value, currency);
    const terms = formatDiscount(data.duration, formattedValue);
    const redemptions = formatDiscountRedemptions(0, data.usageLimit);
    const expiryDate = data.expirationDate ? formatDateTime(data.expirationDate) : undefined;
    const codes = data.code.value;

    await discountCreateDialog.goto();

    const detailsPage = await test.step('create discount', () => discountCreateDialog.create(data));

    trackDiscountForTeardown(detailsPage.getDiscountId());

    await test.step('verify new discount on details page', async () => {
      await expectSingleLocatorToHaveText(detailsPage.title, data.name);
      await expectSingleLocatorToHaveText(detailsPage.generalCard.name, data.name);
      await expectSingleLocatorToHaveText(detailsPage.generalCard.location, data.location.name);
      await expectSingleLocatorToHaveText(detailsPage.generalCard.codes, codes);
      await expectSingleLocatorToHaveText(detailsPage.generalCard.terms, terms);
      await expectSingleLocatorToHaveText(detailsPage.generalCard.redemptions, redemptions);
      await expectSingleLocatorToHaveText(detailsPage.generalCard.expiryDate, expiryDate);
      await verifyDiscountProductTable(detailsPage.productCard.list.dataTable, [product], [], currency);
    });

    await test.step('verify new discount on list page', async () => {
      const listPage = await detailsPage.returnToDiscountListPage();

      await listPage.searchTextField.fill(data.name);
      await listPage.locationAutocomplete.fill(data.location.name);

      await expect(listPage.dataTable.getRows()).toHaveCount(1);
      await expectDataTableTextColumnToHaveText(listPage.dataTable, discountTableColumnTestIds.name, data.name);
      await expectDataTableTextColumnToHaveText(
        listPage.dataTable,
        discountTableColumnTestIds.location,
        data.location.name
      );
      await expectDataTableTextColumnToHaveText(listPage.dataTable, discountTableColumnTestIds.terms, terms);
      await expectDataTableTextColumnToHaveText(listPage.dataTable, discountTableColumnTestIds.codes, codes);
      await expectDataTableTextColumnToHaveText(
        listPage.dataTable,
        discountTableColumnTestIds.redemptions,
        redemptions
      );
      await expectDataTableTextColumnToHaveText(listPage.dataTable, discountTableColumnTestIds.expiryDate, expiryDate);
    });
  });
}

test('creates a discount with one-time products and deposits', async ({
  createDeposit,
  createProductOneTime,
  discountCreateDialog,
  trackDiscountForTeardown,
}) => {
  const product = await createProductOneTime(getProductOneTimeCreateData());
  const deposit = await createDeposit(getDepositCreateData({ location: product.location }));
  const data = getDiscountCreateData({ location: product.location, products: [product], deposits: [deposit] });

  await discountCreateDialog.goto();

  const detailsPage = await test.step('create discount', () => discountCreateDialog.create(data));

  trackDiscountForTeardown(detailsPage.getDiscountId());

  await test.step('verify one-time products and deposits on details page', async () => {
    await verifyDiscountProductTable(
      detailsPage.productCard.list.dataTable,
      [product],
      [deposit],
      product.location.currency
    );
  });
});

test('creates a discount with billing plans', async ({
  createUnitType,
  discountCreateDialog,
  trackDiscountForTeardown,
}) => {
  const unitType = await createUnitType(
    getUnitTypeCreateData({ bookingPlans: getDefaultUnitTypeCreateMonthlyBookingPlans() })
  );
  const billingPlans = unitType.bookingPlans.slice(0, 2).map((plan) => ({ unitType, plan }));
  const data = getDiscountCreateData({ location: unitType.location, billingPlans });

  await discountCreateDialog.goto();

  const detailsPage = await test.step('create discount', () => discountCreateDialog.create(data));

  trackDiscountForTeardown(detailsPage.getDiscountId());

  await test.step('verify billing plans on details page', async () => {
    await verifyDiscountBillingPlanTable(
      detailsPage.billingPlanCard.list.dataTable,
      billingPlans,
      unitType.location.currency
    );
  });
});

test('creates a discount with one-time products and billing plans', async ({
  createProductOneTime,
  createUnitType,
  discountCreateDialog,
  trackDiscountForTeardown,
}) => {
  const unitType = await createUnitType(
    getUnitTypeCreateData({ bookingPlans: getDefaultUnitTypeCreateMonthlyBookingPlans() })
  );
  const product = await createProductOneTime(getProductOneTimeCreateData({ location: unitType.location }));
  const billingPlans = unitType.bookingPlans.slice(0, 2).map((plan) => ({ unitType, plan }));
  const currency = unitType.location.currency;
  const data = getDiscountCreateData({ location: unitType.location, products: [product], billingPlans });

  await discountCreateDialog.goto();

  const detailsPage = await test.step('create discount', () => discountCreateDialog.create(data));

  trackDiscountForTeardown(detailsPage.getDiscountId());

  await test.step('verify one-time products and billing plans on details page', async () => {
    await verifyDiscountProductTable(detailsPage.productCard.list.dataTable, [product], [], currency);
    await verifyDiscountBillingPlanTable(detailsPage.billingPlanCard.list.dataTable, billingPlans, currency);
  });
});

test('creates a discount reusing a code for a different product', async ({
  createDiscount,
  createProductOneTime,
  discountCreateDialog,
  trackDiscountForTeardown,
}) => {
  const location = locations.storeroomInnsbruck;
  const code = 'REUSEDPRODUCT';

  const [existingProduct, product] = await Promise.all([
    createProductOneTime(getProductOneTimeCreateData({ location })),
    createProductOneTime(getProductOneTimeCreateData({ location })),
  ]);
  await createDiscount(getDiscountCreateData({ location, products: [existingProduct], code }));

  const data = getDiscountCreateData({ location, products: [product], code });

  await discountCreateDialog.goto();

  const detailsPage = await test.step('create discount', () => discountCreateDialog.create(data));

  trackDiscountForTeardown(detailsPage.getDiscountId());

  await test.step('verify new discount on details page', async () => {
    await expectSingleLocatorToHaveText(detailsPage.generalCard.codes, code);
    await verifyDiscountProductTable(detailsPage.productCard.list.dataTable, [product], [], location.currency);
  });
});

test('creates a discount reusing a code for a different billing plan', async ({
  createDiscount,
  createUnitType,
  discountCreateDialog,
  trackDiscountForTeardown,
}) => {
  const code = 'REUSEDPLAN';

  const unitType = await createUnitType(
    getUnitTypeCreateData({
      location: locations.storeroomInnsbruck,
      bookingPlans: getDefaultUnitTypeCreateMonthlyBookingPlans(),
    })
  );
  const location = unitType.location;
  const [existingPlan, plan] = unitType.bookingPlans;
  await createDiscount(getDiscountCreateData({ location, billingPlans: [{ unitType, plan: existingPlan }], code }));

  const data = getDiscountCreateData({ location, billingPlans: [{ unitType, plan }], code });

  await discountCreateDialog.goto();

  const detailsPage = await test.step('create discount', () => discountCreateDialog.create(data));

  trackDiscountForTeardown(detailsPage.getDiscountId());

  await test.step('verify new discount on details page', async () => {
    await expectSingleLocatorToHaveText(detailsPage.generalCard.codes, code);
    await verifyDiscountBillingPlanTable(
      detailsPage.billingPlanCard.list.dataTable,
      [{ unitType, plan }],
      location.currency
    );
  });
});

test('creates a discount reusing a code in a different location', async ({
  createDiscount,
  createProductOneTime,
  discountCreateDialog,
  trackDiscountForTeardown,
}) => {
  const existingLocation = locations.storeroomInnsbruck;
  const location = locations.city1030;
  const code = 'REUSEDLOCATION';

  const [existingProduct, product] = await Promise.all([
    createProductOneTime(getProductOneTimeCreateData({ location: existingLocation })),
    createProductOneTime(getProductOneTimeCreateData({ location })),
  ]);
  await createDiscount(getDiscountCreateData({ location: existingLocation, products: [existingProduct], code }));

  const data = getDiscountCreateData({ location, products: [product], code });

  await discountCreateDialog.goto();

  const detailsPage = await test.step('create discount', () => discountCreateDialog.create(data));

  trackDiscountForTeardown(detailsPage.getDiscountId());

  await test.step('verify new discount on details page', async () => {
    await expectSingleLocatorToHaveText(detailsPage.generalCard.location, location.name);
    await expectSingleLocatorToHaveText(detailsPage.generalCard.codes, code);
  });
});

test('rejects a discount without all required fields', async ({ discountCreateDialog }) => {
  await discountCreateDialog.goto();

  await test.step('submit invalid discount create form', () => discountCreateDialog.submit());

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

test('rejects a discount with a duplicate name', async ({
  createDiscount,
  createProductOneTime,
  discountCreateDialog,
}) => {
  const product = await createProductOneTime(getProductOneTimeCreateData());
  const existing = await createDiscount(getDiscountCreateData({ location: product.location, products: [product] }));
  const data = getDiscountCreateData({ name: existing.name, location: product.location, products: [product] });

  await discountCreateDialog.goto();

  await test.step('submit discount create form with duplicate name', async () => {
    await discountCreateDialog.fill(data);
    await discountCreateDialog.submit();
  });

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

test('rejects a discount with a duplicate code', async ({
  createDiscount,
  createProductOneTime,
  discountCreateDialog,
}) => {
  const product = await createProductOneTime(getProductOneTimeCreateData({ location: locations.storeroomInnsbruck }));
  const existing = await createDiscount(
    getDiscountCreateData({ location: product.location, products: [product], code: 'DUPECODE' })
  );
  const data = getDiscountCreateData({ location: product.location, products: [product], code: existing.code.value });

  await discountCreateDialog.goto();

  await test.step('submit discount create form with duplicate code', async () => {
    await discountCreateDialog.fill(data);
    await discountCreateDialog.submit();
  });

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