"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProcessRegionalBusPositionsTask = void 0;
const RopidGTFSTripsModel_1 = require("../../../../ropid-gtfs/RopidGTFSTripsModel");
const cache_1 = require("../../../../ropid-gtfs/data-access/cache");
const Di_1 = require("../../../ioc/Di");
const VPContainerToken_1 = require("../../../ioc/VPContainerToken");
const CoreToken_1 = require("@golemio/core/dist/helpers/ioc/CoreToken");
const queueprocessors_1 = require("@golemio/core/dist/integration-engine/queueprocessors");
const workers_1 = require("@golemio/core/dist/integration-engine/workers");
const golemio_errors_1 = require("@golemio/core/dist/shared/golemio-errors");
const PositionsRepository_1 = require("../data-access/PositionsRepository");
const TripsRepository_1 = require("../data-access/TripsRepository");
const DelayComputationManager_1 = require("../helpers/DelayComputationManager");
const UpdateDelaySchema_1 = require("../schema/UpdateDelaySchema");
const UpdateDelayLimiter_1 = require("./helpers/UpdateDelayLimiter");
class ProcessRegionalBusPositionsTask extends workers_1.AbstractTask {
    constructor(queuePrefix) {
        super(queuePrefix);
        this.queueName = "processRegionalBusPositions";
        this.queueTtl = 2 * 60 * 1000; // 2 minutes
        this.schema = UpdateDelaySchema_1.UpdateDelayValidationSchema;
        this.enrichTripWithGtfsComputationData = async (trip) => {
            try {
                const gtfsComputationData = await this.delayComputationManager.getComputationObject(trip.gtfs_trip_id);
                if (gtfsComputationData === null) {
                    return trip;
                }
                trip.gtfsData = gtfsComputationData;
            }
            catch (err) {
                if (err instanceof golemio_errors_1.GeneralError) {
                    this.logger.warn(err);
                }
                else {
                    this.logger.error(new golemio_errors_1.GeneralError("Error while getting delay computation data for trip " + trip.gtfs_trip_id, this.constructor.name, err));
                }
            }
            return trip;
        };
        /**
         * Propagate known position properties from trips positions to processed positions, when the processed position property is
         * `undefined`
         *
         * @param tripsPositions Trips positions to propagate properties from
         * @param processedPositions Processed positions to propagate properties to
         */
        this.propagateKnownPositionProperties = (tripPositions, processedPositions) => {
            for (let i = 0; i < processedPositions.positions.length; i++) {
                if (processedPositions.positions[i] === null)
                    continue;
                processedPositions.positions[i] = {
                    ...tripPositions.positions[i],
                    ...processedPositions.positions[i],
                };
            }
        };
        this.updateComputedPositionsAndTrips = async (processedPositions) => {
            let positions = [];
            let trips = new Map();
            for (const processedPosition of processedPositions) {
                positions = positions.concat(processedPosition.positions.filter((position) => position !== null));
                trips.set(processedPosition.context.tripId, {
                    id: processedPosition.context.tripId,
                    last_position_id: processedPosition.context.lastPositionId ?? null,
                    last_position_context: processedPosition.context,
                    is_canceled: processedPosition.context.lastPositionCanceled,
                });
            }
            await this.positionsRepository.bulkSave(positions);
            await this.tripsRepository.bulkSave(Array.from(trips.values()));
        };
        this.logger = Di_1.VPContainer.resolve(CoreToken_1.CoreToken.Logger);
        this.tripsRepository = new TripsRepository_1.TripsRepository();
        this.positionsRepository = new PositionsRepository_1.PositionsRepository();
        this.delayComputationRedisRepository = new cache_1.DelayComputationRedisRepository();
        const gtfsTripsRepository = new RopidGTFSTripsModel_1.RopidGTFSTripsModel();
        this.delayComputationManager = new DelayComputationManager_1.DelayComputationManager(gtfsTripsRepository);
        this.updateDelayLimiter = new UpdateDelayLimiter_1.UpdateDelayLimiter("ProcessRegionalBusPositionsTask", this.queueTtl);
        this.regionalBusPositionsManager = Di_1.VPContainer.resolve(VPContainerToken_1.VPContainerToken.RegionalBusPositionsManager);
    }
    async execute(data, msgProperties) {
        if (!this.updateDelayLimiter.isActualMessage(msgProperties)) {
            return; // skip processing of old messages
        }
        const tripIds = data.updatedTrips.map((trip) => trip.id);
        let gtfsComputationTrips = [];
        let tripsPositionsWithGtfsData = [];
        try {
            const tripsPositionsToUpdate = await this.positionsRepository.getPositionsForUpdateDelay(tripIds);
            gtfsComputationTrips =
                await this.delayComputationRedisRepository.getTripPropertiesBatch(tripsPositionsToUpdate.map((trip) => trip.gtfs_trip_id), ["shapes_anchor_points", "stop_times"]);
            for (let i = 0; i < tripsPositionsToUpdate.length; i++) {
                const trip = tripsPositionsToUpdate[i];
                const gtfsData = gtfsComputationTrips[i];
                tripsPositionsWithGtfsData.push({
                    ...trip,
                    gtfsData,
                });
            }
            const tripsWithNewlyCreatedCache = [];
            const tripsWithExistingCache = [];
            const processedPositions = await Promise.all(tripsPositionsWithGtfsData.map(async (trip) => {
                if (trip.gtfsData === null) {
                    trip = await this.enrichTripWithGtfsComputationData(trip);
                    if (trip.gtfsData === null) {
                        return null;
                    }
                    tripsWithNewlyCreatedCache.push(trip);
                }
                else {
                    tripsWithExistingCache.push(trip);
                }
                const computedPositions = this.regionalBusPositionsManager.computePositions(trip);
                this.propagateKnownPositionProperties(trip, computedPositions);
                return computedPositions;
            }));
            const validProcessedPositions = processedPositions.filter((e) => !!e);
            await this.updateComputedPositionsAndTrips(validProcessedPositions);
            await Promise.all([
                this.invalidateBacktrackedPositions(validProcessedPositions),
                this.delayComputationRedisRepository.cacheTripDataBatch(tripsWithNewlyCreatedCache, validProcessedPositions),
                this.expireDelayComputationCache(tripsWithExistingCache, validProcessedPositions),
            ]);
            await queueprocessors_1.QueueManager.sendMessageToExchange(this.queuePrefix, "propagateDelay", {
                processedPositions: validProcessedPositions,
                trips: data.updatedTrips,
            });
        }
        catch (err) {
            if (err instanceof golemio_errors_1.AbstractGolemioError) {
                throw err;
            }
            throw new golemio_errors_1.GeneralError(`Error while updating delay. For trip ids:${tripIds.slice(0, 10).join(",")}`, this.constructor.name, err);
        }
    }
    /**
     * Expire delay computation cache for given trips
     */
    async expireDelayComputationCache(tripWithExistingCache, positions) {
        const tripIdsWithTtl = [];
        for (const trip of tripWithExistingCache) {
            const expirationTtl = this.delayComputationRedisRepository.calculateExpirationTtl(positions, trip.id);
            if (trip.gtfsData) {
                tripIdsWithTtl.push({
                    tripId: trip.gtfs_trip_id,
                    expirationTtl,
                });
            }
        }
        try {
            await this.delayComputationRedisRepository.expirePxat(tripIdsWithTtl);
        }
        catch (err) {
            this.logger.error(err);
        }
    }
    /**
     * Invalidate positions after the last stop sequence of backtracked positions
     * We do this to improve historical data quality
     * as well as to prevent serving of incorrect stop times to API clients
     * (vehicle is expected to repeat some part of the trip)
     */
    async invalidateBacktrackedPositions(processedPositions) {
        const tripWithStopSequenceTuples = processedPositions
            .filter(({ isBacktrackingDetected }) => !!isBacktrackingDetected)
            .map(({ context, positions }) => {
            const tripId = context.tripId;
            const maxStopSequence = this.getMaxStopSequence(positions);
            return [tripId, maxStopSequence];
        });
        if (tripWithStopSequenceTuples.length === 0) {
            return;
        }
        const count = await this.positionsRepository.invalidatePositionsAfterStopSequence(tripWithStopSequenceTuples);
        this.logger.info(`Invalidated ${count} backtracked positions`);
    }
    getMaxStopSequence(positions) {
        const maxStopSequence = Math.max(...positions.map((position) => position?.last_stop_sequence ?? 0));
        if (maxStopSequence === 0) {
            throw new golemio_errors_1.GeneralError("Max stop sequence must be greater than 0", this.constructor.name);
        }
        return maxStopSequence;
    }
}
exports.ProcessRegionalBusPositionsTask = ProcessRegionalBusPositionsTask;
//# sourceMappingURL=ProcessRegionalBusPositionsTask.js.map