import {
  BookingPackageFlight,
  BookingPackageFlightMetaDataLine,
} from "@qite/tide-client/build/types";
import { differenceInMinutes, isEqual, parseISO } from "date-fns";
import {
  FlightDirectionFilter,
  FlightFilterOption,
  FlightFilterOptions,
  GroupedFlightDetails,
  GroupedFlights,
} from "../../types";

/*interface FlightGroup {
  code: string;
  startDate: Date,
  endDate: Date;
  options: BookingPackageFlight[];
}*/

export const buildGroupedFlights = (
  outwardFlights: BookingPackageFlight[] | undefined,
  returnFlights: BookingPackageFlight[] | undefined
) => {
  if (!outwardFlights || !returnFlights) return [] as GroupedFlights[];

  // let outwardGroups = groupFlights(outwardFlights);
  // let returnGroups = groupFlights(returnFlights);

  const pairs: {
    outward: BookingPackageFlight;
    return: BookingPackageFlight;
  }[] = [];
  outwardFlights.forEach((outwardFlight) => {
    if (outwardFlight.externalGuid) {
      const returnFlight = returnFlights.find(
        (x) => x.externalGuid === outwardFlight.externalGuid
      )!;
      pairs.push({ outward: outwardFlight, return: returnFlight });
    } else {
      const outwardCode = outwardFlight.code.substring(0, 7);
      const returnCode = outwardCode.split(" ").reduce((a, b) => `${b} ${a}`);

      returnFlights
        .filter((x) => x.code.startsWith(returnCode))
        .forEach((returnFlight) => {
          pairs.push({ outward: outwardFlight, return: returnFlight });
        });
    }
  });

  const results = pairs.map((x) => {
    const outwardFlightDetails = getFlightDetails(x.outward);
    const returnFlightDetails = getFlightDetails(x.return);

    return {
      isSelected: x.outward.isSelected && x.return.isSelected,
      price: x.outward.price + x.return.price,
      outward: outwardFlightDetails,
      return: returnFlightDetails,
      selectedOutward: x.outward,
      selectedReturn: x.return,
    } as GroupedFlights;
  });

  return results;
};

