import { HttpRequest, Accept, HttpStatusCodes } from '../base';
import { AUTH_TOKEN } from '../commands';
import { HotelSearchClient } from './lib';
import { BookingParams } from '@fixtures/travelxen/booking_data';

Cypress.Commands.add('createHotelBooking', (bookingParams: BookingParams) => {
  let userId: string;
  cy.log('create hotel booking for onboard company');
  cy.task('getDataFromCache', 'userResponse').then((response: any) => {
    userId = response.uuid;
  });
  const { location, checkInDate, checkOutDate, tripName, reason, tripId } = bookingParams;
  getHotelLocation(location)
    .then((placeId) =>
      hotelSearchWithPlaceId({
        placeId,
        userId,
        description: location,
        checkInDate,
        checkOutDate
      })
    )
    .then((searchId) => hotelSearchesWithSearchId(searchId))
    .then((searchResponse) => getHotelRates(searchResponse))
    .then((hotelRatesResponse) => contract(hotelRatesResponse))
    .then((contractId) => getPaymentMethod(contractId))
    .then(({ paymentMethodId, contractId }) =>
      book({ paymentId: paymentMethodId, contractId, tripName, reason, tripId, userId })
    );
});

Cypress.Commands.add('approveBooking', (bookingId) => {
  approveMyBooking(bookingId);
});

Cypress.Commands.add('declineBooking', (bookingId) => {
  declineMyBooking(bookingId);
});

function getHotelLocation(location: string) {
  cy.allure().logStep('retrieve hotel locations');

  return cy
    .request({
      method: HttpRequest.Get,
      url: HotelSearchClient.location.replace('{placeInput}', location),
      headers: {
        Authorization: AUTH_TOKEN,
        Accept: Accept.ApplicationJson
      },
      retryOnStatusCodeFailure: true,
      qs: {
        includeFavoriteHotels: true,
        addLocation: true
      }
    })
    .then(({ body, status }) => {
      expect(status, 'location results retrieved').to.equal(HttpStatusCodes.Ok);
      return body.predictions[0].location.placeId;
    });
}

function hotelSearchWithPlaceId(hotelSearchParams: {
  placeId: string;
  userId: string;
  description: string;
  checkInDate: string;
  checkOutDate: string;
}) {
  cy.allure().logStep('hotel search with placeId');
  const { placeId, userId, description, checkInDate, checkOutDate } = hotelSearchParams;
  return cy
    .request({
      method: HttpRequest.Get,
      url: HotelSearchClient.search,
      headers: {
        Authorization: AUTH_TOKEN,
        ContentType: Accept.ApplicationJson
      },
      retryOnStatusCodeFailure: true,
      qs: {
        includeSpecialRates: true,
        placeId: placeId,
        description: description,
        checkInDate: checkInDate,
        checkOutDate: checkOutDate,
        numberOfRooms: 1,
        numberOfGuests: 1,
        travelerUuids: userId,
        prefetchRooms: true,
        emptyData: false
      }
    })
    .then(({ body, status }) => {
      expect(status, 'search with placeId results retrieved').to.equal(
        HttpStatusCodes.Ok
      );
      return body.searchId;
    });
}

function hotelSearchesWithSearchId(searchId: string) {
  cy.allure().logStep('hotel search with searchId');
  cy.task('putDataInCache', { key: 'searchId', data: searchId });

  return cy
    .request({
      method: HttpRequest.Get,
      url: HotelSearchClient.search + `/${searchId}`,
      headers: {
        Authorization: AUTH_TOKEN,
        Accept: Accept.ApplicationJson
      },
      retryOnStatusCodeFailure: true
    })
    .then(({ body, status }) => {
      expect(status, 'search with searchId results retrieved').to.equal(
        HttpStatusCodes.Ok
      );
      return body;
    });
}

