import { PayloadAction, createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import {
  BookingAttributes,
  BookingOptions,
  GroupedFlights,
  ProductAttributes,
} from "../../types";

import {
  AirlineBookingPackageOption,
  AirportBookingPackageOption,
  BookingAirlineGroup,
  BookingAirportGroup,
  BookingOptionGroup,
  BookingOptionPax,
  BookingOptionUnit,
  BookingPackage,
  BookingPackageDetailsRequest,
  BookingPackageFlight,
  BookingPackagePax,
  BookingPackageRequest,
  BookingPackageRequestRoom,
  BookingPackageRoom,
  BookingTravelAgent,
  GenerateBookingAccommodationRequest,
  PerBookingPackageOption,
} from "@qite/tide-client/build/types";
import { first, isEmpty, isNil, range } from "lodash";
import { RootState } from "../../store";
import { selectAgentId } from "../travelers-form/travelers-form-slice";
import packageApi from "./api";
import { BookingStep, OPTIONS_FORM_STEP } from "./constants";
import {
  selectAccommodationCodes,
  selectBookingAttributes,
  selectBookingRooms,
  selectLanguageCode,
  selectOfficeId,
  selectProductAttributes,
  selectProductCode,
} from "./selectors";

export interface BookingState {
  officeId: number;
  languageCode: string;
  productAttributes?: ProductAttributes;
  bookingAttributes?: BookingAttributes;
  calculateDeposit: boolean;
  bookingNumber?: string;
  isRetry: boolean;
  package?: BookingPackage;
  agents?: BookingTravelAgent[];
  isBusy: boolean;
  skipPaymentWithAgent: boolean;
  generatePaymentUrl: boolean;
  isUnavailable?: boolean;
  tagIds?: number[];
  agentAdressId?: number;
  remarks?: string;
  voucherCodes?: string[];
  bookingOptions: BookingOptions;
  bookingType: string;
  currentStep: BookingStep;
  translations?: {
    language: string;
    value: any;
  }[];
  accommodationViewId?: number;
  accommodationViews?: { [key: string]: string };
}

const initialState: BookingState = {
  officeId: 1,
  languageCode: "nl-BE",
  bookingOptions: {
    b2b: {
      tagIds: [],
      entryStatus: 2,
      customEntryStatusId: undefined,
    },
    b2b2c: {
      tagIds: [],
      entryStatus: 2,
      customEntryStatusId: undefined,
    },
    b2c: {
      tagIds: [],
      entryStatus: 0,
      customEntryStatusId: undefined,
    },
  },
  bookingType: "b2c",
  productAttributes: undefined,
  bookingAttributes: undefined,
  calculateDeposit: false,
  bookingNumber: undefined,
  isRetry: false,
  package: undefined,
  isBusy: false,
  skipPaymentWithAgent: false,
  generatePaymentUrl: false,
  tagIds: [],
  agentAdressId: undefined,
  currentStep: OPTIONS_FORM_STEP,
  translations: undefined,
};

export const fetchPackage = createAsyncThunk(
  "booking/fetchPackage",
  async (_, { dispatch }) => {
    dispatch(setFetchingPackage(true));
    await dispatch(fetchAgents());
    await dispatch(fetchPackageDetails());
    await dispatch(fetchAccommodationViews());
    dispatch(setFetchingPackage(false));
  }
);

const fetchAgents = createAsyncThunk(
  "booking/agents",
  async (_, { dispatch, getState, signal }) => {
    const settings = getState() as RootState;
    return await packageApi.fetchAgents(signal, settings.apiSettings);
  }
);

const fetchPackageDetails = createAsyncThunk(
  "booking/details",
  async (_, { dispatch, getState, signal }) => {
    const state = getState() as RootState;

    const officeId = selectOfficeId(state);
    const productAttributes = selectProductAttributes(state);
    const bookingAttributes = selectBookingAttributes(state);
    const agentId = selectAgentId(state);
    const rooms = selectBookingRooms(state);
    const languageCode = selectLanguageCode(state);

    if (isNil(productAttributes)) {
      throw Error("productAttributes could not be found");
    }

    if (isNil(bookingAttributes)) {
      throw Error("bookingAttributes could not be found");
    }

    if (!rooms?.length) {
      throw Error("rooms could not be found");
    }

    var requestRooms = rooms?.map((x, i) => {
      var room = { index: i, pax: [] } as BookingPackageRequestRoom;
      range(0, x.adults).forEach(() => {
        room.pax.push({
          age: 30,
        } as BookingPackagePax);
      });
      x.childAges.forEach((x) => {
        room.pax.push({
          age: x,
        } as BookingPackagePax);
      });
      return room;
    });

    const isAllotment =
      bookingAttributes.tourCode ||
      bookingAttributes.allotmentName ||
      (bookingAttributes.allotmentIds && bookingAttributes.allotmentIds.length);

    const request = {
      officeId: officeId,
      agentId: agentId,
      payload: {
        searchType: isAllotment ? 1 : 0,
        catalogueId: bookingAttributes.catalog,
        productCode: productAttributes.productCode,
        fromDate: bookingAttributes.startDate,
        toDate: bookingAttributes.endDate,
        includeFlights: bookingAttributes.includeFlights,
        allotmentName: bookingAttributes.allotmentName,
        allotmentIds: bookingAttributes.allotmentIds ?? [],
        tourCode: bookingAttributes.tourCode,
        rooms: requestRooms,
      } as BookingPackageDetailsRequest,
    } as BookingPackageRequest<BookingPackageDetailsRequest>;

    return await packageApi.fetchDetails(
      request,
      signal,
      languageCode,
      state.apiSettings
    );
  }
);

const fetchAccommodationViews = createAsyncThunk(
  "booking/accommodationViews",
  async (_, { dispatch, getState, signal }) => {
    const state = getState() as RootState;
    if (!state.booking.accommodationViewId) return Promise.resolve();

    const languageCode = selectLanguageCode(state);
    const accommodationCodes = selectAccommodationCodes(state);
    const productCode = selectProductCode(state);

    if (!productCode) {
      throw Error('No product selected');
    }

    const request = {
      languageCode: languageCode,
      productCode: productCode,
      accommodationCodes: accommodationCodes,
      contentViewId: state.booking.accommodationViewId
    } as GenerateBookingAccommodationRequest;

    return await packageApi.fetchAccommodationViews(request, signal, state.apiSettings);
  }
);

const getActiveOption = (state: BookingState) => {
  if (state.package) return state.package.options.find((x) => x.isSelected);
  return null;
};

const changeOutwardFlight = (state: BookingPackage, flight: BookingPackageFlight) => {
  const currentOutwardFlight = state.outwardFlights.find(x => x.isSelected)!;
  const currentReturnFlight = state.returnFlights.find(x => x.isSelected)!;

  if (currentOutwardFlight?.entryLineGuid == flight.entryLineGuid) return;

  const newFlight = state.outwardFlights.find(x => x.entryLineGuid == flight.entryLineGuid);
  if (newFlight) {
    newFlight.isSelected = true;
    currentOutwardFlight.isSelected = false;

    if (newFlight.externalGuid) {
      if (currentOutwardFlight.externalGuid !== newFlight.externalGuid) {
        const newReturnFlight = state.returnFlights.find(x => x.externalGuid === newFlight.externalGuid)!;

        currentReturnFlight.isSelected = false;
        newReturnFlight.isSelected = true;
      }
    } else if (currentReturnFlight.externalGuid) {
      const firstInternal = state.returnFlights.find(x => !x.externalGuid);

      if (firstInternal) {
        currentReturnFlight.isSelected = false;
        firstInternal.isSelected = true;
      }
    }
  }
}

const changeReturnFlight = (state: BookingPackage, flight: BookingPackageFlight) => {
  const currentReturnFlight = state.returnFlights.find(x => x.isSelected)!;

  if (currentReturnFlight?.entryLineGuid == flight.entryLineGuid) return;

  const newFlight = state.outwardFlights.find(x => x.entryLineGuid == flight.entryLineGuid);
  if (newFlight) {
    newFlight.isSelected = true;
    currentReturnFlight.isSelected = false;
  }
}

const changePackageOption = (state: BookingPackage) => {
  const selectedOutward = state.outwardFlights.find(x => x.isSelected)!;
  const selectedReturn = state.returnFlights.find(x => x.isSelected)!;
  const validOptions = selectedOutward.validOptions.filter(x => selectedReturn.validOptions.some(y => x === y));

  const currentOption = state.options.find(x => x.isSelected)!;
  if (validOptions.some(x => x === currentOption.id)) return;

  const firstOption = state.options.find(x => validOptions.some(y => y === x.id))!;
  currentOption.isSelected = false;
  firstOption.isSelected = true;

  const currentRooms = currentOption.rooms.map(r => {
    const selectedOption = r.options.find(o => o.isSelected)!;

    return {
      accommodation: selectedOption?.accommodationCode,
      regime: selectedOption?.regimeCode
    };
  });

  firstOption.rooms.forEach((r, i) => {
    const currentRoom = currentRooms[i];

    const selectedOption = r.options.find(o => o.isSelected);
    const selection = r.options.find(x => x.accommodationCode === currentRoom.accommodation && x.regimeCode === currentRoom.regime);

    if (selection) {
      if (selection.entryLineGuid !== selectedOption?.entryLineGuid) {
        if (selectedOption) selectedOption.isSelected = false;
        selection.isSelected = true;
      }
    } else {
      const accommodationSelection = r.options.find(x => x.accommodationCode === currentRoom.accommodation);
      if (accommodationSelection) {
        if (accommodationSelection.entryLineGuid !== selectedOption?.entryLineGuid) {
          if (selectedOption) selectedOption.isSelected = false;
          accommodationSelection.isSelected = true;
        }
      } else {
        const firstOption = r.options[0];
        if (firstOption.entryLineGuid !== selectedOption?.entryLineGuid) {
          if (selectedOption) selectedOption.isSelected = false;
          firstOption.isSelected = true;
        }
      }
    }
  });
}

const bookingSlice = createSlice({
  name: "booking",
  initialState,
  reducers: {
    setOfficeId(state, action: PayloadAction<number>) {
      state.officeId = action.payload;
    },
    setLanguageCode(state, action: PayloadAction<string>) {
      state.languageCode = action.payload;
    },
    setTranslations(state, action: PayloadAction<any>) {
      state.translations = action.payload;
    },
    setBookingOptions(state, action: PayloadAction<BookingOptions>) {
      state.bookingOptions = action.payload;
    },
    setBookingType(state, action: PayloadAction<string>) {
      state.bookingType = action.payload;
    },
    setProductAttributes(state, action: PayloadAction<ProductAttributes>) {
      state.productAttributes = action.payload;
    },
    setBookingAttributes(state, action: PayloadAction<BookingAttributes>) {
      state.bookingAttributes = action.payload;
    },
    setCalculateDeposit(state, action: PayloadAction<boolean>) {
      state.calculateDeposit = action.payload;
    },
    setBookingNumber(state, action: PayloadAction<string>) {
      state.bookingNumber = action.payload;
    },
    setIsRetry(state, action: PayloadAction<boolean>) {
      state.isRetry = action.payload;
    },
    setFetchingPackage(state, action: PayloadAction<boolean>) {
      state.isBusy = action.payload;
    },
    setPackage(state, action: PayloadAction<BookingPackage>) {
      state.package = action.payload;
    },
    setPackageRooms(state, action: PayloadAction<BookingPackageRoom[]>) {
      const option = getActiveOption(state);
      if (option) option.rooms = action.payload;
    },
    setPackageOptionPax(state, action: PayloadAction<BookingOptionPax[]>) {
      const option = getActiveOption(state);
      if (option) option.optionPax = action.payload;
    },
    setPackageOptionUnits(state, action: PayloadAction<BookingOptionUnit[]>) {
      const option = getActiveOption(state);
      if (option) option.optionUnits = action.payload;
    },
    setSkipPayment(state, action: PayloadAction<boolean>) {
      state.skipPaymentWithAgent = action.payload;
    },
    setGeneratePaymentUrl(state, action: PayloadAction<boolean>) {
      state.generatePaymentUrl = action.payload;
    },
    setPackageGroups(
      state,
      action: PayloadAction<BookingOptionGroup<PerBookingPackageOption>[]>
    ) {
      const option = getActiveOption(state);
      if (option) option.groups = action.payload;
    },
    setPackageAirlineGroups(
      state,
      action: PayloadAction<BookingAirlineGroup<AirlineBookingPackageOption>[]>
    ) {
      const option = getActiveOption(state);
      if (option) option.airlineGroups = action.payload;
    },
    setPackageAirportGroups(
      state,
      action: PayloadAction<BookingAirportGroup<AirportBookingPackageOption>[]>
    ) {
      const option = getActiveOption(state);
      if (option) option.airportGroups = action.payload;
    },
    setTagIds(state, action: PayloadAction<number[] | undefined>) {
      state.tagIds = action.payload;
    },
    setAgentAdressId(state, action: PayloadAction<number | undefined>) {
      state.agentAdressId = action.payload;
    },
    setBookingRemarks(state, action: PayloadAction<string>) {
      state.remarks = action.payload;
    },
    setVoucherCodes(state, action: PayloadAction<string[]>) {
      state.voucherCodes = action.payload;
    },
    setCurrentStep(state, action: PayloadAction<BookingStep>) {
      document.body.scrollTop = 0; // For Safari
      document.documentElement.scrollTop = 0; // For Chrome, Firefox, IE and Opera

      state.currentStep = action.payload;
    },
    setFlights(state, action: PayloadAction<GroupedFlights>) {
      if (!state.package) return;

      changeOutwardFlight(state.package, action.payload.selectedOutward);
      changeReturnFlight(state.package, action.payload.selectedReturn);
      changePackageOption(state.package);
    },
    setAccommodationViewId(state, action: PayloadAction<number>) {
      state.accommodationViewId = action.payload;
    }
  },
  extraReducers: (builder) => {
    builder.addCase(fetchPackageDetails.fulfilled, (state, action) => {
      if (action.payload) {
        if (action.payload.errorCode) {
          console.error(
            action.payload.errorCode,
            action.payload.errorMessage,
            action.payload.errorDetails
          );

          state.isUnavailable = true;
          return;
        }

        if (!action.payload.payload) {
          state.isUnavailable = true;
          return;
        }

        const bookingRooms = state.bookingAttributes?.rooms;
        const flight = state.bookingAttributes?.flight;
        const packageDetails = action.payload.payload;

        let activeOption = packageDetails.options.find((x) => x.isSelected)!;

        if (flight) {
          const selectedOutward = packageDetails.outwardFlights.find(
            (x) => x.isSelected
          );
          const outwardFlights = packageDetails.outwardFlights.filter(
            (x) =>
              x.code === flight.outwardCode &&
              x.flightMetaData.flightLines[0]!.flightClass ===
              flight.outwardClass &&
              x.flightMetaData.flightLines.map((y) => y.number).join(",") ===
              flight.outwardNumbers.join(",")
          );

          // ook bij identieke vertrekvluchten eerst kijken als de returnflight kan gekoppeld worden.
          // op die manier moet het juiste koppel selected worden.
          // Enkel werkend met externe vluchten
          let outwardFlight: BookingPackageFlight | undefined = undefined;
          if (!isEmpty(outwardFlights)) {
            const returnExternalGuids = packageDetails.returnFlights
              .filter(
                (x) =>
                  x.code === flight.returnCode &&
                  x.flightMetaData.flightLines[0]!.flightClass ===
                  flight.returnClass &&
                  x.flightMetaData.flightLines
                    .map((y) => y.number)
                    .join(",") === flight.returnNumbers.join(",")
              )
              .map((f) => f.externalGuid)
              .filter((e) => e);
            outwardFlight = outwardFlights.find((o) =>
              returnExternalGuids.includes(o.externalGuid)
            );
            if (!outwardFlight) {
              outwardFlight = first(outwardFlights);
            }
          }

          if (selectedOutward && outwardFlight) {
            selectedOutward.isSelected = false;
            outwardFlight.isSelected = true;
          }

          const selectedReturn = packageDetails.returnFlights.find(
            (x) => x.isSelected
          );
          const returnFlight = outwardFlight?.externalGuid
            ? packageDetails.returnFlights.find(
              (x) => x.externalGuid === outwardFlight?.externalGuid
            )
            : packageDetails.returnFlights.find(
              (x) =>
                x.code === flight.returnCode &&
                x.flightMetaData.flightLines[0]!.flightClass ===
                flight.returnClass &&
                x.flightMetaData.flightLines
                  .map((y) => y.number)
                  .join(",") === flight.returnNumbers.join(",")
            );

          if (selectedReturn && returnFlight) {
            selectedReturn.isSelected = false;
            returnFlight.isSelected = true;
          }

          if (outwardFlight && returnFlight) {
            if (!outwardFlight.validOptions.some((x) => x == activeOption.id)) {
              activeOption.isSelected = false;

              activeOption = packageDetails.options.find((x) =>
                outwardFlight?.validOptions.some((y) => y === x.id)
              )!;
              activeOption.isSelected = true;
            }
          }
        }

        if (
          activeOption &&
          bookingRooms?.some((x) => x.accommodationCode || x.regimeCode)
        ) {
          bookingRooms.forEach((room, i) => {
            if (room.accommodationCode || room.regimeCode) {
              activeOption.rooms[i].options = activeOption.rooms[i].options.map(
                (ro) => ({
                  ...ro,
                  isSelected:
                    ro.accommodationCode == room.accommodationCode &&
                    ro.regimeCode == room.regimeCode,
                })
              );
            }

            // Fallback to an option that has the requested accommodation OR regime if the requested option is not available. If no fallback is available, select the first option.
            if (!activeOption.rooms[i].options.some(x => x.isSelected)) {
              const fallbackOption = activeOption.rooms[i].options.find(x => x.accommodationCode == room.accommodationCode || x.regimeCode == room.regimeCode);
              if (fallbackOption) {
                fallbackOption.isSelected = true;
              } else {
                activeOption.rooms[i].options[0].isSelected = true;
              }
            }
          });
        }

        state.package = packageDetails;
      }
    });
    builder.addCase(fetchAgents.fulfilled, (state, action) => {
      if (action.payload) {
        state.agents = action.payload;
      }
    });
    builder.addCase(fetchAccommodationViews.fulfilled, (state, action) => {
      if (action.payload) {
        state.accommodationViews = action.payload;
      }
    });
  },
});

export const {
  setOfficeId,
  setLanguageCode,
  setTranslations,
  setBookingOptions,
  setBookingType,
  setProductAttributes,
  setBookingAttributes,
  setCalculateDeposit,
  setBookingNumber,
  setIsRetry,
  setFetchingPackage,
  setPackage,
  setPackageRooms,
  setPackageOptionPax,
  setPackageOptionUnits,
  setPackageGroups,
  setSkipPayment,
  setGeneratePaymentUrl,
  setTagIds,
  setAgentAdressId,
  setBookingRemarks,
  setVoucherCodes,
  setCurrentStep,
  setPackageAirlineGroups,
  setPackageAirportGroups,
  setFlights,
  setAccommodationViewId
} = bookingSlice.actions;

export default bookingSlice.reducer;

