import { FlightLoadedState, FlightStatusLegacy } from '../enumerators';
import { IArrivalStation } from './arrival-station.model';
import { ILegContract, IMultiFlight, IRouteContract } from './contracts';
import { IDepartureStation } from './departure-station.model';
import { FlightModel, MultiLegFlight } from './flight.model';
import { getStrategy, getStrategyByType } from './get-flight.strategy';
import { Leg } from './leg.model';
import { Duration } from './duration.model';
import { getDuration, zeroDuration } from './duration.model';
import * as moment from 'moment';
import { getTotalMinutes } from '.';
import { MultiLegFlightStrategy } from './multileg-fligh.strategy';

export function createFlights(contracts: IRouteContract[]): FlightModel[] {
    return contracts?.map((contract) => createFlight(contract)) ?? [];
}

const factories = { createFlight, createLeg };

export function createFlight(contract: IRouteContract): FlightModel {
    const strategy = getStrategyByType(contract.routeType);

    if (contract.routeType == 'MultiLeg') {
        (contract as IMultiFlight).legs = (contract as IMultiFlight).legs.sort((l1, l2) => {
            if (!l1.departure.times.actualBlockOff || !l2.departure.times.actualBlockOff) {
                return 0;
            }

            if (l1.departure.times.actualBlockOff.utc > l2.departure.times.actualBlockOff.utc) {
                return 1;
            }
            if (l1.departure.times.actualBlockOff.utc < l2.departure.times.actualBlockOff.utc) {
                return -1;
            }
            return 0;
        });
    }

    const flight = strategy.fromContract(contract, factories) as FlightModel;

    return populate(flight);
}

function getSearchDate(leg: Leg) {
    const departure = leg.departure.times.scheduled.local;
    return moment.parseZone(departure).local(true).toDate();
}

export function populate(flight: FlightModel) {
    const strategy = getStrategy(flight);
    const flights = strategy.getFlights(flight).filter((f) => !!f.flightId);
    const firstLeg = strategy.getFirstLeg(flight);
    const lastLeg = strategy.getLastLeg(flight);
    const legs = strategy.getLegs(flight);

    flight.dateToSearchBy = getSearchDate(firstLeg);
    flight.lastUpdate = new Date();
    flight.loadedState = FlightLoadedState.scheduled;
    flight.boardings = legs.length - 1;

    flight.hash = flights
        .map(
            (f) =>
                `${f.flightId.carrier}${f.flightId.flightNumber}${moment(
                    f.dateToSearchBy,
                ).format('DDMMYYYY')}`,
        )
        .join('');

    flight.duration = getScheduledFlightTime(firstLeg, lastLeg);

    link(legs);
    link(flights);

    legs.forEach((leg, index) => {
        leg.index = index;
        leg.crossIndex = index + 1;
    });

    // debug info, don't remove before 2.01.2022
    // const operator = 'CI';
    // flight.operatingBy = {
    //     scheduled: operator,
    //     actual: operator,
    //     operators: [{
    //         carrier: operator,
    //         flightNumber: 'SU-1234',
    //     }]
    // };
    return flight;
}

// TODO: refactor this method by moving calculations to server as much as possible
export function createLeg(legContract: ILegContract): Leg {
    const leg: Leg = { ...legContract } as any as Leg;

    leg.arrival = createArrival(legContract);
    leg.departure = createDeparture(legContract);

    // TODO: TASK 7352 - pending migration to server
    leg.duration = getCurrentFlightTime(leg);
    leg.scheduledDuration = getDuration(
        leg.departure.times.scheduled?.utc,
        leg.arrival.times.scheduled?.utc,
    );
    leg.estimatedDuration = getDuration(
        leg.departure.times.latest?.utc,
        leg.arrival.times.latest?.utc,
    );
    leg.reroute = legContract.flags?.returnToAirport;
    leg.changeRoute = legContract.flags?.routeChanged;

    // debug info. could be removed after 02.2022
    // leg.operatingBy = {
    //     scheduled: 'KL',
    //     actual: 'SU',
    //     operators: [
    //         {
    //             carrier: 'KL',
    //             flightNumber: '1234',
    //         },
    //         {
    //             carrier: 'KL',
    //             flightNumber: '1234',
    //         },
    //     ],
    // };

    // ToDo: Refactor / move to server
    leg.hasCodesharing = leg.operatingBy?.operators?.length > 0;

    calculateLegState(leg);
    // debug info. could be removed after 02.2022
    // leg.reroute = true;
    // leg.changeRoute = true;

    return leg;
}