export const buildFilterOptions = (
  outwardFlights: BookingPackageFlight[] | undefined,
  returnFlights: BookingPackageFlight[] | undefined,
  translations: any
) => {
  if (!outwardFlights || !returnFlights) return undefined;

  const airports: FlightFilterOption[] = [];
  const airlines: FlightFilterOption[] = [];
  const numberOfStops: FlightFilterOption[] = [];
  const outwardDeparturePeriods: FlightFilterOption[] = [];
  const returnDeparturePeriods: FlightFilterOption[] = [];

  let lowestDepartureTravelDuration = 9999;
  let highestDepartureTravelDuration = 0;
  let lowestDepartureChangeDuration = 9999;
  let highestDepartureChangeDuration = 0;

  outwardFlights.forEach((flight) => {
    const airlineCode = flight.code.split("/")[1];

    if (flight.flightMetaData.flightLines?.length) {
      const firstLine = flight.flightMetaData.flightLines[0];

      if (!airports.some((x) => x.value === firstLine.departureAirport)) {
        airports.push({
          value: firstLine?.departureAirport,
          label: firstLine.departureAirportDescription,
          count: 0,
          isSelected: false,
        });
      }
    }

    if (!airlines.some((x) => x.value === airlineCode)) {
      airlines.push({
        value: airlineCode,
        label: flight.airlineDescription,
        count: 0,
        isSelected: false,
      });
    }

    const stopCount = flight.flightMetaData.flightLines.length - 1;
    if (!numberOfStops.some((x) => x.value === stopCount + "")) {
      numberOfStops.push({
        value: stopCount + "",
        label:
          stopCount === 0
            ? translations.FLIGHTS_FORM.DIRECT_FLIGHT
            : stopCount == 1
            ? `${stopCount} ${translations.FLIGHTS_FORM.STOP}`
            : `${stopCount} ${translations.FLIGHTS_FORM.STOPS}`,
        count: 0,
        isSelected: false,
      });
    }

    const departureTime = flight.flightMetaData.flightLines[0].departureTime;
    const timeBracket = determineTimeBracket(departureTime);
    if (!outwardDeparturePeriods.some((x) => x.value === timeBracket)) {
      outwardDeparturePeriods.push({
        value: timeBracket,
        label: getBracketTranslation(timeBracket, translations),
        count: 0,
        isSelected: false,
      });
    }

    const travelDurationInMinutes = minutesFromTicks(
      flight.flightMetaData.durationInTicks
    );
    if (travelDurationInMinutes > highestDepartureTravelDuration)
      highestDepartureTravelDuration = travelDurationInMinutes;
    if (travelDurationInMinutes < lowestDepartureTravelDuration)
      lowestDepartureTravelDuration = travelDurationInMinutes;

    const changeDurationInMinutes = getTotalChangeDuration(flight);
    if (changeDurationInMinutes > highestDepartureChangeDuration)
      highestDepartureChangeDuration = changeDurationInMinutes;
    if (changeDurationInMinutes < lowestDepartureChangeDuration)
      lowestDepartureChangeDuration = changeDurationInMinutes;
  });

  let lowestReturnTravelDuration = 9999;
  let highestReturnTravelDuration = 0;
  let lowestReturnChangeDuration = 9999;
  let highestReturnChangeDuration = 0;

  returnFlights.forEach((flight) => {
    const durationInMinutes = minutesFromTicks(
      flight.flightMetaData.durationInTicks
    );
    if (durationInMinutes > highestReturnTravelDuration)
      highestReturnTravelDuration = durationInMinutes;
    if (durationInMinutes < lowestReturnTravelDuration)
      lowestReturnTravelDuration = durationInMinutes;

    const changeDurationInMinutes = getTotalChangeDuration(flight);
    if (changeDurationInMinutes > highestReturnChangeDuration)
      highestReturnChangeDuration = changeDurationInMinutes;
    if (changeDurationInMinutes < lowestReturnChangeDuration)
      lowestReturnChangeDuration = changeDurationInMinutes;

    const departureTime = flight.flightMetaData.flightLines[0].departureTime;
    const timeBracket = determineTimeBracket(departureTime);
    if (!returnDeparturePeriods.some((x) => x.value === timeBracket)) {
      returnDeparturePeriods.push({
        value: timeBracket,
        label: getBracketTranslation(timeBracket, translations),
        count: 0,
        isSelected: false,
      });
    }
  });

  return {
    airports: airports,
    airlines: airlines,
    numberOfStops: numberOfStops,
    outward: {
      departurePeriod: outwardDeparturePeriods,
      travelDuration: {
        min: lowestDepartureTravelDuration,
        max: highestDepartureTravelDuration,
        selectedMin: lowestDepartureTravelDuration,
        selectedMax: highestDepartureTravelDuration,
      },
      changeDuration: {
        min: lowestDepartureChangeDuration,
        max: highestDepartureChangeDuration,
        selectedMin: lowestDepartureChangeDuration,
        selectedMax: highestDepartureChangeDuration,
      },
    },
    return: {
      departurePeriod: returnDeparturePeriods,
      travelDuration: {
        min: lowestReturnTravelDuration,
        max: highestReturnTravelDuration,
        selectedMin: lowestReturnTravelDuration,
        selectedMax: highestReturnTravelDuration,
      },
      changeDuration: {
        min: lowestReturnChangeDuration,
        max: highestReturnChangeDuration,
        selectedMin: lowestReturnChangeDuration,
        selectedMax: highestReturnChangeDuration,
      },
    },
  } as FlightFilterOptions;
};

export const filterGroupedFlights = (
  groups: GroupedFlights[],
  filterOptions: FlightFilterOptions | undefined
) => {
  if (!groups.length || !filterOptions) return [];

  let filteredGroups = groups;
  if (filterOptions.airlines.some((x) => x.isSelected)) {
    const selectedAirlineCodes = filterOptions.airlines.filter(
      (x) => x.isSelected
    );
    filteredGroups = filteredGroups.filter((x) =>
      selectedAirlineCodes.some((y) => y.value === x.outward.airlineCode)
    );
  }

  if (filterOptions.airports.some((x) => x.isSelected)) {
    const selectedAirlineCodes = filterOptions.airports.filter(
      (x) => x.isSelected
    );
    filteredGroups = filteredGroups.filter((x) =>
      selectedAirlineCodes.some(
        (y) => y.value === x.outward.departureAirportCode
      )
    );
  }

  if (filterOptions.numberOfStops.some((x) => x.isSelected)) {
    const selectedNumberOfStops = filterOptions.numberOfStops.filter(
      (x) => x.isSelected
    );
    filteredGroups = filteredGroups.filter((x) =>
      selectedNumberOfStops.some(
        (y) => parseInt(y.value) === x.outward.flightLines.length - 1
      )
    );
  }

  filteredGroups = filterGroupedFlightByDirection(
    filteredGroups,
    true,
    filterOptions.outward
  );
  filteredGroups = filterGroupedFlightByDirection(
    filteredGroups,
    false,
    filterOptions.return
  );

  return filteredGroups;
};

