import { Utility, utilitySelector, LocationUtility, Logs, Timeouts, guidRegex } from '@utility-lib/index';
import { hotelSelector } from './search';
import { HotelParams, ReSearchUtility } from '../modal';
import Sinon from 'cypress/types/sinon';

type supplierType = 'EXPEDIA' | 'CUG' | 'AGODA' | 'BOOKING_COM' | 'SABRE' | 'PRICELINE';

type tabType = 'Reviews' | 'Amenities' | 'Room options' | 'Overview';

type PaymentType = 'Deposit required' | 'Prepay' | 'Pay at hotel';

export const hotelSearchResultSelector = {
  getDatesFromCalendar: '.hotel-header__detail__date [class^="hotel-header__detail__datetxt"]',
  hotelDetailPageLoading: '[class="hotel-details-loading loaded grid-loaded"]',
  selectHotelRoom: '[qaid="hotelRoomSelect"] button:not([disabled])',
  hotelSearchHeader: '.hotel-header .hotel-header__detail__name',
  selectHotel: '.ta-button__content span',
  roomOptionHeader: '.room__price',
  selectRoomOption: '.price__action-button-container',
  showMoreOptions: '[class^="show-more__container"] span span',
  editSearch: '[class^="hotel-header__detail__edit"]',
  hotelNameInput: '[name="inputSearch"]',
  searchHotel: '[class$="ta-button--primary-theme"]',
  hotelLocation: '.ta-form-element',
  dropdown: '[class^="ta-typeahead__item-wrap"]',
  calendarButton: '[class^="ta-date-trigger"].ta-date-trigger__text',
  nextMonthCalendarButton: '[class$="ta-calendar__handle--right"]',
  selectDate: '[class^="ta-calendar-day"]',
  roomSelect: '[formcontrolname="numberOfRooms"]',
  pickHotelFromMap: '.map [id^="marker-hotel-"] .pin-base__bubble__label',
  selectHotelFromMap: '[class^="detail-section"]',
  payment: '[class="filter-dropdown filter-dropdown--default"] [title="Payment"]',
  closeSvg: '[class^="ta-modal-header__close"]',
  priceBlock: '.price-section__price-block',
  selectFromDropDown: '.mat-checkbox-label',
  paymentAtCheckIn: '.room-payreq-modal .ta-button__content',
  selectHotelChain: 'ta-filter-more-item[label="Hotel chains"]',
  selectPrepayHotelRoomFilter: '[class^="hotel-details__payment-banner"] .banner__text-3',
  headsUpModel: '.heads-up-modal',
  roomRateModel: '.room-rate-modal',
  continueHeadsUpButton: '.cdk-overlay-pane [class^="ta-button ta-button--medium-size"]',
  continueRoomRateButton: '.cdk-overlay-pane [class^="ta-button ta-button--medium-size ta-button--primary-color"]',
  recommendedHotelsCards: 'ta-similar-options',
  navigationTabs: '.ta-tabs__item',
  hotelDetailSections: '.hotel-details__wrapper > section',
  paymentTypeDropDown: '[class^="filter-dropdown"] [title="Payment"]',
  paymentTypeDropdownDisabled: '.filter-dropdown__disabled',
  selectPaymentPayAtHotel: '[qaid="Pay at hotel"]',
  selectPaymentPrepay: '[qaid="Prepay"]',
  aaaMemberRate: 'ta-modal [class^="ta-button ta-button--medium-size ta-button--primary-color"]',
  taHeader: 'ta-header',
  taHotelHeader: 'ta-hotel-header',
  taHotelSubHeader: '.hotel-details__subheader',
  hotelNoReviewLabel: '.hotel-reviews__no-details__no-review-label',
  hotelReviewFilter: '[headerlabel="Ratings"] .filter-dropdown',
  navanHotelReviewFilter: '.filter-dropdown__label[title="Ratings"]',
  selectHotelReviewDropDown: '#guestRatings .filter-boolean__label',
  acceptHotelReviewFilter: '[aria-label="see results"]',
  depositRequired: '[qaid="hotelRoomPaymentSchedule"] span span',
  priceSection: 'ta-price-section',
  priceSectionLoader: 'ta-price-section .price__skeleton',
  seeMoreOptions: '.root__action__text',
  paymentRequredWhenCheckIn: '.room-payreq-modal',
  hotelChains: '#hotelChains',
  matCheckbox: 'mat-checkbox',
  filterSearchTitle: (filterName: string) => `.filter-search [title="${filterName}"]`,
  filterSearchGroupName: (filterGroupName: string) => `.filter-search [groupname="${filterGroupName}"] > div`,
  supplierImage: (supplier: string) => `[src="assets/images/svg/inventory/${supplier}.svg"]`,
  ariaLabelRoom: (num: number) => `[aria-label="${num} Room"]`,
  ariaLabelRooms: (num: number) => `[aria-label="${num} Rooms"]`,
  hotelLocationInput: '[class="hotel-header__detail__name"]',
  hotelFilterSliderRight: '.horizontal-slider__scroll-right'
};

