import { BookingAdjustmentData } from '@/manager/modules/booking/booking-types';
import { BookingAdjustmentAdjustedItemsTable } from '@/manager/modules/booking/components/BookingAdjustmentAdjustedItemsTable';
import { BookingAdjustmentApplyChangesRadioGroup } from '@/manager/modules/booking/components/BookingAdjustmentApplyChangesRadioGroup';
import { BookingAdjustmentCurrentItemsTable } from '@/manager/modules/booking/components/BookingAdjustmentCurrentItemsTable';
import { BookingAdjustmentItemsFieldSet } from '@/manager/modules/booking/components/BookingAdjustmentItemsFieldSet';
import { BookingAdjustmentOneTimeProductsTable } from '@/manager/modules/booking/components/BookingAdjustmentOneTimeProductsTable';
import { BookingAdjustmentProrationRadioGroup } from '@/manager/modules/booking/components/BookingAdjustmentProrationRadioGroup';
import { BookingAdjustmentRecurringProductsTable } from '@/manager/modules/booking/components/BookingAdjustmentRecurringProductsTable';
import { BookingAdjustmentSummaryTimeline } from '@/manager/modules/booking/components/BookingAdjustmentSummaryTimeline';
import { BookingAdjustmentRevertDialog } from '@/manager/modules/booking/views/BookingAdjustmentRevertDialog';
import { BaseDialog } from '@/manager/shared/base/BaseDialog';
import { getDialogErrorsHost, getSnackbarHost } from '@/manager/shared/utils/locator-utils';
import { getDetailsPageRegExpUrl } from '@/manager/shared/utils/url-utils';
import { Page } from '@playwright/test';

export class BookingAdjustmentDialog extends BaseDialog {
  readonly itemsFieldSet = new BookingAdjustmentItemsFieldSet(this.main);
  readonly recurringProductsTable = new BookingAdjustmentRecurringProductsTable(this.main);
  readonly oneTimeProductsTable = new BookingAdjustmentOneTimeProductsTable(this.main);
  readonly applyChangesRadioGroup = new BookingAdjustmentApplyChangesRadioGroup(this.main);
  readonly prorationRadioGroup = new BookingAdjustmentProrationRadioGroup(this.main);
  readonly currentItemsTable = new BookingAdjustmentCurrentItemsTable(this.main);
  readonly adjustedItemsTable = new BookingAdjustmentAdjustedItemsTable(this.main);
  readonly summaryTimeline = new BookingAdjustmentSummaryTimeline(this.main);
  readonly revertDialog = new BookingAdjustmentRevertDialog(this.host);
  readonly errors = getDialogErrorsHost(this.main);
  readonly adjustedSnackbar = getSnackbarHost(this.host, 'Booking successfully adjusted');
  readonly noChangesSnackbar = getSnackbarHost(this.host, 'No changes applied');
  readonly outdatedSnackbar = getSnackbarHost(
    this.host,
    'The current data was updated somewhere else and does not reflect the current state. Please reload this window and try again.'
  );

  constructor(host: Page) {
    super(host, 'booking-adjustment-dialog');
  }

  async goto(bookingId: number): Promise<void> {
    await this.host.goto(`/bookings/${bookingId}/adjust`);
  }

  async adjust(data: BookingAdjustmentData): Promise<void> {
    await this.fill(data);
    await this.adjustAfterFill(data);
  }

  async revert(data: BookingAdjustmentData): Promise<void> {
    await this.fill(data);
    await this.submit();
    await this.revertDialog.confirm();
    await this.host.waitForURL(getDetailsPageRegExpUrl('bookings'));
  }

  async submitWithoutChanges(): Promise<void> {
    await Promise.all([
      this.noChangesSnackbar.waitFor(),
      this.host.waitForURL(getDetailsPageRegExpUrl('bookings')),
      this.submit(),
    ]);
  }

  async fill(data: BookingAdjustmentData): Promise<void> {
    if (data.unitType) {
      await this.itemsFieldSet.unitTypeAutocomplete.select(data.unitType.name);
    }

    if (data.unit) {
      await this.itemsFieldSet.unitAutocomplete.select(data.unit.name);
    }

    if (data.plan?.name) {
      await this.itemsFieldSet.bookingPlanAutocomplete.select(data.plan.name);
    }

    if (data.insurance) {
      await this.itemsFieldSet.protectionPlanAutocomplete.select(data.insurance.name);
    }

    if (data.deposit) {
      await this.itemsFieldSet.depositAutocomplete.select(data.deposit.name);
    }

    if (data.discount) {
      await this.itemsFieldSet.discountAutocomplete.select(data.discount.name);
    }

    for (const name of data.recurringProducts?.remove ?? []) {
      await this.recurringProductsTable.remove(name);
    }

    for (const change of data.recurringProducts?.changeQuantity ?? []) {
      await this.recurringProductsTable.setQuantity(change.name, change.quantity);
    }

    for (const item of data.recurringProducts?.add ?? []) {
      await this.recurringProductsTable.add(item.product.name, item.quantity);
    }

    for (const name of data.oneTimeProducts?.remove ?? []) {
      await this.oneTimeProductsTable.remove(name);
    }

    for (const change of data.oneTimeProducts?.changeQuantity ?? []) {
      await this.oneTimeProductsTable.setQuantity(change.name, change.quantity);
    }

    for (const item of data.oneTimeProducts?.add ?? []) {
      await this.oneTimeProductsTable.add(item.product.name, item.quantity);
    }

    await this.applyChangesRadioGroup.fill(data.applyType, data.applyDate);

    if (data.prorationBehavior) {
      await this.prorationRadioGroup.fill(data.prorationBehavior);
    }
  }

  async adjustAfterFill(data: BookingAdjustmentData): Promise<void> {
    await Promise.all([this.adjustedSnackbar.or(this.outdatedSnackbar).waitFor(), this.submit()]);

    // Async post-create backend jobs can bump the booking's updated_at between the view load and the
    // save, failing the save's optimistic lock. Recover once, the way the error message tells the
    // user to: reload (which fetches a fresh timestamp), refill, and save again.
    if (await this.outdatedSnackbar.isVisible()) {
      // Accept the browser's unsaved-changes prompt that reloading the dirty form raises.
      this.host.once('dialog', (dialog) => void dialog.accept());
      await this.host.reload();
      await this.fill(data);
      await Promise.all([this.adjustedSnackbar.waitFor(), this.submit()]);
    }

    await this.host.waitForURL(getDetailsPageRegExpUrl('bookings'));
  }

  async submit(): Promise<void> {
    await this.adjustedItemsTable.loadingBar.waitFor({ state: 'hidden' });
    await this.submitButton.click();
  }
}