const filterGroupedFlightByDirection = (
  groups: GroupedFlights[],
  isOutward: boolean,
  directionFilter: FlightDirectionFilter
) => {
  let filteredGroups = groups;

  if (directionFilter.departurePeriod.some((x) => x.isSelected)) {
    const selectedDeparturePeriods = directionFilter.departurePeriod.filter(
      (x) => x.isSelected
    );
    filteredGroups = filteredGroups.filter((x) =>
      selectedDeparturePeriods.some(
        (y) =>
          y.value ===
          determineTimeBracket((isOutward ? x.outward : x.return).departureTime)
      )
    );
  }

  filteredGroups = filteredGroups.filter(
    (x) =>
      directionFilter.travelDuration.selectedMin <=
        (isOutward ? x.outward : x.return).travelDurationMinutes &&
      (isOutward ? x.outward : x.return).travelDurationMinutes <=
        directionFilter.travelDuration.selectedMax
  );
  return filteredGroups.filter(
    (x) =>
      directionFilter.changeDuration.selectedMin <=
        (isOutward ? x.outward : x.return).changeDurationMinutes &&
      (isOutward ? x.outward : x.return).changeDurationMinutes <=
        directionFilter.changeDuration.selectedMax
  );
};

export const formatMinutes = (minutes: number) => {
  var hh = Math.floor(minutes / 60);
  var mm = Math.floor(minutes % 60);
  return pad(hh, 2) + ":" + pad(mm, 2);
};

const getFlightDetails = (flight: BookingPackageFlight) => {
  const firstLine = flight.flightMetaData.flightLines[0];
  const lastLine =
    flight.flightMetaData.flightLines[
      flight.flightMetaData.flightLines.length - 1
    ];

  const airlineCode = flight.code.split("/")[1];
  const waitDurations = getWaitDurations(flight.flightMetaData.flightLines);

  return {
    airline: flight.airlineDescription,
    airlineCode: airlineCode,
    departureDate: firstLine.departureDate,
    departureTime: firstLine.departureTime,
    departureAirportCode: firstLine.departureAirport,
    departureAirport: firstLine.departureAirportDescription,
    arrivalDate: lastLine.arrivalDate,
    arrivalTime: lastLine.arrivalTime,
    arrivalAirport: lastLine.arrivalAirportDescription,
    travelDuration: formatDuration(flight.flightMetaData.durationInTicks),
    travelDurationMinutes: minutesFromTicks(
      flight.flightMetaData.durationInTicks
    ),
    changeDurationMinutes: getTotalChangeDuration(flight),
    numberOfStops: flight.flightMetaData.flightLines.length - 1,
    isNextDay: isNextDay(firstLine.departureDate, lastLine.arrivalDate),
    travelClass: firstLine.travelClass,
    flightLines: flight.flightMetaData.flightLines.map((x, i) => ({
      airline: x.operatingAirlineDescription,
      departureDate: x.departureDate,
      departureTime: x.departureTime,
      departureAirport: x.departureAirportDescription,
      arrivalDate: x.arrivalDate,
      arrivalTime: x.arrivalTime,
      arrivalAirport: x.arrivalAirportDescription,
      number: `${x.airlineCode} ${x.number}`,
      travelDuration: formatDuration(x.durationInTicks),
      waitDuration:
        waitDurations.length - 1 <= i ? waitDurations[i] : undefined,
    })),
  } as GroupedFlightDetails;
};

const isNextDay = (startDateString: string, endDateString: string) => {
  const startDate = parseISO(startDateString);
  const endDate = parseISO(endDateString);

  return !isEqual(startDate, endDate);
};

const getWaitDurations = (lines: BookingPackageFlightMetaDataLine[]) => {
  if (lines.length <= 1) return [];
  let arrivalDate = lines[0].arrivalDate;
  let arrivalTime = lines[0].arrivalTime;

  const waitDurations: string[] = [];
  for (var i = 1; i < lines.length; i++) {
    const line = lines[i];

    const waitDuration = getWaitDuration(
      arrivalDate,
      arrivalTime,
      line.departureDate,
      line.departureTime
    );
    waitDurations.push(waitDuration);

    arrivalDate = line.arrivalDate;
    arrivalTime = line.arrivalTime;
  }

  return waitDurations;
};