export class HotelSearchResults {
  static selectHotel(index = 0) {
    cy.get(hotelSearchResultSelector.selectHotel, Timeouts.MEDIUM_TIMEOUT_60_SEC)
      .should('be.visible')
      .then((hotels) => {
        if (index >= hotels.length) {
          throw new Error("Can't select hotel with index " + index);
        }

        this.openNewHotelTab();
        this.closePopup();
        cy.wrap(hotels).eq(index).click({ force: true });
      });
    cy.allure().logStep(`select ${index} hotel`);
  }

  static closePopup() {
    cy.get(utilitySelector.bodySelector).then((body) => {
      if (body.find(hotelSearchResultSelector.closeSvg).length > 0) {
        cy.get(hotelSearchResultSelector.closeSvg).click();
      }
    });
  }

  static getReviewedHotel() {
    cy.wait(Timeouts.SHORT_TIMEOUT_3_SEC.timeout);
    cy.get(utilitySelector.bodySelector).then((body) => {
      if (body.find(hotelSearchResultSelector.hotelFilterSliderRight).length > 0) {
        cy.get(hotelSearchResultSelector.hotelFilterSliderRight).then((sliderEle) => {
          if (sliderEle.is(':visible')) {
            cy.get(hotelSearchResultSelector.hotelFilterSliderRight).click();
          }
        });
      }
      cy.get(hotelSearchResultSelector.navanHotelReviewFilter).click();

      cy.get(hotelSearchResultSelector.selectHotelReviewDropDown).eq(0).click();
      cy.get(hotelSearchResultSelector.acceptHotelReviewFilter).click();
    });
  }

  static openNewHotelTab() {
    cy.window().then((win) => {
      cy.stub(win, 'open')
        //! When we call cy.stub(win, 'open') it replaces win.open method with a Sinon stub function
        //! but pass the `_self` argument
        //! so the new tab will open in the current window
        //! instead of a new one
        //! stub.wrappedMethod Holds a reference to the original method/function this stub has wrapped.
        .callsFake((url) => {
          return (win.open as Sinon.SinonStub).wrappedMethod.call(win, url, '_self');
        });
    });
  }

  static selectPaymentRoomOption(paymentType: string) {
    cy.get(hotelSelector.paymentDropdown).click();

    if (paymentType == 'Prepay') {
      cy.get(hotelSelector.prepayCheckBox).click();
    } else if (paymentType == 'PayAtHotel') {
      cy.get(hotelSelector.payAtHotelCheckBox).click();
    }
  }
  static getRoom() {
    cy.allure().logStep('select first room from the room option');
    cy.get(hotelSearchResultSelector.selectHotelRoom, Timeouts.MAX_TIMEOUT_120_SEC)
      .should('be.visible')
      .eq(0)
      .scrollIntoView({ easing: 'linear' });
    cy.get(hotelSearchResultSelector.selectHotelRoom, Timeouts.MEDIUM_TIMEOUT_60_SEC)
      .eq(0)
      .should('be.visible')
      .click({ force: true });
  }

  static getDepositRequiredRoom(hotelParams: HotelParams) {
    cy.allure().logStep('select deposit required room');
    this.selectRoomOptionFromShowMoreOptions();
    this.openSeeMoreRoomOptions();
    this.selectDepositRequiredRoom(hotelParams).then(() => {
      if (hotelParams.isNeedToReselectLocation) {
        Logs.logToCypressAndAllure('There are no rooms available with deposit required. Trying to find a new hotel');
        ReSearchUtility.changeHotelFromSearchPage(LocationUtility.getDepositRequiredHotels(), hotelParams);
        HotelSearchResults.selectHotel(0);
        HotelSearchResults.getDepositRequiredRoom(hotelParams);
      }
    });
  }

