import { BookingBasketContent } from '@/apps/portal/modules/booking/components/BookingBasketContent';
import {
  IBookingCreateCustomer,
  IBookingCreateData,
  IBookingCreateDiscount,
  IBookingCreateInsurance,
  IBookingCreateLocation,
  IBookingCreateNotAvailableData,
  IBookingCreateUnitType,
} from '@/apps/portal/modules/booking/types/booking-entity-types';
import { BookingLocationView } from '@/apps/portal/modules/booking/views/BookingLocationView';
import { BookingPaymentView } from '@/apps/portal/modules/booking/views/BookingPaymentView';
import { BookingSuccessView } from '@/apps/portal/modules/booking/views/BookingSuccessView';
import { BookingUnitTypeView } from '@/apps/portal/modules/booking/views/BookingUnitTypeView';
import { ICookieConsentData } from '@/apps/portal/modules/ui/cookie-consent/types/cookie-consent-types';
import { ICustomerType } from '@/shared/types/customer-types';
import {
  formatAddressWithoutCountry,
  formatCurrency,
  formatDiscount,
  formatPercentage,
  formatDateLegacy,
} from '@/shared/utils/format-utils';
import { decreaseByPercentage, increaseByPercentage } from '@/shared/utils/math-utils';
import { expect } from '@playwright/test';
import dayjs from 'dayjs';

export async function expectBookingNotToBeCreatedWithNeighboringUnit(
  startView: BookingLocationView,
  data: IBookingCreateData,
  notAvailable: IBookingCreateNotAvailableData
): Promise<void> {
  // select location specific booking portal
  const unitTypeView = await startView.select(data.location.id);

  // accept cookie consent banner
  await acceptCookieConsentBanner(unitTypeView, data.location.showCookieConsentBanner);

  // select unit type
  const optionsView = await unitTypeView.select(
    notAvailable.unitType,
    data.location.categories.length > 1 ? notAvailable.category : undefined
  );

  // fail select move in date
  const sizeNotAvailableView = await optionsView.failSelect(data.moveInDate);

  // check visibility of back to size selection button
  await expect(sizeNotAvailableView.backToSizeSelectionButton).toBeVisible();
}

export async function expectBookingToBeCreatedWithNeighboringUnit(
  startView: BookingLocationView,
  data: IBookingCreateData,
  notAvailable: IBookingCreateNotAvailableData,
  isMobile: boolean
): Promise<void> {
  // select location specific booking portal
  const unitTypeView = await startView.select(data.location.id);

  // accept cookie consent banner
  await acceptCookieConsentBanner(unitTypeView, data.location.showCookieConsentBanner);

  // select unit type
  const oldOptionsView = await unitTypeView.select(
    notAvailable.unitType,
    data.location.categories.length > 1 ? notAvailable.category : undefined
  );

  // fail select move in date
  const sizeNotAvailableView = await oldOptionsView.failSelect(data.moveInDate);

  // select neighboring unit
  const newOptionsView = await sizeNotAvailableView.select(data.unitType.name);

  // select booking plan and insurance
  await newOptionsView.selectWithoutMoveInDate(data.unitType.billing, data.insurance?.name);

  // confirm selected options
  const basketView = await newOptionsView.confirm();

  // add discount code
  data.discount && (await basketView.add(data.discount.code));

  // check basket summary
  await expectBasketContentToDisplayCorrectData(
    basketView.basketContent,
    isMobile,
    'private',
    data.moveInDate,
    data.location,
    data.unitType,
    data.insurance,
    data.discount
  );

  // confirm basket summary
  const customerView = await basketView.confirm();

  // set customer view viewport
  customerView.isMobile = isMobile;

  // set customer data
  await customerView.fill(data.customer);

  // open drawer
  isMobile && (await customerView.openDrawer());

  // check basket summary
  await expectBasketContentToDisplayCorrectData(
    customerView.getBasketContent(),
    isMobile,
    data.customer.type,
    data.moveInDate,
    data.location,
    data.unitType,
    data.insurance,
    data.discount
  );

  // confirm customer data
  const paymentView = await customerView.confirm();

  // set payment info
  await paymentView.fill(data.paymentMethod);

  // confirm payment and create booking
  await confirmPayment(isMobile, paymentView, data);
}

