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;
}