  static waitForLoadAllSelectButtons() {
    cy.get(hotelSearchResultSelector.priceSectionLoader).should('not.exist', Timeouts.MAX_TIMEOUT_120_SEC);
  }

  private static selectDepositRequiredRoom(hotelParams: HotelParams) {
    cy.allure().logStep('select first room from the deposit required room option');
    return cy.get(utilitySelector.bodySelector).then((body) => {
      if (body.find(hotelSearchResultSelector.depositRequired).length > 0 && body.text().includes('Deposit required')) {
        this.selectRoomByPaymentType('Deposit required');
        this.acceptHeadsUp();
        this.acceptRoomRate();
        cy.wait('@waitForRoomSelected').then((xhr) => {
          hotelParams.isNeedToReselectLocation = xhr.response?.statusCode !== 200;
        });
      } else {
        hotelParams.isNeedToReselectLocation = true;
      }
    });
  }

  static selectRoomByPaymentType(paymentType: PaymentType) {
    cy.get(hotelSearchResultSelector.priceSection)
      .contains(paymentType)
      .parentsUntil(hotelSearchResultSelector.priceSection)
      .find(utilitySelector.buttonSelector)
      .then((ele) => {
        cy.wrap(ele).scrollIntoView({ easing: 'linear' });
        cy.wrap(ele).should('be.visible');
        cy.wrap(ele).click({ force: true });
      });
  }

  static selectRoomOption() {
    this.getRoom();

    cy.get(utilitySelector.bodySelector).then((paymentAtCheckIn) => {
      if (paymentAtCheckIn.find(hotelSearchResultSelector.paymentAtCheckIn).length > 0) {
        cy.get(hotelSearchResultSelector.paymentAtCheckIn).contains('Continue').click();
        cy.allure().logStep('continue payment at check in');
      } else {
        cy.allure().logStep('payment not required');
      }
    });
    this.acceptHeadsUp();
    this.acceptRoomRate();
    this.paymentRequiredWhenCheckIn();
  }

  static acceptExclusiveAAAMemberRate() {
    cy.get(utilitySelector.bodySelector).then((memberRate) => {
      cy.log('accept Exclusive AAA Member Rate');
      if (memberRate.find(hotelSearchResultSelector.aaaMemberRate).length > 0) {
        cy.get(hotelSearchResultSelector.aaaMemberRate).click();
      }
    });
  }

  static acceptHeadsUp() {
    cy.wait(Timeouts.SHORT_TIMEOUT_3_SEC.timeout);
    cy.get(utilitySelector.bodySelector).then((headsUp) => {
      if (headsUp.find(hotelSearchResultSelector.headsUpModel).length > 0) {
        cy.allure().logStep('accept heads up');
        cy.get(hotelSearchResultSelector.continueHeadsUpButton).last().click();
      } else {
        cy.log('heads up model not found');
        cy.allure().logStep('heads up model not found');
      }
    });
  }

  static paymentRequiredWhenCheckIn() {
    cy.wait(Timeouts.SHORT_TIMEOUT_3_SEC.timeout);
    cy.get(utilitySelector.bodySelector).then((headsUp) => {
      if (headsUp.find(hotelSearchResultSelector.paymentRequredWhenCheckIn).length > 0) {
        cy.allure().logStep('Payment required at check-in');
        cy.get(hotelSearchResultSelector.continueHeadsUpButton).last().click();
      } else {
        cy.log('Payment required at check-in not found');
        cy.allure().logStep('Payment required at check-in not found');
      }
    });
  }

  static acceptRoomRate() {
    cy.wait(Timeouts.SHORT_TIMEOUT_3_SEC.timeout);
    cy.get(utilitySelector.bodySelector).then((headsUp) => {
      if (headsUp.find(hotelSearchResultSelector.roomRateModel).length > 0) {
        cy.allure().logStep('accept room rate');
        cy.get(hotelSearchResultSelector.continueRoomRateButton).last().click();
      } else {
        cy.log('room rate model not found');
        cy.allure().logStep('room rate model not found');
      }
    });
  }

  static selectFilter(filterName: string) {
    cy.allure().logStep('choose hotel filter');
    cy.get(hotelSearchResultSelector.selectHotel, Timeouts.MEDIUM_TIMEOUT_60_SEC);
    cy.get(hotelSearchResultSelector.filterSearchTitle(filterName)).click({
      force: true
    });
  }