export async function expectBookingNotToBeCreatedDueToDeclinedPayment(
  startView: BookingLocationView,
  data: IBookingCreateData,
  isMobile: boolean
): Promise<void> {
  // select location specific booking portal
  const unitTypeView = await startView.select(data.location.id);

  // accept cookie consent banner
  await acceptCookieConsentBanner(unitTypeView, data.location.showCookieConsentBanner);

  // select unit type
  const optionsView = await unitTypeView.select(
    data.unitType.name,
    data.location.categories.length > 1 ? data.unitType.category : undefined
  );

  // select move in date, booking plan and insurance
  await optionsView.select(data.moveInDate, data.unitType.billing, data.insurance?.name);

  // confirm selected options
  const basketView = await optionsView.confirm();

  // add discount code
  data.discount && (await basketView.add(data.discount.code));

  // check basket summary
  await expectBasketContentToDisplayCorrectData(
    basketView.basketContent,
    isMobile,
    'private',
    data.moveInDate,
    data.location,
    data.unitType,
    data.insurance,
    data.discount
  );

  // confirm basket summary
  const customerView = await basketView.confirm();

  // set customer view viewport
  customerView.isMobile = isMobile;

  // set customer data
  await customerView.fill(data.customer);

  // open drawer
  isMobile && (await customerView.openDrawer());

  // check basket summary
  await expectBasketContentToDisplayCorrectData(
    customerView.getBasketContent(),
    isMobile,
    data.customer.type,
    data.moveInDate,
    data.location,
    data.unitType,
    data.insurance,
    data.discount
  );

  // confirm customer data
  const paymentView = await customerView.confirm();

  // set payment info
  await paymentView.fill(data.paymentMethod);

  // try to confirm payment
  await paymentView.payNowButton.click();

  try {
    await expect(paymentView.stripeFieldError).toBeVisible({ timeout: 2500 });
  } catch (e) {
    await expect(paymentView.stripeAlertError).toBeVisible();
  }
}

export async function expectBookingToBeCreated(
  startView: BookingLocationView,
  data: IBookingCreateData,
  isMobile: boolean
): Promise<void> {
  // select location specific booking portal
  const unitTypeView = await startView.select(data.location.id);

  // accept cookie consent banner
  await acceptCookieConsentBanner(unitTypeView, data.location.showCookieConsentBanner);

  // select unit type
  const optionsView = await unitTypeView.select(
    data.unitType.name,
    data.location.categories.length > 1 ? data.unitType.category : undefined
  );

  // select move in date, booking plan and insurance
  await optionsView.select(data.moveInDate, data.unitType.billing, data.insurance?.name);

  // confirm selected options
  const basketView = await optionsView.confirm();

  // add discount code
  data.discount && (await basketView.add(data.discount.code));

  // check basket summary
  await expectBasketContentToDisplayCorrectData(
    basketView.basketContent,
    isMobile,
    'private',
    data.moveInDate,
    data.location,
    data.unitType,
    data.insurance,
    data.discount
  );

  // confirm basket summary
  const customerView = await basketView.confirm();

  // set customer view viewport
  customerView.isMobile = isMobile;

  // set customer data
  await customerView.fill(data.customer);

  // open drawer
  isMobile && (await customerView.openDrawer());

  // check basket summary
  await expectBasketContentToDisplayCorrectData(
    customerView.getBasketContent(),
    isMobile,
    data.customer.type,
    data.moveInDate,
    data.location,
    data.unitType,
    data.insurance,
    data.discount
  );

  // confirm customer data
  const paymentView = await customerView.confirm();

  // set payment info
  await paymentView.fill(data.paymentMethod);

  // confirm payment and create booking
  await confirmPayment(isMobile, paymentView, data);
}

async function acceptCookieConsentBanner(view: BookingUnitTypeView, showCookieConsentBanner: boolean): Promise<void> {
  if (!showCookieConsentBanner) {
    return;
  }

  const cookieConsentData: ICookieConsentData = {
    allowMarketing: true,
  };

  await view.acceptCookieConsent(cookieConsentData);
}

async function confirmPayment(isMobile: boolean, view: BookingPaymentView, data: IBookingCreateData): Promise<void> {
  // currently restricting sepa payment tests for desktop (see: comments of https://kinn.atlassian.net/browse/KIN-2835)
  if (!isMobile && data.paymentMethod.kind === 'sepa') {
    return;
  }

  // confirm payment
  const successView = await view.confirm();

  // check success view
  await expectSuccessViewToDisplayCorrectData(successView, data.customer);
}

async function expectSuccessViewToDisplayCorrectData(
  successView: BookingSuccessView,
  customer: IBookingCreateCustomer
): Promise<void> {
  const customerName = customer.companyName ?? `${customer.firstName} ${customer.lastName}`;

  await expect(successView.welcomeMessage).toBeVisible();
  await expect(successView.thanksMessage).toContainText(customerName);
  await expect(successView.emailMessage).toContainText(customer.email);
  await expect(successView.repeatButton).toBeVisible();
}

