"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.videoAnalyticsService = exports.VideoAnalyticsService = void 0;
const database_1 = require("../../../config/database");
const logger_1 = require("../../../utils/logger");
const crypto_1 = require("crypto");
const library_1 = require("@prisma/client/runtime/library");
class VideoAnalyticsService {
    /**
     * Hash IP address for privacy (SHA-256)
     */
    hashIpAddress(ipAddress) {
        return (0, crypto_1.createHash)('sha256').update(ipAddress).digest('hex');
    }
    /**
     * Hash user agent for privacy (SHA-256, truncate version numbers first)
     */
    hashUserAgent(userAgent) {
        // Truncate version numbers for better privacy
        const truncated = userAgent
            .replace(/\d+\.\d+\.\d+/g, 'X.X.X') // Remove version numbers
            .slice(0, 200); // Limit length
        return (0, crypto_1.createHash)('sha256').update(truncated).digest('hex');
    }
    /**
     * Record a new video view
     */
    async recordView(params) {
        const { videoId, userId, ipAddress, userAgent, referer } = params;
        try {
            const view = await database_1.prisma.videoView.create({
                data: {
                    videoId,
                    userId: userId || null,
                    ipAddressHash: ipAddress ? this.hashIpAddress(ipAddress) : null,
                    userAgentHash: userAgent ? this.hashUserAgent(userAgent) : null,
                    referer: referer || null,
                    watchTimeSeconds: 0,
                    completed: false,
                },
            });
            // Increment view counter on video
            await database_1.prisma.video.update({
                where: { id: videoId },
                data: {
                    viewCount: {
                        increment: 1,
                    },
                },
            });
            logger_1.logger.info(`Recorded view for video ${videoId}`, { viewId: view.id });
            return view.id;
        }
        catch (error) {
            logger_1.logger.error('Failed to record video view', { error, videoId });
            throw error;
        }
    }
    /**
     * Record a video event (play, pause, seek, complete)
     */
    async recordEvent(params) {
        const { videoId, viewId, eventType, timestamp } = params;
        try {
            await database_1.prisma.videoEvent.create({
                data: {
                    videoId,
                    viewId: viewId || null,
                    eventType,
                    timestamp: new library_1.Decimal(timestamp),
                },
            });
            // If complete event, mark view as completed and increment finishCount
            if (eventType === 'complete' && viewId) {
                await database_1.prisma.videoView.update({
                    where: { id: viewId },
                    data: { completed: true },
                });
                await database_1.prisma.video.update({
                    where: { id: videoId },
                    data: {
                        finishCount: {
                            increment: 1,
                        },
                    },
                });
            }
        }
        catch (error) {
            logger_1.logger.error('Failed to record video event', { error, videoId, eventType });
            // Don't throw - we don't want analytics failures to break playback
        }
    }
    /**
     * Update watch time for a view (called periodically during playback)
     */
    async updateWatchTime(viewId, watchTimeSeconds) {
        try {
            await database_1.prisma.videoView.update({
                where: { id: viewId },
                data: {
                    watchTimeSeconds,
                    updatedAt: new Date(),
                },
            });
        }
        catch (error) {
            logger_1.logger.error('Failed to update watch time', { error, viewId });
            // Don't throw - we don't want analytics failures to break playback
        }
    }
    /**
     * Aggregate analytics for a video
     */
    async aggregateVideoAnalytics(videoId) {
        try {
            const [views, uniqueViewers, totalWatchTime, avgWatchTime, completions] = await Promise.all([
                // Total views
                database_1.prisma.videoView.count({
                    where: { videoId },
                }),
                // Unique viewers (registered users only)
                database_1.prisma.videoView.findMany({
                    where: {
                        videoId,
                        userId: { not: null },
                    },
                    distinct: ['userId'],
                    select: { userId: true },
                }).then(views => views.length),
                // Total watch time
                database_1.prisma.videoView.aggregate({
                    where: { videoId },
                    _sum: {
                        watchTimeSeconds: true,
                    },
                }),
                // Average watch time
                database_1.prisma.videoView.aggregate({
                    where: { videoId },
                    _avg: {
                        watchTimeSeconds: true,
                    },
                }),
                // Completions
                database_1.prisma.videoView.count({
                    where: {
                        videoId,
                        completed: true,
                    },
                }),
            ]);
            // Get video duration for completion rate calculation
            const video = await database_1.prisma.video.findUnique({
                where: { id: videoId },
                select: { durationSeconds: true },
            });
            const completionRate = views > 0 && video?.durationSeconds
                ? new library_1.Decimal((completions / views) * 100)
                : new library_1.Decimal(0);
            // Update video analytics fields
            await database_1.prisma.video.update({
                where: { id: videoId },
                data: {
                    uniqueViewers,
                    totalWatchTimeSeconds: totalWatchTime._sum.watchTimeSeconds || 0,
                    averageWatchTimeSeconds: avgWatchTime._avg.watchTimeSeconds
                        ? new library_1.Decimal(avgWatchTime._avg.watchTimeSeconds)
                        : new library_1.Decimal(0),
                    completionRate,
                },
            });
            logger_1.logger.info(`Aggregated analytics for video ${videoId}`, {
                views,
                uniqueViewers,
                completionRate: completionRate.toNumber(),
            });
        }
        catch (error) {
            logger_1.logger.error('Failed to aggregate video analytics', { error, videoId });
            throw error;
        }
    }
    /**
     * Get detailed analytics for a video
     */
    async getVideoAnalytics(videoId, startDate, endDate) {
        const where = { videoId };
        if (startDate || endDate) {
            where.createdAt = {};
            if (startDate)
                where.createdAt.gte = startDate;
            if (endDate)
                where.createdAt.lte = endDate;
        }
        try {
            // Overview stats
            const video = await database_1.prisma.video.findUnique({
                where: { id: videoId },
                select: {
                    viewCount: true,
                    uniqueViewers: true,
                    totalWatchTimeSeconds: true,
                    averageWatchTimeSeconds: true,
                    completionRate: true,
                },
            });
            if (!video) {
                throw new Error('Video not found');
            }
            // Views over time (last 30 days, grouped by day)
            const thirtyDaysAgo = new Date();
            thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
            const viewsByDay = await database_1.prisma.$queryRaw `
        SELECT
          DATE(created_at) as date,
          COUNT(*) as count
        FROM video_views
        WHERE video_id = ${videoId}
          AND created_at >= ${thirtyDaysAgo}
        GROUP BY DATE(created_at)
        ORDER BY DATE(created_at) ASC
      `;
            // Top referrers
            const topReferrers = await database_1.prisma.videoView.groupBy({
                by: ['referer'],
                where: {
                    videoId,
                    referer: { not: null },
                },
                _count: {
                    referer: true,
                },
                orderBy: {
                    _count: {
                        referer: 'desc',
                    },
                },
                take: 10,
            });
            // Registered viewers
            const registeredViewers = await database_1.prisma.videoView.findMany({
                where: {
                    videoId,
                    userId: { not: null },
                },
                include: {
                    user: {
                        select: {
                            id: true,
                            name: true,
                            email: true,
                        },
                    },
                },
                orderBy: {
                    createdAt: 'desc',
                },
                take: 100,
            });
            return {
                overview: {
                    totalViews: video.viewCount,
                    uniqueViewers: video.uniqueViewers,
                    averageWatchTime: video.averageWatchTimeSeconds.toNumber(),
                    completionRate: video.completionRate.toNumber(),
                    totalWatchTime: video.totalWatchTimeSeconds,
                },
                viewsOverTime: viewsByDay.map((row) => ({
                    date: row.date,
                    count: Number(row.count),
                })),
                topReferrers: topReferrers.map((item) => ({
                    referer: item.referer || 'Direct',
                    count: item._count.referer,
                })),
                registeredViewers: registeredViewers.map((view) => ({
                    userId: view.userId,
                    userName: view.user?.name || null,
                    userEmail: view.user?.email || '',
                    watchTime: view.watchTimeSeconds,
                    completed: view.completed,
                    viewedAt: view.createdAt,
                })),
            };
        }
        catch (error) {
            logger_1.logger.error('Failed to get video analytics', { error, videoId });
            throw error;
        }
    }
    /**
     * Get top videos by a metric
     */
    async getTopVideos(metric, limit = 10) {
        try {
            const orderBy = metric === 'views' ? { viewCount: 'desc' } : { totalWatchTimeSeconds: 'desc' };
            const videos = await database_1.prisma.video.findMany({
                select: {
                    id: true,
                    title: true,
                    viewCount: true,
                    totalWatchTimeSeconds: true,
                },
                orderBy,
                take: limit,
            });
            return videos.map((video) => ({
                id: video.id,
                title: video.title,
                value: metric === 'views' ? video.viewCount : video.totalWatchTimeSeconds,
            }));
        }
        catch (error) {
            logger_1.logger.error('Failed to get top videos', { error, metric });
            throw error;
        }
    }
    /**
     * Reset analytics for a video
     */
    async resetAnalytics(videoId) {
        try {
            // Delete all views and events
            await database_1.prisma.videoView.deleteMany({
                where: { videoId },
            });
            await database_1.prisma.videoEvent.deleteMany({
                where: { videoId },
            });
            // Reset counters
            await database_1.prisma.video.update({
                where: { id: videoId },
                data: {
                    viewCount: 0,
                    uniqueViewers: 0,
                    totalWatchTimeSeconds: 0,
                    averageWatchTimeSeconds: new library_1.Decimal(0),
                    completionRate: new library_1.Decimal(0),
                    finishCount: 0,
                },
            });
            logger_1.logger.info(`Reset analytics for video ${videoId}`);
        }
        catch (error) {
            logger_1.logger.error('Failed to reset analytics', { error, videoId });
            throw error;
        }
    }
}
exports.VideoAnalyticsService = VideoAnalyticsService;
exports.videoAnalyticsService = new VideoAnalyticsService();
//# sourceMappingURL=video-analytics.service.js.map