"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.UpdateGtfsTripIdTask = void 0;
const RopidGTFSStopTimesModel_1 = require("../../../../ropid-gtfs/RopidGTFSStopTimesModel");
const cache_1 = require("../../../../ropid-gtfs/data-access/cache");
const CommonRunsRepository_1 = require("../../runs/data-access/CommonRunsRepository");
const helpers_1 = require("@golemio/core/dist/integration-engine/helpers");
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 TripStopsManager_1 = require("../helpers/TripStopsManager");
const VPUtils_1 = require("../helpers/VPUtils");
const GTFSTripRunManagerFactory_1 = require("../helpers/gtfs-trip-run/GTFSTripRunManagerFactory");
const UpdateGtfsTripIdSchema_1 = require("../schema/UpdateGtfsTripIdSchema");
class UpdateGtfsTripIdTask extends workers_1.AbstractTask {
    constructor(queuePrefix) {
        super(queuePrefix);
        this.queueName = "updateGTFSTripId";
        this.queueTtl = 5 * 60 * 1000; // 5 minutes
        this.schema = UpdateGtfsTripIdSchema_1.UpdateGtfsTripIdValidationSchema;
        this.tripsRepository = new TripsRepository_1.TripsRepository();
        this.positionsRepository = new PositionsRepository_1.PositionsRepository();
        this.gtfsStopTimesRepository = new RopidGTFSStopTimesModel_1.RopidGTFSStopTimesModel();
        const commonRunsRepository = new CommonRunsRepository_1.CommonRunsRepository();
        const runTripsRedisRepository = new cache_1.RunTripsRedisRepository();
        const blockStopsRedisRepository = new cache_1.BlockStopsRedisRepository();
        this.gtfsTripRunManager = GTFSTripRunManagerFactory_1.GTFSTripRunManagerFactory.create(GTFSTripRunManagerFactory_1.GTFSTripRunType.Common, this.positionsRepository, this.tripsRepository, commonRunsRepository, runTripsRedisRepository);
        this.tripStopsManager = new TripStopsManager_1.TripStopsManager(this.gtfsStopTimesRepository, blockStopsRedisRepository);
    }
    /**
     * Take all newly computed positions and save it, then save the last ones to the trips
     */
    async execute(data, messageProperties) {
        // unique ids in input data in key-value (id-positions) structure
        let tripIdToPositionDict = {};
        for (const position of data.positions) {
            tripIdToPositionDict[position.trips_id] = position;
        }
        // find gtfs data for each trip
        const foundTrips = await Promise.all(data.trips.map(async (trip) => {
            if (!trip.start_timestamp) {
                return;
            }
            try {
                return await this.tripsRepository.findGTFSTripId(trip);
            }
            catch (err) {
                helpers_1.log.error(err);
                return err;
            }
        }));
        let existingTrips = [], existingTripErrors = [];
        // filter only trips with found gtfs data
        for (const trip of foundTrips) {
            if (trip instanceof golemio_errors_1.AbstractGolemioError) {
                existingTripErrors.push(trip);
            }
            else if (trip instanceof Array) {
                existingTrips.push(...trip);
            }
            else if (trip?.constructor === Object) {
                existingTrips.push(trip);
            }
        }
        let blockIdToTripIdsDict = {};
        for (const trip of existingTrips) {
            if (!trip.gtfs_block_id || !trip.gtfs_trip_id)
                continue;
            if (!blockIdToTripIdsDict[trip.gtfs_block_id]) {
                blockIdToTripIdsDict[trip.gtfs_block_id] = new Set();
            }
            blockIdToTripIdsDict[trip.gtfs_block_id].add(trip.gtfs_trip_id);
        }
        // create cache for trip stops (grouped by block id)
        const blockIdToTripStopScheduleDict = await Object.entries(blockIdToTripIdsDict).reduce(async (acc, [blockId, gtfsTripIds]) => {
            const tripStops = await acc;
            try {
                tripStops[blockId] = await this.tripStopsManager.setAndReturnTripStopsSchedule(blockId, [...gtfsTripIds]);
            }
            catch (err) {
                helpers_1.IntegrationErrorHandler.handle(err);
            }
            return acc;
        }, Promise.resolve({}));
        let currentPosition;
        for (const trip of existingTrips) {
            if (tripIdToPositionDict[trip.id]) {
                currentPosition = tripIdToPositionDict[trip.id];
                if (trip.gtfs_block_id) {
                    // Determine current position tracking status
                    currentPosition.is_tracked = VPUtils_1.VPUtils.determineTrackingStatus(trip.gtfs_trip_id, blockIdToTripStopScheduleDict[trip.gtfs_block_id], currentPosition);
                }
                continue;
            }
            if (!currentPosition) {
                continue;
            }
            // Determine new position tracking status
            const newPositionTrackingStatus = VPUtils_1.VPUtils.determineNewPositionTracking(blockIdToTripStopScheduleDict[trip.gtfs_block_id], trip, currentPosition);
            // duplicate position with new created trip for second trip
            const newPosition = { ...currentPosition };
            newPosition.trips_id = trip.id;
            newPosition.is_tracked = newPositionTrackingStatus;
            data.positions.push(newPosition);
        }
        await this.positionsRepository.bulkSave(data.positions);
        await this.generateAndSaveScheduledTrips(tripIdToPositionDict, data.trips, existingTrips);
        // successfully updated gtfs ids
        for (let i = 0, chunkSize = 100, imax = existingTrips.length; i < imax; i += chunkSize) {
            await queueprocessors_1.QueueManager.sendMessageToExchange(this.queuePrefix, "updateDelay", {
                positions: [],
                updatedTrips: existingTrips.slice(i, i + chunkSize),
            }, { timestamp: messageProperties?.timestamp });
        }
        // gtfsId updating errors
        for (let i = 0, imax = existingTripErrors.length; i < imax; i++) {
            helpers_1.IntegrationErrorHandler.handle(existingTripErrors[i]);
        }
    }
    generateAndSaveScheduledTrips(tripIdToPositionDict, inputTrips, existingTrips) {
        let operationPromises = [];
        for (const inputTrip of inputTrips) {
            if (!tripIdToPositionDict[inputTrip.id]?.origin_timestamp) {
                continue;
            }
            const existingTrip = existingTrips.find((existingTrip) => existingTrip.id === inputTrip.id);
            const lastPositionIsoTimestamp = new Date(tripIdToPositionDict[inputTrip.id].origin_timestamp).toISOString();
            let routeId = inputTrip.origin_route_name;
            let runNumber = inputTrip.run_number;
            // We don't trust the run number and route name from the input data
            // so we use the ones from the scheduled/existing trip if they exist
            if (existingTrip?.internal_run_number && existingTrip?.internal_route_name) {
                routeId = existingTrip.internal_route_name;
                runNumber = existingTrip.internal_run_number;
            }
            if (!routeId || !runNumber) {
                continue;
            }
            const operationPromise = this.gtfsTripRunManager
                .setAndReturnScheduledTrips({
                route_id: routeId,
                run_number: runNumber,
                msg_last_timestamp: lastPositionIsoTimestamp,
            })
                .then((scheduledTrips) => void scheduledTrips);
            operationPromises.push(operationPromise);
        }
        return Promise.all(operationPromises);
    }
}
exports.UpdateGtfsTripIdTask = UpdateGtfsTripIdTask;
//# sourceMappingURL=UpdateGtfsTripIdTask.js.map