  static selectFilterByGroupName(filterGroupName: string) {
    cy.allure().logStep('choose hotel filter');
    cy.get(hotelSearchResultSelector.selectHotel, Timeouts.MEDIUM_TIMEOUT_60_SEC);
    cy.get(hotelSearchResultSelector.filterSearchGroupName(filterGroupName)).click({
      force: true
    });
  }

  static selectPrepayHotel() {
    cy.allure().logStep('select Prepay Hotel');
    this.selectFilterByGroupName('more');
    cy.get(hotelSearchResultSelector.selectHotelChain).click();
    cy.get(hotelSearchResultSelector.hotelChains).find(hotelSearchResultSelector.matCheckbox).eq(0).click();
    cy.get(hotelSearchResultSelector.acceptHotelReviewFilter).click();
  }

  static selectPrepayRoomFilter() {
    cy.allure().logStep('select prepay hotel room');
    cy.get(hotelSearchResultSelector.selectPrepayHotelRoomFilter).click();
  }

  static selectSupplierSpecificRoom(supplier: supplierType) {
    cy.wait('@waitForMapLoad');
    this.selectRoomOptionFromShowMoreOptions();
    cy.get(hotelSearchResultSelector.supplierImage(supplier), Timeouts.MAX_TIMEOUT_120_SEC)
      .eq(0)
      .parentsUntil(hotelSearchResultSelector.roomOptionHeader)
      .find(hotelSearchResultSelector.selectHotelRoom)
      .eq(0)
      .click({ force: true });
    cy.allure().logStep(`select ${supplier} room`);
  }

  static selectRoomOptionFromShowMoreOptions() {
    cy.get(hotelSearchResultSelector.hotelDetailPageLoading).should('be.visible');
    this.waitForLoadAllSelectButtons();
    cy.get(utilitySelector.bodySelector).then((showMoreRoomOptions) => {
      if (showMoreRoomOptions.find(hotelSearchResultSelector.showMoreOptions).length > 0) {
        cy.allure().logStep('select room option from show more options');
        cy.get(hotelSearchResultSelector.showMoreOptions, Timeouts.MAX_TIMEOUT_120_SEC).then((ele) => {
          cy.wrap(ele).scrollIntoView({ easing: 'linear' });
          cy.wrap(ele).click({ force: true });
        });
      } else {
        cy.log('no show more options');
        cy.allure().logStep('no show more options');
      }
    });
  }

  static openSeeMoreRoomOptions() {
    this.waitForLoadAllSelectButtons();
    cy.get(utilitySelector.bodySelector).then((seeMoreOptions) => {
      if (seeMoreOptions.find(hotelSearchResultSelector.seeMoreOptions).length > 0) {
        cy.allure().logStep('Select room option from see more options');
        cy.get(hotelSearchResultSelector.seeMoreOptions, Timeouts.MAX_TIMEOUT_120_SEC).each(($el) => {
          cy.wrap($el)
            .then(($el) => {
              cy.wrap($el).scrollIntoView({ easing: 'linear' });
            })
            .each((ele) => {
              cy.wrap(ele).click({ force: true });
            });
        });
      } else {
        cy.log('no see more options');
        cy.allure().logStep('no see more options');
      }
    });
  }

  static setCheckInCheckOutDate() {
    this.selectFutureDate(2);
  }

  static selectFutureDate(setNoOfMonthsLater: number) {
    cy.allure().logStep(`select ${setNoOfMonthsLater} months later hotel`);
    cy.get(hotelSearchResultSelector.calendarButton).eq(0).click();

    Cypress._.times(setNoOfMonthsLater, () => {
      cy.get(hotelSearchResultSelector.nextMonthCalendarButton).click();
    });

    cy.get(hotelSearchResultSelector.selectDate).contains('15').click();
    cy.get(hotelSearchResultSelector.selectDate).contains('17').click();
  }

  static selectHotelLocation(location: string) {
    cy.allure().logStep(`set hotel location: ${location}`);

    cy.get(hotelSearchResultSelector.hotelLocation).type(`${location}`);
    cy.wait('@waitForLocation');
    cy.get(hotelSearchResultSelector.dropdown).eq(0).click();
  }

