import { Injectable } from '@angular/core';
import { RouteTypeLegacy } from '@app/shared/enumerators';
import {
    FlightModel,
    getFirstLeg,
    IArrivalStation,
    IDepartureStation,
    Leg,
    StationChange,
    Stations,
    Transfer,
} from '@app/shared/models-legacy';
import { getLastLeg } from './get-flight.strategy';

@Injectable({ providedIn: 'root' })
export class TransferLogic {
    getTransfer(
        flight: FlightModel,
        nextFlight: FlightModel,
        leg: Leg,
    ): Transfer {
        const type = this.getType(flight, nextFlight, leg);

        if (!type) {
            return {
                type: undefined,
                arrival: undefined,
                departure: undefined,
            };
        }

        const { arrival, departure } = this.getStations(
            type,
            flight,
            nextFlight,
            leg,
        );

        const stationChange = this.getStationChange({ arrival, departure });

        return {
            type,
            arrival,
            departure,
            stationChange,
        };
    }

    getType(
        flight: FlightModel,
        nextFlight: FlightModel,
        leg: Leg,
    ): RouteTypeLegacy | null {
        return flight && nextFlight
            ? RouteTypeLegacy.Connecting
            : leg
            ? RouteTypeLegacy.MultiLeg
            : null;
    }

    getStations(
        type: RouteTypeLegacy,
        flight: FlightModel,
        nextFlight: FlightModel,
        leg: Leg,
    ): Stations {
        const arrival = this.getArrival(type, flight, leg);
        const departure = this.getDeparture(type, nextFlight, leg);

        return { arrival, departure };
    }

    cityChanged({ arrival, departure }: Stations) {
        const arrivalInfo = arrival?.latest || arrival?.scheduled;
        const departureInfo = departure?.latest || departure?.scheduled;

        return arrivalInfo?.cityCode !== departureInfo?.cityCode;
    }

    airportChanged({ arrival, departure }: Stations) {
        const arrivalInfo = arrival?.latest || arrival?.scheduled;
        const departureInfo = departure?.latest || departure?.scheduled;

        return arrivalInfo?.airportCode !== departureInfo?.airportCode;
    }

    terminalChanged({ arrival, departure }: Stations) {
        if (!arrival?.terminal || !departure?.terminal) return false;

        return arrival.terminal !== departure.terminal;
    }

    getStationChange(stations: Stations): StationChange {
        if (this.cityChanged(stations)) {
            return 'city';
        }

        if (this.airportChanged(stations)) {
            return 'airport';
        }

        if (this.terminalChanged(stations)) {
            return 'terminal';
        }

        return 'noChange';
    }

    private mapToTransfer(
        arrival: IArrivalStation,
        departure: IDepartureStation,
    ) {
        if (this.cityChanged({ arrival, departure })) {
            return this.constructTransfer(
                RouteTypeLegacy.Connecting,
                arrival,
                departure,
                'city',
            );
        }

        if (this.airportChanged({ arrival, departure })) {
            return this.constructTransfer(
                RouteTypeLegacy.Connecting,
                arrival,
                departure,
                'airport',
            );
        }

        if (this.terminalChanged({ arrival, departure })) {
            return this.constructTransfer(
                RouteTypeLegacy.Connecting,
                arrival,
                departure,
                'terminal',
            );
        }

        return this.constructTransfer(
            RouteTypeLegacy.Connecting,
            arrival,
            departure,
            'noChange',
        );
    }

    private getConnectingTransfers(flights: FlightModel[]): Transfer[] {
        return flights
            .reduce((acc, cv) => {
                if (cv.leg) {
                    acc.push(cv.leg);
                    return acc;
                }

                if (cv.legs) {
                    return [...acc, ...cv.legs];
                }

                return acc;
            }, [])
            .filter((f) => f.next)
            .map((leg) => {
                const { arrival } = leg;
                const { departure } = leg.next;

                return this.mapToTransfer(arrival, departure);
            });
    }

    private getMultiLegTransfers(flights: FlightModel[]) {
        const connectedLegs = flights
            .map((flight) => {
                return (flight.legs || []).filter((l) => l.next);
            })
            .reduce((a, b) => a.concat(b), []);

        return connectedLegs.map((leg) => {
            const { arrival } = leg;
            const { departure } = leg.next;

            return this.mapToTransfer(arrival, departure);
        });
    }

    getTransfers(
        flights: FlightModel[],
        type: RouteTypeLegacy.Connecting | RouteTypeLegacy.MultiLeg,
    ): Transfer[] {
        return type === RouteTypeLegacy.Connecting
            ? this.getConnectingTransfers(flights)
            : this.getMultiLegTransfers(flights);
    }

    getPrimaryTransfer(transfers: Transfer[]): Transfer | null {
        if (!transfers) return null;

        return this.sortTransfers(transfers)[0];
    }

    private getArrival(
        type: RouteTypeLegacy,
        flight: FlightModel,
        leg: Leg,
    ): IArrivalStation | null {
        if (!type) return null;

        return type === RouteTypeLegacy.Connecting
            ? getLastLeg(flight).arrival
            : leg.arrival;
    }

    private getDeparture(
        type: RouteTypeLegacy,
        nextFlight: FlightModel,
        leg: Leg,
    ): IDepartureStation | null {
        if (!type) return null;

        return type === RouteTypeLegacy.Connecting
            ? getFirstLeg(nextFlight).departure
            : leg.next.departure;
    }

    private constructTransfer(
        type: RouteTypeLegacy,
        arrival: IArrivalStation,
        departure: IDepartureStation,
        stationChange: StationChange,
    ): Transfer {
        return {
            type,
            arrival,
            departure,
            stationChange,
        };
    }

    private sortTransfers(transfers: Transfer[]): Transfer[] {
        const importanceAsc = ['noChange', 'terminal', 'airport'];

        return transfers.sort(
            (t1, t2) =>
                importanceAsc.indexOf(t1.stationChange) -
                importanceAsc.indexOf(t2.stationChange),
        );
    }
}
