"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
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 __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TripsRepository = void 0;
const DateTimeUtils_1 = require("../../../../../helpers/DateTimeUtils");
const Di_1 = require("../../../ioc/Di");
const VPContainerToken_1 = require("../../../ioc/VPContainerToken");
const const_1 = require("../../../../../schema-definitions/const");
const VPTripsModel_1 = require("../../../../../schema-definitions/vehicle-positions/models/VPTripsModel");
const CoreToken_1 = require("@golemio/core/dist/helpers/ioc/CoreToken");
const models_1 = require("@golemio/core/dist/integration-engine/models");
const golemio_errors_1 = require("@golemio/core/dist/shared/golemio-errors");
const golemio_validator_1 = require("@golemio/core/dist/shared/golemio-validator");
const moment_timezone_1 = __importDefault(require("@golemio/core/dist/shared/moment-timezone"));
const sequelize_1 = __importStar(require("@golemio/core/dist/shared/sequelize"));
const tsyringe_1 = require("@golemio/core/dist/shared/tsyringe");
const const_2 = require("../../../../../const");
const PositionsRepository_1 = require("./PositionsRepository");
const RawQueryProvider_1 = require("./helpers/RawQueryProvider");
const TripsMapper_1 = __importDefault(require("./helpers/TripsMapper"));
let TripsRepository = class TripsRepository extends models_1.PostgresModel {
    constructor() {
        super("VPTripsRepository", {
            pgTableName: VPTripsModel_1.VPTripsModel.TABLE_NAME,
            pgSchema: const_1.PG_SCHEMA,
            outputSequelizeAttributes: VPTripsModel_1.VPTripsModel.attributeModel,
            savingType: "insertOrUpdate",
        }, new golemio_validator_1.JSONSchemaValidator("VPTripsRepositoryValidator", VPTripsModel_1.VPTripsModel.jsonSchema));
        this.findAllAssocTripIds = async (tripIds) => {
            const connection = this.connector.getConnection();
            return Array.isArray(tripIds) && tripIds.length > 0
                ? (await connection.query(RawQueryProvider_1.RawQueryProvider.getFindAssociatedTripsQuery(), {
                    type: sequelize_1.default.QueryTypes.SELECT,
                    bind: { tripIds: tripIds.map((id) => `${id}%`) },
                })).map((res) => ({
                    id: res.id,
                    gtfs_trip_id: res.gtfs_trip_id,
                    gtfs_block_id: res.gtfs_block_id,
                    gtfs_route_type: res.gtfs_route_type,
                    start_timestamp: res.start_timestamp,
                    end_timestamp: res.end_timestamp,
                    run_number: res.run_number,
                    internal_run_number: res.internal_run_number,
                    origin_route_name: res.origin_route_name,
                    internal_route_name: res.internal_route_name,
                    vehicle_registration_number: res.vehicle_registration_number,
                }))
                : [];
        };
        this.findGTFSTripId = async (trip) => {
            let foundGtfsTrips = [];
            if (Number(trip.start_cis_stop_id) >= 5400000 && Number(trip.start_cis_stop_id) < 5500000) {
                // trains
                // array of founded gtfs trips, 0 or 1 or more with same block_id
                foundGtfsTrips = await this.findGTFSTripIdsTrain(trip);
            }
            else {
                // other
                foundGtfsTrips = await this.findGTFSTripIdBasic(trip);
            }
            if (foundGtfsTrips.length < 1) {
                return;
            }
            // update existing trip with gtfs_trip_id, gtfs_block_id and other gtfs data
            const firstFoundGtfsTrip = foundGtfsTrips.shift();
            const { startTimestamp, endTimestamp } = this.deduceTimestamps(firstFoundGtfsTrip.min_stop_time, firstFoundGtfsTrip.max_stop_time, trip.start_timestamp);
            await this.update({
                ...firstFoundGtfsTrip,
                internal_run_number: firstFoundGtfsTrip.internal_run_number ?? trip.run_number,
                internal_route_name: firstFoundGtfsTrip.internal_route_name ?? trip.origin_route_name,
                start_timestamp: startTimestamp,
                end_timestamp: endTimestamp,
            }, {
                where: {
                    id: trip.id,
                },
            });
            const newIds = [
                {
                    id: trip.id,
                    gtfs_trip_id: firstFoundGtfsTrip.gtfs_trip_id,
                    gtfs_block_id: firstFoundGtfsTrip.gtfs_block_id,
                    gtfs_route_type: firstFoundGtfsTrip.gtfs_route_type,
                    internal_run_number: firstFoundGtfsTrip.internal_run_number ?? trip.run_number,
                    internal_route_name: firstFoundGtfsTrip.internal_route_name ?? trip.origin_route_name,
                    start_timestamp: startTimestamp.toISOString(),
                    end_timestamp: endTimestamp.toISOString(),
                },
            ];
            // for other trips insert new rows with suffixed id
            for (const foundTrip of foundGtfsTrips) {
                const newId = `${trip.id}_gtfs_trip_id_${foundTrip.gtfs_trip_id}`;
                foundTrip.id = newId;
                const { startTimestamp, endTimestamp } = this.deduceTimestamps(foundTrip.min_stop_time, foundTrip.max_stop_time, trip.start_timestamp);
                await this.save({
                    ...trip,
                    ...foundTrip,
                    internal_run_number: foundTrip.internal_run_number ?? trip.run_number,
                    internal_route_name: foundTrip.internal_route_name ?? trip.origin_route_name,
                    start_timestamp: startTimestamp,
                    end_timestamp: endTimestamp,
                });
                newIds.push({
                    id: newId,
                    gtfs_trip_id: foundTrip.gtfs_trip_id,
                    gtfs_block_id: foundTrip.gtfs_block_id,
                    gtfs_route_type: foundTrip.gtfs_route_type,
                    start_timestamp: startTimestamp.toISOString(),
                    end_timestamp: endTimestamp.toISOString(),
                    run_number: trip.run_number,
                    internal_run_number: foundTrip.internal_run_number ?? trip.run_number,
                    internal_route_name: foundTrip.internal_route_name ?? trip.origin_route_name,
                    origin_route_name: trip.origin_route_name,
                    vehicle_registration_number: trip.vehicle_registration_number,
                });
            }
            return newIds;
        };
        this.deleteNHoursOldData = async (hours) => {
            try {
                return await this.sequelizeModel.destroy({
                    where: {
                        updated_at: {
                            [sequelize_1.Op.lt]: sequelize_1.default.literal(`NOW() - INTERVAL '${hours} HOURS'`),
                        },
                    },
                });
            }
            catch (err) {
                throw new golemio_errors_1.GeneralError("Error while purging old data", this.constructor.name, err);
            }
        };
        this.deduceTimestamps = (min_stop_time, max_stop_time, origin_start_timestamp) => {
            return {
                startTimestamp: DateTimeUtils_1.DateTimeUtils.getStopDateTimeForTripOrigin(min_stop_time, origin_start_timestamp),
                endTimestamp: DateTimeUtils_1.DateTimeUtils.getStopDateTimeForTripOrigin(max_stop_time, origin_start_timestamp),
            };
        };
        this.upsertCommonRunTrip = async ({ run }, gtfsTrip) => {
            try {
                const [record] = await this.sequelizeModel.upsert(TripsMapper_1.default.mapCommonRunToDto(gtfsTrip, run), {
                    returning: true,
                });
                return record.get({ plain: true });
            }
            catch (err) {
                throw new golemio_errors_1.GeneralError("upsertCommonRunTrip: Error while saving to database.", this.name, err);
            }
        };
        this.upsertMetroRunTrip = async (runInput, gtfsTrip) => {
            try {
                const [record] = await this.sequelizeModel.upsert(TripsMapper_1.default.mapMetroRunToDto(gtfsTrip, runInput), {
                    returning: true,
                });
                return record.get({ plain: true });
            }
            catch (err) {
                throw new golemio_errors_1.GeneralError("upsertMetroRunTrip: Error while saving to database.", this.name, err);
            }
        };
        this.upsertEntity = async (tripDto) => {
            try {
                const [record] = await this.sequelizeModel.upsert(tripDto, { returning: true });
                return record.get({ plain: true });
            }
            catch (err) {
                throw new golemio_errors_1.GeneralError("upsertNotPublic: error while saving data", this.name, err);
            }
        };
        this.getPropagateDelayTripsWithPositions = async (data) => {
            const where = {
                gtfs_trip_id: data.nextGtfsTrips.map((element) => element.trip_id),
            };
            if (data.currentRegistrationNumber) {
                where.vehicle_registration_number = data.currentRegistrationNumber;
            }
            return this.sequelizeModel.findAll({
                include: [
                    {
                        as: "last_position",
                        model: this.positionsRepository["sequelizeModel"],
                        where: {
                            state_position: {
                                [sequelize_1.default.Op.in]: [const_2.StatePositionEnum.BEFORE_TRACK, const_2.StatePositionEnum.INVISIBLE],
                            },
                            origin_timestamp: data.currentOriginTimestamp,
                        },
                    },
                ],
                where: where,
                order: [["start_timestamp", "ASC"]],
            });
        };
        this.hasOne = (model, options) => {
            return this.sequelizeModel.hasOne(model, options);
        };
        this.findAll = async (options) => {
            return this.sequelizeModel.findAll(options);
        };
        this.findGTFSTripIdBasic = async (trip) => {
            const connection = this.connector.getConnection();
            const startDate = (0, moment_timezone_1.default)(trip.start_timestamp).tz("Europe/Prague");
            const startDateDayBefore = startDate.clone().subtract(1, "day");
            const startDateYMD = startDate.format("YYYY-MM-DD");
            const startDateDayName = startDate.format("dddd").toLowerCase();
            const startDateDayBeforeYMD = startDateDayBefore.format("YYYY-MM-DD");
            const startDateDayBeforeDayName = startDateDayBefore.format("dddd").toLowerCase();
            const parsedAswStopId = trip.start_asw_stop_id ? parseInt(trip.start_asw_stop_id, 10) : 0;
            const replacements = {
                startDateDayBeforeYMD,
                startDateYMD,
                cisLineShortName: trip.cis_line_short_name,
                startCisStopId: trip.start_cis_stop_id || 0,
                startAswStopId: parsedAswStopId % 10000,
                startAswNodeID: Math.floor(parsedAswStopId / 10000),
                startCisStopPlatformCode: trip.start_cis_stop_platform_code,
                startDateFormatted: startDate.utc().format(),
                startDateDayName,
                startDateDayBeforeDayName,
            };
            const sqlQuery = RawQueryProvider_1.RawQueryProvider.getFindGtfsCommonTripsQuery();
            let result = (await connection.query(sqlQuery, {
                type: sequelize_1.default.QueryTypes.SELECT,
                replacements,
            }));
            if (result.length < 1) {
                this.logger.warn({ ...replacements, parsedAswStopId }, `${this.constructor.name}: Model data was not found for id '${trip.id}' (basic).`);
            }
            else if (result.length > 1) {
                this.logger.warn(replacements, `There are too many gtfs trips (${result.length}) linked with id '${trip.id}' (trip).`);
                result = [];
            }
            return result;
        };
        this.findGTFSTripIdsTrain = async (trip) => {
            const connection = this.connector.getConnection();
            const sqlQuery = RawQueryProvider_1.RawQueryProvider.getFindGtfsTrainTripsQuery();
            let results = (await connection.query(sqlQuery, {
                type: sequelize_1.default.QueryTypes.SELECT,
                replacements: {
                    tripId: trip.id,
                },
            }));
            if (results.length < 1) {
                this.logger.verbose(`Model data was not found for id '${trip.id}' (train).`, true, this.constructor.name, 5001);
            }
            else if (results.length > 10) {
                this.logger.verbose(`There are too many gtfs trips (${results.length}) linked with id '${trip.id}' (train).`);
                results = [];
            }
            return results;
        };
        /**
         * Return all valid trips for GTFS Realtime feed
         *
         * @returns {Promise<any[]>}
         */
        this.findAllForGTFSRt = async (minValidToDateTime) => {
            return this.sequelizeModel.findAll({
                attributes: [
                    "id",
                    "run_number",
                    "internal_run_number",
                    "start_timestamp",
                    "cis_line_id",
                    "cis_trip_number",
                    "start_time",
                    "vehicle_registration_number",
                    "gtfs_trip_id",
                    "gtfs_route_id",
                    "gtfs_route_type",
                    "gtfs_route_short_name",
                    "provider_source_type",
                    "wheelchair_accessible",
                ],
                include: [
                    {
                        as: "last_position",
                        model: this.positionsRepository["sequelizeModel"],
                        attributes: [
                            "is_canceled",
                            "origin_timestamp",
                            "last_stop_sequence",
                            "bearing",
                            "lat",
                            "lng",
                            "speed",
                            "delay",
                            "state_position",
                        ],
                        where: {
                            state_position: {
                                [sequelize_1.default.Op.in]: [
                                    const_2.StatePositionEnum.ON_TRACK,
                                    const_2.StatePositionEnum.AT_STOP,
                                    const_2.StatePositionEnum.CANCELED,
                                    const_2.StatePositionEnum.BEFORE_TRACK,
                                    const_2.StatePositionEnum.BEFORE_TRACK_DELAYED,
                                ],
                            },
                            valid_to: {
                                [sequelize_1.default.Op.or]: [null, { [sequelize_1.default.Op.gte]: minValidToDateTime }],
                            },
                            lat: {
                                [sequelize_1.default.Op.ne]: null,
                            },
                            lng: {
                                [sequelize_1.default.Op.ne]: null,
                            },
                        },
                    },
                    {
                        as: "vehicle_descriptor",
                        model: this.descriptorRepository["sequelizeModel"],
                        attributes: ["is_air_conditioned", "has_usb_chargers"],
                    },
                ],
                order: [
                    ["updated_at", "DESC"],
                    ["id", "ASC"],
                ],
            });
        };
        this.findAllForPublicCache = async (minValidToDateTime) => {
            try {
                return this.sequelizeModel.findAll({
                    attributes: [
                        "id",
                        "gtfs_trip_id",
                        "gtfs_route_type",
                        "gtfs_route_short_name",
                        "cis_trip_number",
                        [sequelize_1.default.literal("last_position.lat"), "lat"],
                        [sequelize_1.default.literal("last_position.lng"), "lng"],
                        [sequelize_1.default.literal("last_position.bearing"), "bearing"],
                        [sequelize_1.default.literal("last_position.delay"), "delay"],
                        [sequelize_1.default.literal("last_position.state_position"), "state_position"],
                        "created_at",
                        // Additional properties for detailed vehiclepositions in public API
                        "wheelchair_accessible",
                        "run_number",
                        "internal_run_number",
                        "vehicle_registration_number",
                        "agency_name_scheduled",
                        "agency_name_real",
                        "origin_route_name",
                        "gtfs_direction_id",
                        "gtfs_shape_id",
                        "gtfs_trip_headsign",
                        "provider_source_type",
                        [sequelize_1.default.literal("last_position.shape_dist_traveled"), "shape_dist_traveled"],
                        [sequelize_1.default.literal("last_position.last_stop_sequence"), "last_stop_sequence"],
                        [sequelize_1.default.literal("last_position.origin_timestamp"), "origin_timestamp"],
                        [sequelize_1.default.literal("last_position.last_stop_headsign"), "last_stop_headsign"],
                    ],
                    include: [
                        {
                            as: "last_position",
                            model: this.positionsRepository["sequelizeModel"],
                            attributes: [],
                            required: true,
                            where: {
                                state_position: {
                                    [sequelize_1.default.Op.in]: [
                                        const_2.StatePositionEnum.ON_TRACK,
                                        const_2.StatePositionEnum.AT_STOP,
                                        const_2.StatePositionEnum.CANCELED,
                                        const_2.StatePositionEnum.OFF_TRACK,
                                        const_2.StatePositionEnum.BEFORE_TRACK,
                                        const_2.StatePositionEnum.BEFORE_TRACK_DELAYED,
                                    ],
                                },
                                lat: {
                                    [sequelize_1.default.Op.ne]: null,
                                },
                                lng: {
                                    [sequelize_1.default.Op.ne]: null,
                                },
                                valid_to: {
                                    [sequelize_1.default.Op.or]: [null, { [sequelize_1.default.Op.gte]: minValidToDateTime }],
                                },
                            },
                        },
                    ],
                    where: {
                        gtfs_trip_id: {
                            [sequelize_1.default.Op.ne]: null,
                        },
                    },
                    order: [
                        ["created_at", "ASC"],
                        ["id", "ASC"],
                    ],
                    raw: true,
                });
            }
            catch (err) {
                throw new golemio_errors_1.GeneralError("findAllForPublicCache: failed to get trips", this.name, err);
            }
        };
        this.refreshGtfsTripData = async () => {
            const connection = this.connector.getConnection();
            try {
                const [_result, rowCount] = await connection.query(RawQueryProvider_1.RawQueryProvider.getUpdateGtfsTripDataQuery(), {
                    type: sequelize_1.QueryTypes.UPDATE,
                });
                this.logger.info(`refreshGtfsTripData: updated ${rowCount} trips`);
            }
            catch (err) {
                throw new golemio_errors_1.GeneralError("refreshGtfsTripData: error while updating data", this.name, err);
            }
        };
        this.getTripsWithInvalidGtfsTripId = async (providerSourceType) => {
            const connection = this.connector.getConnection();
            try {
                return await connection.query(RawQueryProvider_1.RawQueryProvider.getFindTripsWithInvalidGtfsTripIdQuery(), {
                    type: sequelize_1.QueryTypes.SELECT,
                    raw: true,
                    bind: { providerSourceType },
                });
            }
            catch (err) {
                throw new golemio_errors_1.GeneralError("getTripsWithInvalidGtfsTripId: error while getting trips", this.name, err);
            }
        };
        this.logger = Di_1.VPContainer.resolve(CoreToken_1.CoreToken.Logger);
        this.connector = Di_1.VPContainer.resolve(CoreToken_1.CoreToken.PostgresConnector);
        this.positionsRepository = new PositionsRepository_1.PositionsRepository();
        this.descriptorRepository = Di_1.VPContainer.resolve(VPContainerToken_1.VPContainerToken.DescriptorRepository);
        this.sequelizeModel.belongsTo(this.positionsRepository["sequelizeModel"], {
            as: "last_position",
            foreignKey: "last_position_id",
        });
        this.sequelizeModel.belongsTo(this.descriptorRepository["sequelizeModel"], {
            as: "vehicle_descriptor",
            foreignKey: "vehicle_registration_number",
            targetKey: "registration_number",
            scope: {
                [sequelize_1.default.Op.and]: [
                    sequelize_1.default.literal(`${VPTripsModel_1.VPTripsModel.TABLE_NAME}.gtfs_route_type = vehicle_descriptor.gtfs_route_type`),
                ],
            },
        });
    }
};
exports.TripsRepository = TripsRepository;
exports.TripsRepository = TripsRepository = __decorate([
    (0, tsyringe_1.injectable)(),
    __metadata("design:paramtypes", [])
], TripsRepository);
//# sourceMappingURL=TripsRepository.js.map