  static searchHotel() {
    cy.allure().logStep('search hotel on hotel result page');
    cy.get(hotelSearchResultSelector.searchHotel).click();
    cy.wait('@waitForHotelSearch');
    Utility.expectSearchLoaderDisabled();
  }

  static editSearch() {
    cy.get(hotelSearchResultSelector.hotelNameInput).should('be.visible');
    cy.get(hotelSearchResultSelector.editSearch).click();
  }

  static selectRoom(num: number) {
    cy.get(hotelSearchResultSelector.roomSelect).click();
    if (num == 1) {
      cy.get(hotelSearchResultSelector.ariaLabelRoom(num)).click();
    } else {
      cy.get(hotelSearchResultSelector.ariaLabelRooms(num)).click();
    }
  }

  static waitForHotelResultPageToBeLoaded() {
    cy.wait('@waitForMapLoad');
    //! find the supplier
    cy.get('@waitForHotelSearch').then((xhr: any) => {
      const resPonseBody = xhr.response?.body;
      if (resPonseBody.options.length > 0) {
        cy.allure().logStep('Hotel result page is loaded');
      } else {
        cy.allure().logStep('Hotel result page is not loaded');
      }
    });
  }

  static chooseHotelFromMap() {
    cy.allure().logStep('choose hotel from map');
    cy.get(hotelSearchResultSelector.pickHotelFromMap, Timeouts.MEDIUM_TIMEOUT_60_SEC).eq(0).click();
    this.openNewHotelTab();
    cy.get(hotelSearchResultSelector.selectHotelFromMap).eq(0).click();
  }

  static selectPaymentSchedule(paymentSchedule: string) {
    cy.get(hotelSearchResultSelector.payment, Timeouts.MEDIUM_TIMEOUT_60_SEC).click();
    cy.get(hotelSearchResultSelector.selectFromDropDown).contains(`${paymentSchedule}`).click();
  }

  static verifyRecommendationsIsVisible() {
    cy.allure().logStep('wait and verify recommendations cards is visible');
    cy.wait('@waitForRecommendations').then((xhr) => {
      if ((xhr.response?.body?.length ?? 0) > 0) {
        cy.get(hotelSearchResultSelector.recommendedHotelsCards).should('be.visible');
      }
    });
  }

  static verifyTabsScroll() {
    this.calcHotelResultHeader().then((headerHeight: number) => {
      cy.wait(Timeouts.SHORT_TIMEOUT_5_SEC.timeout);
      this.clickOnTabAndVerifyScrollToSection('Amenities', 'Amenities', headerHeight);
      this.clickOnTabAndVerifyScrollToSection('Room options', 'Room options', headerHeight);
      this.clickOnTabAndVerifyScrollToSection('Overview', 'Overview', headerHeight);
      this.verifyIsHotelHaveReviewsAndClickOnReviewTab(headerHeight);
    });
  }

  /**
   * Check if there are no reviews in the reviews section
   * then get the height of the recommended hotel cards
   * and add to {@param headerHeight} and pass the sum of it to method {@link clickOnTabAndVerifyScrollToSection} as an additional height;
   * If the page contains reviews, then call this method {@see clickOnTabAndVerifyScrollToSection} with {@param headerHeight}.
   * @param headerHeight - It's a header height (expect sum of: application height + hotel page header + hotel subheader).
   */
  static verifyIsHotelHaveReviewsAndClickOnReviewTab(headerHeight: number) {
    cy.allure().logStep('verify is hotel not have reviews');
    cy.get(utilitySelector.bodySelector).then((body) => {
      if (body.find(hotelSearchResultSelector.hotelNoReviewLabel).length) {
        cy.get(hotelSearchResultSelector.hotelNoReviewLabel).then((noReviewLabelEl) => {
          this.getOuterHeightByElementSelector(hotelSearchResultSelector.recommendedHotelsCards).then(
            (outerHeightRecommendation: number) =>
              this.clickOnTabAndVerifyScrollToSection(
                'Reviews',
                noReviewLabelEl.text().trim(),
                outerHeightRecommendation + headerHeight
              )
          );
        });
      } else {
        this.clickOnTabAndVerifyScrollToSection('Reviews', 'Reviews', headerHeight);
      }
    });
  }