const getWaitDuration = (
  arrivalDateString: string,
  arrivalTime: string,
  departureDateString: string,
  departureTime: string
) => {
  const minutes = getWaitDurationInMinutes(
    arrivalDateString,
    arrivalTime,
    departureDateString,
    departureTime
  );

  var hh = Math.floor(minutes / 60);
  var mm = Math.floor(minutes % 60);
  return pad(hh, 2) + ":" + pad(mm, 2);
};

const getWaitDurationInMinutes = (
  arrivalDateString: string,
  arrivalTime: string,
  departureDateString: string,
  departureTime: string
) => {
  const arrivalDate = parseISO(arrivalDateString);
  const arrivalTimeParts = arrivalTime.split(":");

  arrivalDate.setHours(parseInt(arrivalTimeParts[0]));
  arrivalDate.setMinutes(parseInt(arrivalTimeParts[1]));

  const departureDate = parseISO(departureDateString);
  const departureTimeParts = departureTime.split(":");

  departureDate.setHours(parseInt(departureTimeParts[0]));
  departureDate.setMinutes(parseInt(departureTimeParts[1]));

  return differenceInMinutes(departureDate, arrivalDate);
};

/*const groupFlights = (flights: BookingPackageFlight[]) => {
  let flightsPool = [...flights];
  let groups = [] as FlightGroup[];
  for (var i = 0; i < flightsPool.length; i++) {
    const flight = flightsPool[i];

    const relatedFlights = flightsPool.filter(x => x != flight
      && x.code === flight.code
      && isDateEqual(x.startDateTime, flight.startDateTime)
      && isDateEqual(x.endDateTime, flight.endDateTime)
    );

    flightsPool = flightsPool.filter(x => x != flight
      && relatedFlights.some(y => y != x));

    groups.push({
      code: flight.code,
      startDate: parseISO(flight.startDateTime),
      endDate: parseISO(flight.endDateTime),
      options: [flight, ...relatedFlights]
    });
  }
}

const isDateEqual = (first: string, second: string) => {
  const firstDate = parseISO(first);
  const secondDate = parseISO(second);

  return isEqual(firstDate, secondDate);
}*/

const minutesFromTicks = (ticks: number) => {
  const totalSeconds = ticks / 10_000_000;
  return Math.floor(totalSeconds / 60);
};

const formatDuration = (ticks: number) => {
  if (!ticks) return "";

  const totalSeconds = ticks / 10_000_000;
  var hh = Math.floor(totalSeconds / 3600);
  var mm = Math.floor((totalSeconds % 3600) / 60);
  return pad(hh, 2) + ":" + pad(mm, 2);
};

const pad = (input: number, width: number) => {
  const n = input + "";
  return n.length >= width ? n : new Array(width - n.length + 1).join("0") + n;
};

const determineTimeBracket = (input: string) => {
  const time = parseInt(input.replace(":", ""));
  if (time <= 500) return "0000-0500";
  if (time > 500 && time <= 1200) return "0500-1200";
  if (time > 1200 && time <= 1800) return "1201-1800";
  return "1800-2400";
};

const getBracketTranslation = (input: string, translations: any) => {
  if (input === "0000-0500") return translations.FLIGHTS_FORM.NIGHT_DEPARTURE;
  if (input === "0500-1200") return translations.FLIGHTS_FORM.MORNING_DEPARTURE;
  if (input === "1200-1800")
    return translations.FLIGHTS_FORM.AFTERNOON_DEPARTURE;
  return translations.FLIGHTS_FORM.EVENING_DEPARTURE;
};

const getTotalChangeDuration = (flight: BookingPackageFlight) => {
  const lines = flight.flightMetaData.flightLines;

  if (lines.length <= 1) return 0;
  let arrivalDate = lines[0].arrivalDate;
  let arrivalTime = lines[0].arrivalTime;

  let waitDuration = 0;
  for (var i = 1; i < lines.length; i++) {
    const line = lines[i];

    waitDuration += getWaitDurationInMinutes(
      arrivalDate,
      arrivalTime,
      line.departureDate,
      line.departureTime
    );
  }

  return waitDuration;
};