function getHotelRates(searchResponse: any) {
  cy.allure().logStep('get hotel rates');

  return cy
    .request({
      method: HttpRequest.Get,
      url:
        HotelSearchClient.search +
        `/${searchResponse.searchId}/hotels/${searchResponse.options[0].uuid}/rates`,
      headers: {
        Authorization: AUTH_TOKEN,
        Accept: Accept.ApplicationJson
      },
      retryOnStatusCodeFailure: true
    })
    .then(({ body, status }) => {
      expect(status, 'hotel rates retrieved').to.equal(HttpStatusCodes.Ok);
      return body;
    });
}

function contract(hotelRatesResponse: any) {
  cy.allure().logStep('create contract');

  const contractUrl = String(hotelRatesResponse._links.rates.href).replace(
    'rates',
    `rooms/${hotelRatesResponse.rooms[0].uuid}/contract`
  );

  return cy
    .request({
      method: HttpRequest.Post,
      url: contractUrl,
      headers: {
        Authorization: AUTH_TOKEN,
        Accept: Accept.ApplicationJson
      },
      retryOnStatusCodeFailure: true
    })
    .then(({ body, status }) => {
      expect(status, 'hotel contract created').to.equal(HttpStatusCodes.Ok);
      return body.uuid;
    });
}

function getPaymentMethod(contractId: any) {
  cy.allure().logStep('get payment method');

  return cy
    .request({
      method: HttpRequest.Get,
      url: HotelSearchClient.hotelPaymentMethod.replace('{contractId}', contractId),
      headers: {
        Authorization: AUTH_TOKEN,
        Accept: Accept.ApplicationJson
      },
      retryOnStatusCodeFailure: true
    })
    .then(({ body, status }) => {
      expect(status, 'payment method retrieved').to.equal(HttpStatusCodes.Ok);
      const paymentMethodId = body[0].uuid;
      return { paymentMethodId, contractId };
    });
}

function book(bookParams: {
  paymentId: string;
  contractId: string;
  tripName: string;
  reason: string;
  tripId: string;
  userId: string;
}) {
  cy.allure().logStep('hotel book');
  const { paymentId, contractId, tripName, reason, tripId, userId } = bookParams;
  let bookingUrl: string;
  cy.task('getDataFromCache', 'searchId')
    .then((searchId: any) => {
      bookingUrl =
        HotelSearchClient.search +
        `/${searchId}/contracts/${contractId}/book/streaming?paymentMethodUuid=${paymentId}&tripId=${tripId}&tripName=${tripName}&reason=${reason}&locale=en-US`;
    })
    .then(() => {
      return cy
        .request({
          method: HttpRequest.Post,
          url: bookingUrl,
          headers: {
            Authorization: AUTH_TOKEN,
            Accept: Accept.ApplicationJson
          },
          body: {
            passengerData: [
              {
                passenger: {
                  uuid: userId
                }
              }
            ]
          },
          retryOnStatusCodeFailure: true
        })
        .then(({ body, status }) => {
          expect(status, 'hotel booking created').to.equal(HttpStatusCodes.Ok);
          const regex = /"bookingId" : "(.*)"/;
          cy.task('putDataInCache', {
            key: 'bookingConfirmation',
            data: body.match(regex)[1]
          });
        });
    });
}

function approveMyBooking(bookingId: string) {
  if (AUTH_TOKEN) {
    cy.request({
      method: 'POST',
      url: `/api/admin/bookingApprovals/${bookingId}/approve`,
      headers: {
        Authorization: AUTH_TOKEN
      },
      retryOnStatusCodeFailure: true
    }).then(({ status }) => {
      expect(status, 'Booking is approved').to.equal(200);
    });
  } else {
    throw new Error('Please login before calling api endpoints');
  }
}

function declineMyBooking(bookingId: string) {
  if (AUTH_TOKEN) {
    cy.request({
      method: 'POST',
      url: `/api/admin/bookingApprovals/${bookingId}/reject?reason=Out_of_policy`,
      headers: {
        Authorization: AUTH_TOKEN
      },
      retryOnStatusCodeFailure: true
    }).then(({ status }) => {
      expect(status, 'Booking is declined').to.equal(200);
    });
  } else {
    throw new Error('Please login before calling api endpoints');
  }
}
