import {
  Flights,
  FlightSearchResults,
  flightSearchResultSelector,
  FlightTripType
} from '@flight-lib/index';
import { LocationUtility, Timeouts, Utility, utilitySelector } from '@utility-lib/index';
import { FlightBookingParams, FlightUtility } from '@flight-modal/index';

type CallbackFunction<T> = (value: T) => void;
type AirportRetryFunctions = {
  airportForEventFn: () => string;
  airportsForBaseFlowFn: () => string[];
};

export class FlightReSearchUtility {
  static isNoResultFoundWrapper(
    callbackFn: CallbackFunction<FlightBookingParams>,
    params: FlightBookingParams
  ) {
    cy.get(
      flightSearchResultSelector.flightResultLoadedCount,
      Timeouts.MAX_TIMEOUT_120_SEC
    ).should('exist');
    FlightSearchResults.isNoResultFound().then((isNoResultFound) => {
      if (isNoResultFound) {
        FlightReSearchUtility.switchLocationIfSearchResultEmpty(params);
      } else {
        callbackFn(params);
      }
    });
  }

  static initAirportFnsForBookingParams(
    flightTripType: FlightTripType
  ): AirportRetryFunctions {
    const airportsForBaseFlowFn = FlightUtility.isItMultiCityType(flightTripType)
      ? LocationUtility.getOriginsDestinationsForMultiCity
      : LocationUtility.getPairOfOriginAndDestinationAirportsUS;
    const airportForEventFn = LocationUtility.getAirportUS;
    return {
      airportsForBaseFlowFn,
      airportForEventFn
    };
  }

  static updateFlightSearchLocations(
    flightParams: FlightBookingParams,
    locations: string[]
  ) {
    flightParams.locations = locations;
  }

  static switchLocationIfSearchResultEmpty(
    flightParams: FlightBookingParams,
    maxNumOfAttempt = 3
  ) {
    cy.get(utilitySelector.bodySelector).then((checkIfFlightSoldOut) => {
      if (FlightReSearchUtility.isFlightSoldOutOrNoFlightFound(checkIfFlightSoldOut)) {
        const logMessage = `Change locations because of the problem with the availability of tickets. Attempt: ${
          flightParams.countAttempt + 1
        }`;
        cy.allure().logStep(logMessage);
        cy.log(logMessage);
        if (flightParams.countAttempt > maxNumOfAttempt) {
          const errorMessage = `>>>>>>>>> Cannot find available flights after ${
            maxNumOfAttempt + 1
          } attempts Please check the Provider <<<<<<<<<<`;
          cy.allure().logStep(errorMessage);
          cy.log(errorMessage);
          throw Error(errorMessage);
        }
        flightParams.countAttempt++;
        Utility.waitUntilWarningMsgDisabled();
        FlightReSearchUtility.setLocationsForNewSearch(flightParams);
      }
    });
  }

  static isFlightSoldOutOrNoFlightFound(body: JQuery<HTMLElement>): boolean {
    return (
      body.find(utilitySelector.warningMsg).length > 0 ||
      body.find(flightSearchResultSelector.flightNoResultSection).length > 0
    );
  }

  static setLocationsForNewSearch(flightParams: FlightBookingParams) {
    cy.allure().logStep(
      `${flightParams.flightTripType}: Start a new search with other locations`
    );
    switch (flightParams.flightTripType) {
      case FlightTripType.ONEWAY: {
        FlightReSearchUtility.retrySearchForOneWay(flightParams);
        break;
      }
      case FlightTripType.ROUNDTRIP: {
        FlightReSearchUtility.retrySearchForRoundTrip(flightParams);
        break;
      }
      case FlightTripType.MULTICITY_BUNDLE:
      case FlightTripType.MULTICITY_CUSTOM: {
        FlightReSearchUtility.retrySearchForMultiCity(flightParams);
        break;
      }
    }
  }

  static retrySearchForOneWay(flightParams: FlightBookingParams) {
    this.reselectLocationsAndSearch(flightParams);
    FlightSearchResults.onewaySearchResult(flightParams);
  }

  static retrySearchForRoundTrip(flightParams: FlightBookingParams) {
    this.reselectLocationsAndSearch(flightParams);
    FlightSearchResults.roundtripSearchResult(flightParams);
  }

  static reselectLocationsAndSearch(flightParams: FlightBookingParams) {
    cy.allure().logStep('Reselect locations for new search');
    FlightSearchResults.clickOnFlightHeaderSearchInfoPanel(false);
    if (flightParams.isItEventSearch) {
      this.reselectForEventSearch(flightParams);
    } else {
      FlightReSearchUtility.updateFlightSearchLocations(
        flightParams,
        flightParams.airportsForBaseFlowRetryFn()
      );
      Flights.searchWithLocations(flightParams.locations);
    }
    Utility.expectSearchLoaderDisabled();
  }

  static reselectForEventSearch(flightParams: FlightBookingParams) {
    Flights.getValueFromLocationInput(1).then((destinationInput) => {
      if (!destinationInput) {
        const errorMessage = 'Something went wrong. The destination must not be empty';
        cy.allure().logStep(errorMessage);
        throw Error(errorMessage);
      }
      /*
       Always use IATA code, since the airport definition in airports_us.ts sometimes doesn't match the result from airport resolving service.
       eg, 'DFW - Dallas/Fort Worth International Airport, Dallas' .vs. 'DFW - Dallas/Fort Worth Intl Airport, Dallas, Texas, United States'
       */
      let originLocation = flightParams.airportForEventRetryFn();
      const destAirportCode = (destinationInput as string).substring(0, 3);
      let originAirportCode = originLocation.substring(0, 3);
      while (destAirportCode === originAirportCode) {
        originLocation = flightParams.airportForEventRetryFn();
        originAirportCode = originLocation.substring(0, 3);
      }
      FlightReSearchUtility.updateFlightSearchLocations(flightParams, [originLocation]);
      Flights.setOrigin(originAirportCode);
      Flights.clickSearchGuestButton();
    });
  }

  static retrySearchForMultiCity(flightParams: FlightBookingParams) {
    FlightReSearchUtility.updateFlightSearchLocations(
      flightParams,
      flightParams.airportsForBaseFlowRetryFn()
    );
    FlightSearchResults.clickOnFlightHeaderSearchInfoPanel(true);
    Flights.setLocations(flightParams.locations);
    Flights.searchFlight();
    Utility.expectSearchLoaderDisabled();
    if (flightParams.flightTripType === FlightTripType.MULTICITY_BUNDLE) {
      FlightSearchResults.bundleSearchResult(flightParams);
    } else {
      FlightSearchResults.multicityFlexSearchResult(flightParams);
    }
  }
}
