"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.VideoViewerStats = void 0;
const tslib_1 = require("tslib");
const core_utils_1 = require("../../../helpers/core-utils");
const geo_ip_1 = require("../../../helpers/geo-ip");
const logger_1 = require("../../../helpers/logger");
const constants_1 = require("../../../initializers/constants");
const database_1 = require("../../../initializers/database");
const send_1 = require("../../activitypub/send");
const url_1 = require("../../activitypub/url");
const redis_1 = require("../../redis");
const video_1 = require("../../../models/video/video");
const local_video_viewer_1 = require("../../../models/view/local-video-viewer");
const local_video_viewer_watch_section_1 = require("../../../models/view/local-video-viewer-watch-section");
const lTags = (0, logger_1.loggerTagsFactory)('views');
class VideoViewerStats {
    constructor() {
        this.processingViewersStats = false;
        setInterval(() => this.processViewerStats(), constants_1.VIEW_LIFETIME.VIEWER_STATS);
    }
    addLocalViewer(options) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const { video, ip, viewEvent, currentTime } = options;
            logger_1.logger.debug('Adding local viewer to video stats %s.', video.uuid, Object.assign({ currentTime, viewEvent }, lTags(video.uuid)));
            return this.updateLocalViewerStats({ video, viewEvent, currentTime, ip });
        });
    }
    getWatchTime(videoId, ip) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const stats = yield redis_1.Redis.Instance.getLocalVideoViewer({ ip, videoId });
            return (stats === null || stats === void 0 ? void 0 : stats.watchTime) || 0;
        });
    }
    updateLocalViewerStats(options) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const { video, ip, viewEvent, currentTime } = options;
            const nowMs = new Date().getTime();
            let stats = yield redis_1.Redis.Instance.getLocalVideoViewer({ ip, videoId: video.id });
            if (stats && stats.watchSections.length >= constants_1.MAX_LOCAL_VIEWER_WATCH_SECTIONS) {
                logger_1.logger.warn('Too much watch section to store for a viewer, skipping this one', Object.assign({ currentTime, viewEvent }, lTags(video.uuid)));
                return;
            }
            if (!stats) {
                const country = yield geo_ip_1.GeoIP.Instance.safeCountryISOLookup(ip);
                stats = {
                    firstUpdated: nowMs,
                    lastUpdated: nowMs,
                    watchSections: [],
                    watchTime: 0,
                    country,
                    videoId: video.id
                };
            }
            stats.lastUpdated = nowMs;
            if (viewEvent === 'seek' || stats.watchSections.length === 0) {
                stats.watchSections.push({
                    start: currentTime,
                    end: currentTime
                });
            }
            else {
                const lastSection = stats.watchSections[stats.watchSections.length - 1];
                if (lastSection.start > currentTime) {
                    logger_1.logger.warn('Invalid end watch section %d. Last start record was at %d.', currentTime, lastSection.start);
                }
                else {
                    lastSection.end = currentTime;
                }
            }
            stats.watchTime = this.buildWatchTimeFromSections(stats.watchSections);
            logger_1.logger.debug('Set local video viewer stats for video %s.', video.uuid, Object.assign({ stats }, lTags(video.uuid)));
            yield redis_1.Redis.Instance.setLocalVideoViewer(ip, video.id, stats);
        });
    }
    processViewerStats() {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (this.processingViewersStats)
                return;
            this.processingViewersStats = true;
            if (!(0, core_utils_1.isTestOrDevInstance)())
                logger_1.logger.info('Processing viewer statistics.', lTags());
            const now = new Date().getTime();
            try {
                const allKeys = yield redis_1.Redis.Instance.listLocalVideoViewerKeys();
                for (const key of allKeys) {
                    const stats = yield redis_1.Redis.Instance.getLocalVideoViewer({ key });
                    if (stats.lastUpdated > now - constants_1.VIEW_LIFETIME.VIEWER_STATS) {
                        continue;
                    }
                    try {
                        yield database_1.sequelizeTypescript.transaction((t) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                            const video = yield video_1.VideoModel.load(stats.videoId, t);
                            if (!video)
                                return;
                            const statsModel = yield this.saveViewerStats(video, stats, t);
                            if (video.remote) {
                                yield (0, send_1.sendCreateWatchAction)(statsModel, t);
                            }
                        }));
                        yield redis_1.Redis.Instance.deleteLocalVideoViewersKeys(key);
                    }
                    catch (err) {
                        logger_1.logger.error('Cannot process viewer stats for Redis key %s.', key, Object.assign({ err }, lTags()));
                    }
                }
            }
            catch (err) {
                logger_1.logger.error('Error in video save viewers stats scheduler.', Object.assign({ err }, lTags()));
            }
            this.processingViewersStats = false;
        });
    }
    saveViewerStats(video, stats, transaction) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const statsModel = new local_video_viewer_1.LocalVideoViewerModel({
                startDate: new Date(stats.firstUpdated),
                endDate: new Date(stats.lastUpdated),
                watchTime: stats.watchTime,
                country: stats.country,
                videoId: video.id
            });
            statsModel.url = (0, url_1.getLocalVideoViewerActivityPubUrl)(statsModel);
            statsModel.Video = video;
            yield statsModel.save({ transaction });
            statsModel.WatchSections = yield local_video_viewer_watch_section_1.LocalVideoViewerWatchSectionModel.bulkCreateSections({
                localVideoViewerId: statsModel.id,
                watchSections: stats.watchSections,
                transaction
            });
            return statsModel;
        });
    }
    buildWatchTimeFromSections(sections) {
        return sections.reduce((p, current) => p + (current.end - current.start), 0);
    }
}
exports.VideoViewerStats = VideoViewerStats;
//# sourceMappingURL=video-viewer-stats.js.map