  /**
   * Calculate header height.
   * @return {Cypress.Chainable<number>} - Return a sum of: application height + hotel page header + hotel subheader.
   */
  static calcHotelResultHeader() {
    cy.allure().logStep('calculate hotel page headers');
    return this.getOuterHeightByElementSelector(hotelSearchResultSelector.taHeader)
      .then((headerHeight) =>
        this.getOuterHeightByElementSelector(hotelSearchResultSelector.taHotelHeader, headerHeight)
      )
      .then((hotelHeaderHeight) =>
        this.getOuterHeightByElementSelector(hotelSearchResultSelector.taHotelSubHeader, hotelHeaderHeight)
      );
  }

  static getOuterHeightByElementSelector(elementSelector: string, initialHeight = 0) {
    return cy
      .get(elementSelector)
      .invoke('outerHeight')
      .then((outerHeight) => (outerHeight ?? 0) + initialHeight);
  }

  /**
   * Click on navigation tab with {@param tabName} and wait scroll to section.
   * Find section with {@see sectionName} and get it distance to the top of
   * the page and check if it's close to the scrolled number of px.
   * @param tabName - Navigation tab name.
   * @param sectionName - Name of the section that is expected to be scrolled to after clicking on the tab.
   * @param additionalHeaderHeight - Header height in px. Maximum difference between the distance
   * of the element to the top and the scrolling in px.
   */
  static clickOnTabAndVerifyScrollToSection(tabName: tabType, sectionName: string, additionalHeaderHeight: number) {
    cy.allure().logStep(`verify after click on "${tabName}" tab it should scroll to the ${sectionName} section`);
    cy.get(hotelSearchResultSelector.navigationTabs).contains(tabName).click();

    cy.get(hotelSearchResultSelector.hotelDetailSections)
      .filter(`:contains("${sectionName}")`)
      .then((element) => element[0].offsetTop)
      .then((offsetTop) => {
        cy.window().its('scrollY').should('be.closeTo', offsetTop, additionalHeaderHeight);
      });
  }

  static getPayAtHotel(iteration = 10) {
    cy.get(hotelSearchResultSelector.paymentTypeDropDown).click();
    cy.get(utilitySelector.bodySelector).then(($lookForPayAtHotel) => {
      if ($lookForPayAtHotel.text().includes('Pay at hotel')) {
        cy.log('Pay at hotel found');
        cy.get(hotelSearchResultSelector.selectPaymentPayAtHotel).click();
        this.getRoom();
        cy.wait(Timeouts.SHORT_TIMEOUT_3_SEC.timeout);
        cy.get(utilitySelector.bodySelector).then((paymentAtCheckIn) => {
          if (paymentAtCheckIn.find(hotelSearchResultSelector.paymentAtCheckIn).length > 0) {
            cy.get(hotelSearchResultSelector.paymentAtCheckIn).contains('Continue').click();
            cy.allure().logStep('continue payment at check in');
          } else {
            cy.allure().logStep('payment not required');
          }
        });
        this.acceptHeadsUp();
      } else {
        cy.log('Pay at hotel room not found');
        cy.task('getDataFromCache', 'HotelSearchURL').then((searchURL: any) => {
          cy.visit(searchURL);
          HotelSearchResults.selectHotel(iteration);

          return this.getPayAtHotel(iteration - 1);
        });
      }
    });
  }

  static selectAvailableRoom(hotelPosition = 0) {
    this.syncUrlWithCache(hotelPosition);
    HotelSearchResults.selectHotel(hotelPosition);

    this.checkAvailableRoom().then((hasAvailableRoom: boolean) => {
      if (hasAvailableRoom) {
        this.getRoom();
        this.acceptPaymentAtCheckIn();
        this.acceptHeadsUp();
        this.acceptRoomRate();
      } else {
        // Retry next hotel from the search list.
        return this.selectAvailableRoom(hotelPosition + 1);
      }
    });
  }

  static selectPayAtHotelRoom(hotelPosition = 0) {
    this.syncUrlWithCache(hotelPosition);
    HotelSearchResults.selectHotel(hotelPosition);

    this.checkPaymentFilter('Pay at hotel').then((hasPayAtHotelFilter) => {
      if (hasPayAtHotelFilter) {
        this.selectPayAtHotelFilter();
        this.getRoom();
        this.acceptPaymentAtCheckIn();
        this.acceptHeadsUp();
        this.acceptRoomRate();
      } else {
        // Retry next hotel from the search list.
        return this.selectPayAtHotelRoom(hotelPosition + 1);
      }
    });
  }