export function createDeparture(legContract: ILegContract): IDepartureStation {
    const contract = legContract.departure;
    const { times } = contract;

    const departure: IDepartureStation = {
        boardingStatus: contract.boardingStatus,
        boardingStatusOriginal: contract.boardingStatus,
        checkinStatus: contract.checkinStatus,
        dispatch: contract.dispatch,
        gate: contract.gate,
        parkingStand: contract.parkingStand,
        scheduled: { ...contract.scheduled },
        terminal: contract.terminal,
        _times: times as any,
        times: {
            ...times,
            scheduled: times.scheduledDeparture,
            actual:
                times.actualTime || times.actualBlockOff || times.actualTakeOff,
            estimated: times.estimatedBlockOff || times.estimatedTakeOff,
        },
        latest: contract.latest
            ? { ...contract.latest }
            : { ...contract.scheduled },
        // debug info for registration button. could be removed after 02.2022
        // transition: { registration: { status: OperationStatus.inProgress }} as any
    };

    departure.times.latest =
        departure.times.actual || departure.times.estimated;

    // dont remove, this is debug. could be removed after 02.2022
    // departure.times.scheduled = { ...departure.times.scheduled, utc: new Date(), localTime: '10:35' };
    // departure.times.scheduled.dayChange = { value: 2, title: '+2' };
    return departure;
}

export function createArrival(legContract: ILegContract): IArrivalStation {
    const contract = legContract.arrival;
    const { times } = contract;

    const arrival: IArrivalStation = {
        bagBelt: contract.bagBelt,
        deboardingStatus: contract.deboardingStatus,
        latest: contract.latest
            ? { ...contract.latest }
            : { ...contract.scheduled },
        scheduled: { ...contract.scheduled },
        terminal: contract.terminal,
        _times: times as any,
        times: {
            ...times,
            scheduled: times.scheduledArrival,
            estimated: times.estimatedBlockOn || times.estimatedTouchDown,
            actual:
                times.actualTime ||
                times.actualBlockOn ||
                times.actualTouchDown,
        },
        dispatch: contract.dispatch,
        gate: contract.gate,
    };

    arrival.times.latest = arrival.times.actual || arrival.times.estimated;

    // dont remove, this is debug. could be removed after 02.2022
    // arrival.times.latest = { ...arrival.times.latest, utc: moment().add(1, 'hour').toDate(), localTime: '12:25' };
    // arrival.times.scheduled = { ...arrival.times.scheduled, utc: new Date(), localTime: '10:35' };
    // arrival.times.scheduled.dayChange = { value: 2, title: '+2' };
    return arrival;
}

function link<T extends { next: T; previous: T }>(items: T[]) {
    let previous: T;
    items.forEach((item) => {
        item.previous = previous;
        if (previous) previous.next = item;
        previous = item;
    });
}

function getCurrentFlightTime(leg: Leg) {
    const departureTime = leg.departure.times.actual;
    const arrivalTime = leg.arrival.times.latest?.utc;
    const now = moment().utc().toDate();

    if (!departureTime) {
        return zeroDuration;
    }

    const endTime = arrivalTime <= now ? arrivalTime : now;

    return getDuration(departureTime.utc, endTime);
}

function getScheduledFlightTime(from: Leg, to?: Leg) {
    to = to ?? from;

    return getDuration(
        from.departure.times.scheduled?.utc,
        to.arrival.times.scheduled?.utc,
    );
}

// TODO: TASK 7352 - pending migration to server
export function calculateLegState(leg: Leg) {
    switch (leg.status) {
        case FlightStatusLegacy.scheduled:
        case FlightStatusLegacy.cancelled:
        case FlightStatusLegacy.delayed:
        case FlightStatusLegacy.unknown:
            leg.flightPercent = 0;
            break;
        case FlightStatusLegacy.arrived:
        case FlightStatusLegacy.landed:
            leg.flightPercent = 100;
            break;
        case FlightStatusLegacy.sent:
            leg.flightPercent = 10; // WI 7601
            break;
        default:
            leg.flightPercent = calculateFlightTimePercent(leg);
            break;
    }

    leg.remainingFlightDuration = calculateRemainingDuration(leg);

    //debug info
    // leg.status = FlightStatus.inFlight;
    // leg.flightPercent = 30;
    // leg.remainingFlightDuration = { hours: 5, days: 1, minutes: 24 };
    // leg.checkInDuration = { hours: 4, days: 0, minutes: 12 };
}

export function calculateFlightTimePercent({
    duration,
    estimatedDuration,
    scheduledDuration,
}: Leg): number {
    const totalDuration = estimatedDuration ?? scheduledDuration;

    if (!duration || !totalDuration) return 0;

    const minutesInFlight = getTotalMinutes(duration);
    const totalMinutes = getTotalMinutes(totalDuration);

    const percentage = Math.round(100 * (minutesInFlight / totalMinutes));

    return Math.min(100, percentage);
}

export function calculateRemainingDuration({
    departure,
    arrival,
}: Leg): Duration {
    const start = departure?.times?.actual;
    const end = arrival?.times?.latest || arrival?.times?.scheduled;

    if (!start || !end) return zeroDuration;

    const from = moment(start.utc);
    const to = moment(end.utc);
    const now = moment().utc();

    if (from <= now && now <= to) return getDuration(now.toDate(), to.toDate());

    return zeroDuration;
}

const PopulateLogicLegacy = {
    createFlights,
};

export default PopulateLogicLegacy;
