"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
var SaveDataToDBTask_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.SaveDataToDBTask = void 0;
const RopidGTFSCisStopsModel_1 = require("../../../../ropid-gtfs/RopidGTFSCisStopsModel");
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 sequelize_1 = require("@golemio/core/dist/shared/sequelize");
const tsyringe_1 = require("@golemio/core/dist/shared/tsyringe");
const CisStopRepository_1 = require("../data-access/CisStopRepository");
const TripsMapper_1 = __importDefault(require("../data-access/helpers/TripsMapper"));
const SaveDataToDBSchema_1 = require("../schema/SaveDataToDBSchema");
const MpvMessageTransformation_1 = require("../transformations/MpvMessageTransformation");
const constants_1 = require("../constants");
let SaveDataToDBTask = SaveDataToDBTask_1 = class SaveDataToDBTask extends workers_1.AbstractTask {
    constructor(tripsRepository, cisStopRepository, messageTransformation, ropidGTFSCisStopsModel, logger) {
        super(constants_1.WORKER_NAME);
        this.queueName = "saveDataToDB";
        this.queueTtl = 5 * 60 * 1000; // 5 minutes
        this.schema = SaveDataToDBSchema_1.SaveDataToDBValidationSchema;
        this.tripsRepository = tripsRepository;
        this.cisStopRepository = cisStopRepository;
        this.messageTransformation = messageTransformation;
        this.ropidGTFSCisStopsModel = ropidGTFSCisStopsModel;
        this.logger = logger;
    }
    async execute(data, messageProperties) {
        const taskStartTimestamp = new Date().getTime();
        const now = MpvMessageTransformation_1.MpvMessageTransformation.currentTimeInPrague();
        const nowFormatted = MpvMessageTransformation_1.MpvMessageTransformation.currentTimeInPragueFormatted(now);
        const { trips, positions, stops, tripsWithCisStops } = this.messageTransformation.transform(data.m.spoj);
        const tripEntities = await this.tripsRepository.bulkSave(trips, 
        // Ignore incoming start_timestamp for existing trips to avoid overwriting existing computed timestamp
        // TODO this is a workaround, dedicated column (gtfs_start_timestamp) will be added
        Object.keys(trips[0] ?? {})
            .filter((key) => key !== "start_timestamp")
            .concat("updated_at"), true);
        const { inserted, updated } = TripsMapper_1.default.mapTripEntitiesToUpdateDto(tripEntities, taskStartTimestamp);
        const positionsToAppend = await this.preparePositionsFromCisStops(tripsWithCisStops, trips, nowFormatted, now);
        positions.push(...positionsToAppend);
        // Save cis stops first to later propagate platform codes to departure boards
        await this.saveStops(stops);
        // At this point we want to send ACK to rabbit and let the rest of the processing to be done in the background
        await this.sendDataToUpdateGtfsTripId(inserted, positions, messageProperties?.timestamp);
        await this.sendDataToUpdateDelay(updated, positions, messageProperties?.timestamp);
    }
    async saveStops(stops) {
        try {
            await this.cisStopRepository.bulkSave(stops);
        }
        catch (error) {
            if (error instanceof golemio_errors_1.AbstractGolemioError) {
                this.logger.error(error);
            }
            else {
                this.logger.error(new golemio_errors_1.GeneralError("Error while saving stops", SaveDataToDBTask_1.name, error, undefined, "pid", this.queueName));
            }
        }
    }
    async sendDataToUpdateGtfsTripId(insertedTrips, positions, firstMessageCreatedAt) {
        try {
            // send message for update GTFSTripIds
            for (let i = 0, chunkSize = 50; i < insertedTrips.length; i += chunkSize) {
                const insertedBatch = insertedTrips.slice(i, i + chunkSize);
                const insertedIds = insertedBatch.map((ins) => {
                    return ins.id;
                });
                await queueprocessors_1.QueueManager.sendMessageToExchange(this.queuePrefix, "updateGTFSTripId", {
                    trips: insertedBatch,
                    positions: positions.filter((position) => insertedIds.includes(position.trips_id)),
                }, {
                    timestamp: firstMessageCreatedAt,
                });
            }
        }
        catch (error) {
            if (error instanceof golemio_errors_1.AbstractGolemioError) {
                this.logger.error(error);
            }
            else {
                this.logger.error(new golemio_errors_1.GeneralError("Error while sending data to update GTFS trip id", SaveDataToDBTask_1.name, error, undefined, "pid", this.queueName));
            }
        }
    }
    async sendDataToUpdateDelay(updatedTrips, positions, firstMessageCreatedAt) {
        try {
            // aggregate runs by origin_route_name & run_number to keep related messages in same bulk
            const runTripMap = updatedTrips.length ? { unknown: [] } : {};
            for (const trip of updatedTrips) {
                if (!trip.origin_route_name || !trip.run_number) {
                    runTripMap.unknown.push(trip.id);
                }
                else {
                    const key = `${trip.origin_route_name}_${trip.run_number}`;
                    runTripMap[key] ? runTripMap[key].push(trip.id) : (runTripMap[key] = [trip.id]);
                }
            }
            const chunks = [];
            let idx = 0;
            for (const key in runTripMap) {
                if (!chunks[idx]) {
                    chunks[idx] = [];
                }
                chunks[idx] = chunks[idx].concat(runTripMap[key]);
                if (chunks[idx].length > 200) {
                    idx++;
                }
            }
            const promises = [];
            for (const key in chunks) {
                promises.push(this.getTripDataAndSendToUpdateDelay(chunks[key], positions, firstMessageCreatedAt));
            }
            await Promise.all(promises);
        }
        catch (error) {
            if (error instanceof golemio_errors_1.AbstractGolemioError) {
                this.logger.error(error);
            }
            else {
                this.logger.error(new golemio_errors_1.GeneralError("Error while sending data to update delay", SaveDataToDBTask_1.name, error, undefined, "pid", this.queueName));
            }
        }
    }
    async preparePositionsFromCisStops(tripsWithCisStops, trips, tsText, ts) {
        if (!tripsWithCisStops?.size) {
            return [];
        }
        const newPositions = [];
        const cisList = new Set();
        const cisToTrips = new Map();
        const tripToFirstStopDepartureTime = new Map();
        for (const trip of trips) {
            // we want to have positions of a before_track trip valid_to its departure (start_timestamp)
            tripToFirstStopDepartureTime.set(trip.id, trip.start_timestamp);
        }
        for (const [tripId, cisStop] of tripsWithCisStops.entries()) {
            cisList.add(cisStop);
            cisToTrips.set(cisStop, (cisToTrips.get(cisStop) ?? []).concat([tripId]));
        }
        const rawCisStops = await this.ropidGTFSCisStopsModel.find({
            where: {
                cis: { [sequelize_1.Op.in]: Array.from(cisList) },
                id: { [sequelize_1.Op.like]: `%/301` }, // We want only asw-code like XYZ/301
            },
            attributes: ["cis", "lat", "lon"],
            raw: true,
        });
        for (const cisStop of rawCisStops) {
            if (cisList.has(cisStop.cis) && cisToTrips.has(cisStop.cis)) {
                cisList.delete(cisStop.cis); // prevents to create positions for the same cis stop returning multiple rows
                for (const tripId of cisToTrips.get(cisStop.cis)) {
                    const validTo = tripToFirstStopDepartureTime.get(tripId) ?? null;
                    newPositions.push(this.messageTransformation.positionWithCisStopCoords(tripId, cisStop.lat, cisStop.lon, tsText, ts, validTo));
                }
            }
        }
        return newPositions;
    }
    async getTripDataAndSendToUpdateDelay(chunk, positions, firstMessageCreatedAt) {
        try {
            const updatedTrips = await this.tripsRepository.findAllAssocTripIds(chunk);
            await queueprocessors_1.QueueManager.sendMessageToExchange(this.queuePrefix, "updateDelay", {
                updatedTrips,
                positions: positions.filter((position) => chunk.includes(position.trips_id)),
            }, {
                timestamp: firstMessageCreatedAt,
            });
        }
        catch (error) {
            if (error instanceof golemio_errors_1.AbstractGolemioError) {
                this.logger.error(error);
            }
            else {
                this.logger.error(new golemio_errors_1.GeneralError("Error while sending data to update delay", SaveDataToDBTask_1.name, error, undefined, "pid", this.queueName));
            }
        }
    }
};
exports.SaveDataToDBTask = SaveDataToDBTask;
exports.SaveDataToDBTask = SaveDataToDBTask = SaveDataToDBTask_1 = __decorate([
    (0, tsyringe_1.injectable)(),
    __param(0, (0, tsyringe_1.inject)(VPContainerToken_1.VPContainerToken.TripRepository)),
    __param(1, (0, tsyringe_1.inject)(VPContainerToken_1.VPContainerToken.CisStopRepository)),
    __param(2, (0, tsyringe_1.inject)(VPContainerToken_1.VPContainerToken.MpvMessageTransformation)),
    __param(3, (0, tsyringe_1.inject)(VPContainerToken_1.VPContainerToken.RopidGTFSCisStopsModel)),
    __param(4, (0, tsyringe_1.inject)(CoreToken_1.CoreToken.Logger)),
    __metadata("design:paramtypes", [Function, CisStopRepository_1.CisStopRepository,
        MpvMessageTransformation_1.MpvMessageTransformation,
        RopidGTFSCisStopsModel_1.RopidGTFSCisStopsModel, Object])
], SaveDataToDBTask);
//# sourceMappingURL=SaveDataToDBTask.js.map