  static selectPrepayRoom(hotelPosition = 0) {
    this.syncUrlWithCache(hotelPosition);
    HotelSearchResults.selectHotel(hotelPosition);

    this.checkPaymentFilter('Prepay').then((hasPrepayFilter) => {
      if (hasPrepayFilter) {
        this.selectPrepayFilter();
        this.getRoom();
        this.acceptHeadsUp();
        this.acceptRoomRate();
      } else {
        // Retry next hotel from the search list.
        return this.selectPrepayRoom(hotelPosition + 1);
      }
    });
  }

  static selectHotelByName(hotelName: string) {
    cy.wait('@waitForMapLoad');
    this.openNewHotelTab();
    cy.get(hotelSearchResultSelector.priceBlock, Timeouts.MEDIUM_TIMEOUT_60_SEC);
    this.closePopup();
    cy.get(hotelSearchResultSelector.hotelNameInput).type(hotelName);
    cy.get(hotelSearchResultSelector.selectHotel, Timeouts.MEDIUM_TIMEOUT_60_SEC).eq(0).click({ force: true });
    cy.allure().logStep(`select ${0} hotel`);
  }

  static excludeProvidersFromRoomsList(excludeProviders: string[] = []) {
    cy.intercept('GET', RegExp(`hotel/searches/${guidRegex}/hotels/${guidRegex}`), (req) => {
      if (excludeProviders?.length) {
        req.on('response', (res) => {
          if (res.body.rooms?.length) {
            res.body.rooms = res.body.rooms.filter(
              (room: { provider: string }) => excludeProviders.indexOf(room.provider) === -1
            );
          }
        });
      }
    }).as('hotelDetails');
  }

  static getDatesFromCalendar() {
    cy.get(hotelSearchResultSelector.selectHotel, Timeouts.MEDIUM_TIMEOUT_60_SEC).should('be.visible');
    cy.get(hotelSearchResultSelector.getDatesFromCalendar)
      .should('not.include.text', 'Please select')
      .invoke('text')
      .then((date) => {
        cy.log(date);
        cy.task('putDataInCache', {
          key: 'getDatesFromCalendar',
          data: date.trim()
        });
      });
  }

  static getHotelLocationInput() {
    cy.get(hotelSearchResultSelector.selectHotel, Timeouts.MEDIUM_TIMEOUT_60_SEC).should('be.visible');
    cy.get(hotelSearchResultSelector.hotelLocationInput)
      .invoke('text')
      .then((getHotelLocationInputText) => {
        cy.task('putDataInCache', {
          key: 'getHotelLocationInput',
          data: getHotelLocationInputText
        });
      });
  }

  static saveUrlInCache(key: string) {
    cy.url().then((data: string) => {
      cy.log('<<< Hotel search URL is >>>: ' + data);
      cy.task('putDataInCache', { key, data });
    });
  }

  static visitUrlFromCache(key: string) {
    cy.task('getDataFromCache', key).then((url: any) => cy.visit(url));
  }

  static syncUrlWithCache(retry = 0) {
    const key = 'HotelSearchURL';
    if (retry === 0) {
      this.saveUrlInCache(key);
    } else {
      this.visitUrlFromCache(key);
    }
  }

  static checkPaymentFilter(filterName: string) {
    cy.get(hotelSearchResultSelector.paymentTypeDropDown).click();
    return cy.get(utilitySelector.bodySelector).then((body) => body.text().includes(filterName));
  }

  static selectPayAtHotelFilter() {
    cy.get(hotelSearchResultSelector.selectPaymentPayAtHotel).click();
  }

  static selectPrepayFilter() {
    cy.get(hotelSearchResultSelector.selectPaymentPrepay).click();
  }

  static acceptPaymentAtCheckIn() {
    cy.wait(Timeouts.SHORT_TIMEOUT_3_SEC.timeout);
    cy.get(utilitySelector.bodySelector).then((body) => {
      if (body.find(hotelSearchResultSelector.paymentAtCheckIn).length > 0) {
        cy.get(hotelSearchResultSelector.paymentAtCheckIn).contains('Continue').click();
      }
    });
  }

  static checkAvailableRoom() {
    return cy
      .get(utilitySelector.bodySelector)
      .then((body) => body.find(hotelSearchResultSelector.selectHotelRoom).length > 0);
  }
}