async function expectBasketContentToDisplayCorrectData(
  basketContent: BookingBasketContent,
  isMobile: boolean,
  customerType: ICustomerType,
  moveInDate: Date,
  location: IBookingCreateLocation,
  unitType: IBookingCreateUnitType,
  insurance?: IBookingCreateInsurance,
  discount?: IBookingCreateDiscount
): Promise<void> {
  // calculate prices of basket summary
  const unitTypePriceExclVat = unitType.billing.price * unitType.billing.amount;
  const unitTypePriceDiscounted =
    discount &&
    (discount.type === 'fixed'
      ? unitTypePriceExclVat - discount.value
      : decreaseByPercentage(unitTypePriceExclVat, discount.value));
  const unitTypeTaxRate = customerType === 'private' ? unitType.taxes.b2c : unitType.taxes.b2b;
  const unitTypePriceInclVat = increaseByPercentage(unitTypePriceExclVat, unitTypeTaxRate);
  const unitTypePriceInclVatDiscounted =
    unitTypePriceDiscounted && increaseByPercentage(unitTypePriceDiscounted, unitTypeTaxRate);
  const insurancePriceExclVat = insurance && insurance.billing.price * insurance.billing.amount;
  const insuranceTaxRate = insurance && (customerType === 'private' ? insurance.taxes.b2c : insurance.taxes.b2b);
  const insurancePriceInclVat =
    insurancePriceExclVat && increaseByPercentage(insurancePriceExclVat, insuranceTaxRate ?? 0);
  const totalPrice = unitTypePriceInclVat + (insurancePriceInclVat ?? 0);
  const totalPriceDiscounted =
    unitTypePriceInclVatDiscounted && unitTypePriceInclVatDiscounted + (insurancePriceInclVat ?? 0);
  const recurringPaymentPrice = totalPrice;
  let recurringPaymentPriceDiscounted: number | undefined = undefined;

  if (discount) {
    if (discount.duration.option === 'multiple_months' && discount.duration.amount) {
      const unitTypeBillingEndDate = dayjs().add(
        unitType.billing.amount,
        unitType.billing.type === 'weekly' ? 'weeks' : 'months'
      );
      const discountDurationEndDate = dayjs().add(discount.duration.amount, 'months');

      if (unitTypeBillingEndDate.isBefore(discountDurationEndDate)) {
        recurringPaymentPriceDiscounted = totalPriceDiscounted;
      }
    } else if (discount.duration.option === 'forever') {
      recurringPaymentPriceDiscounted = totalPriceDiscounted;
    }
  }

  // check data of basket summary
  if (isMobile) {
    await expect(basketContent.location).toBeHidden();
  } else {
    await expect(basketContent.location).toBeVisible();
    await expect(basketContent.locationName).toHaveText(location.name);
    await expect(basketContent.locationAddress).toHaveText(
      formatAddressWithoutCountry(location.street, location.postalCode, location.city)
    );
  }

  await expect(basketContent.moveInDateValue).toHaveText(formatDateLegacy(moveInDate));
  await expect(basketContent.unitTypeName).toHaveText(unitType.name);
  await expect(basketContent.unitTypePriceInclVat).toHaveText(formatCurrency(unitTypePriceInclVat, location.currency));

  if (insurancePriceInclVat) {
    await expect(basketContent.insuranceName).toHaveText(insurance.name);
    await expect(basketContent.insurancePriceInclVat).toHaveText(
      formatCurrency(insurancePriceInclVat, location.currency)
    );
  } else {
    await expect(basketContent.insuranceName).toBeHidden();
    await expect(basketContent.insurancePriceInclVat).toBeHidden();
  }

  discount
    ? await expect(basketContent.discountCodeForm.discountTerms).toHaveText(
        formatDiscount(
          discount.duration,
          discount.type === 'percentage'
            ? formatPercentage(discount.value)
            : formatCurrency(discount.value, location.currency)
        )
      )
    : await expect(basketContent.discountCodeForm.discountTerms).toBeHidden();

  if (totalPriceDiscounted) {
    await expect(basketContent.firstInvoiceOldPriceInclVat).toHaveText(formatCurrency(totalPrice, location.currency));
    await expect(basketContent.firstInvoiceNewPriceInclVat).toHaveText(
      formatCurrency(totalPriceDiscounted, location.currency)
    );
  } else {
    await expect(basketContent.firstInvoiceOldPriceInclVat).toBeHidden();
    await expect(basketContent.firstInvoiceNewPriceInclVat).toHaveText(formatCurrency(totalPrice, location.currency));
  }

  if (recurringPaymentPriceDiscounted) {
    await expect(basketContent.recurringPaymentOldPriceInclVat).toHaveText(
      formatCurrency(recurringPaymentPrice, location.currency)
    );
    await expect(basketContent.recurringPaymentNewPriceInclVat).toHaveText(
      formatCurrency(recurringPaymentPriceDiscounted, location.currency)
    );
  } else {
    await expect(basketContent.recurringPaymentOldPriceInclVat).toBeHidden();
    await expect(basketContent.recurringPaymentNewPriceInclVat).toHaveText(
      formatCurrency(recurringPaymentPrice, location.currency)
    );
  }
}
