"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.videoActionsRoutes = videoActionsRoutes;
const database_1 = require("../../../config/database");
const auth_1 = require("../middleware/auth");
const video_analytics_service_1 = require("../services/video-analytics.service");
const logger_1 = require("../../../utils/logger");
const jsonwebtoken_1 = require("jsonwebtoken");
const env_1 = require("../../../config/env");
const path_1 = require("path");
const zod_1 = require("zod");
const UpdateVideoSchema = zod_1.z.object({
    title: zod_1.z.string().min(1).max(500).optional(),
    producer: zod_1.z.string().max(200).nullable().optional(),
    creator: zod_1.z.string().max(200).nullable().optional(),
    category: zod_1.z.enum(['videos', 'curated', 'compilations', 'playback', 'highlights']).nullable().optional(),
    tags: zod_1.z.array(zod_1.z.string().max(100)).max(50).nullable().optional(),
    quality: zod_1.z.string().max(50).nullable().optional(),
    position: zod_1.z.number().int().min(0).nullable().optional(),
    isShort: zod_1.z.boolean().optional(),
    accessLevel: zod_1.z.enum(['free', 'member', 'premium']).optional(),
});
async function videoActionsRoutes(fastify) {
    /**
     * PATCH /videos/:id
     * Update video metadata
     */
    fastify.patch('/:id', {
        preHandler: auth_1.requireAdminRole,
    }, async (request, reply) => {
        const videoId = parseInt(request.params.id);
        const parseResult = UpdateVideoSchema.safeParse(request.body);
        if (!parseResult.success) {
            return reply.code(400).send({ message: 'Invalid input', errors: parseResult.error.errors });
        }
        try {
            const video = await database_1.prisma.video.findUnique({ where: { id: videoId } });
            if (!video) {
                return reply.code(404).send({ message: 'Video not found' });
            }
            const data = {};
            const updates = parseResult.data;
            if (updates.title !== undefined)
                data.title = updates.title;
            if (updates.producer !== undefined)
                data.producer = updates.producer;
            if (updates.creator !== undefined)
                data.creator = updates.creator;
            if (updates.category !== undefined)
                data.category = updates.category;
            if (updates.tags !== undefined)
                data.tags = updates.tags;
            if (updates.quality !== undefined)
                data.quality = updates.quality;
            if (updates.position !== undefined)
                data.position = updates.position;
            if (updates.isShort !== undefined)
                data.isShort = updates.isShort;
            if (updates.accessLevel !== undefined)
                data.accessLevel = updates.accessLevel;
            const updatedVideo = await database_1.prisma.video.update({
                where: { id: videoId },
                data,
            });
            logger_1.logger.info(`Updated video ${videoId} metadata`, { fields: Object.keys(data) });
            return {
                success: true,
                video: {
                    ...updatedVideo,
                    duration: updatedVideo.durationSeconds,
                    thumbnailUrl: updatedVideo.thumbnailPath ? `/media/videos/${updatedVideo.id}/thumbnail` : null,
                },
            };
        }
        catch (error) {
            logger_1.logger.error('Failed to update video', { error, videoId });
            return reply.code(500).send({ message: 'Failed to update video' });
        }
    });
    /**
     * POST /videos/:id/duplicate
     * Duplicate a video with a new title
     */
    fastify.post('/:id/duplicate', {
        preHandler: auth_1.requireAdminRole,
    }, async (request, reply) => {
        const videoId = parseInt(request.params.id);
        const { title } = request.body || {};
        try {
            const originalVideo = await database_1.prisma.video.findUnique({
                where: { id: videoId },
            });
            if (!originalVideo) {
                return reply.code(404).send({ message: 'Video not found' });
            }
            // Create duplicate with new title
            const duplicateTitle = title || `${originalVideo.title || originalVideo.filename} (Copy)`;
            const duplicate = await database_1.prisma.video.create({
                data: {
                    path: originalVideo.path, // Same file path
                    filename: originalVideo.filename,
                    producer: originalVideo.producer,
                    creator: originalVideo.creator,
                    title: duplicateTitle,
                    durationSeconds: originalVideo.durationSeconds,
                    quality: originalVideo.quality,
                    orientation: originalVideo.orientation,
                    hasAudio: originalVideo.hasAudio,
                    fileSize: originalVideo.fileSize,
                    fileHash: originalVideo.fileHash,
                    width: originalVideo.width,
                    height: originalVideo.height,
                    thumbnailPath: originalVideo.thumbnailPath,
                    tags: originalVideo.tags,
                    directoryType: originalVideo.directoryType,
                    category: originalVideo.category,
                    uploaderId: originalVideo.uploaderId,
                },
            });
            logger_1.logger.info(`Duplicated video ${videoId} to ${duplicate.id}`, { originalTitle: originalVideo.title, newTitle: duplicateTitle });
            return {
                success: true,
                duplicate: {
                    id: duplicate.id,
                    title: duplicate.title,
                },
            };
        }
        catch (error) {
            logger_1.logger.error('Failed to duplicate video', { error, videoId });
            return reply.code(500).send({ message: 'Failed to duplicate video' });
        }
    });
    /**
     * POST /videos/:id/replace
     * Replace video file while keeping metadata and URL
     * Note: This endpoint accepts a new file path - actual file upload should go through upload routes
     */
    const ReplaceVideoSchema = zod_1.z.object({
        newPath: zod_1.z.string().min(1).max(500),
        newFilename: zod_1.z.string().min(1).max(255),
        durationSeconds: zod_1.z.number().optional(),
        width: zod_1.z.number().int().optional(),
        height: zod_1.z.number().int().optional(),
        fileSize: zod_1.z.number().optional(),
    });
    fastify.post('/:id/replace', {
        preHandler: auth_1.requireAdminRole,
    }, async (request, reply) => {
        const videoId = parseInt(request.params.id);
        // Validate input with Zod
        const parseResult = ReplaceVideoSchema.safeParse(request.body);
        if (!parseResult.success) {
            return reply.code(400).send({ message: 'Invalid input' });
        }
        const { newPath, newFilename, durationSeconds, width, height, fileSize } = parseResult.data;
        // Path traversal protection
        if (newPath.includes('\0') || newFilename.includes('\0')) {
            return reply.code(400).send({ message: 'Invalid file path' });
        }
        const normalizedPath = (0, path_1.normalize)(newPath);
        if (normalizedPath.includes('..') || normalizedPath.startsWith('/') || normalizedPath.startsWith('\\')) {
            return reply.code(400).send({ message: 'Invalid file path: must be relative with no traversal' });
        }
        const sanitizedFilename = (0, path_1.basename)(newFilename);
        try {
            const existingVideo = await database_1.prisma.video.findUnique({
                where: { id: videoId },
            });
            if (!existingVideo) {
                return reply.code(404).send({ message: 'Video not found' });
            }
            // Update video with new file details
            const updatedVideo = await database_1.prisma.video.update({
                where: { id: videoId },
                data: {
                    path: normalizedPath,
                    filename: sanitizedFilename,
                    originalPath: existingVideo.path, // Save old path for reference
                    originalFilename: existingVideo.filename,
                    durationSeconds: durationSeconds || existingVideo.durationSeconds,
                    width: width || existingVideo.width,
                    height: height || existingVideo.height,
                    fileSize: fileSize ? BigInt(fileSize) : existingVideo.fileSize,
                    lastValidated: new Date(),
                    thumbnailPath: null, // Clear thumbnail, will be regenerated
                },
            });
            logger_1.logger.info(`Replaced video file for ${videoId}`, { oldPath: existingVideo.path, newPath });
            return {
                success: true,
                video: {
                    id: updatedVideo.id,
                    title: updatedVideo.title,
                    filename: updatedVideo.filename,
                },
            };
        }
        catch (error) {
            logger_1.logger.error('Failed to replace video', { error, videoId });
            return reply.code(500).send({ message: 'Failed to replace video' });
        }
    });
    /**
     * GET /videos/:id/analytics
     * Get detailed analytics for a video
     */
    fastify.get('/:id/analytics', {
        preHandler: auth_1.requireAdminRole,
    }, async (request, reply) => {
        const videoId = parseInt(request.params.id);
        const { startDate, endDate } = request.query;
        try {
            const analytics = await video_analytics_service_1.videoAnalyticsService.getVideoAnalytics(videoId, startDate ? new Date(startDate) : undefined, endDate ? new Date(endDate) : undefined);
            return analytics;
        }
        catch (error) {
            logger_1.logger.error('Failed to get video analytics', { error, videoId });
            if (error instanceof Error && error.message === 'Video not found') {
                return reply.code(404).send({ message: 'Video not found' });
            }
            return reply.code(500).send({ message: 'Failed to fetch analytics' });
        }
    });
    /**
     * POST /videos/:id/reset-analytics
     * Reset all analytics for a video
     */
    fastify.post('/:id/reset-analytics', {
        preHandler: auth_1.requireAdminRole,
    }, async (request, reply) => {
        const videoId = parseInt(request.params.id);
        try {
            await video_analytics_service_1.videoAnalyticsService.resetAnalytics(videoId);
            return {
                success: true,
                message: 'Analytics reset successfully',
            };
        }
        catch (error) {
            logger_1.logger.error('Failed to reset analytics', { error, videoId });
            return reply.code(500).send({ message: 'Failed to reset analytics' });
        }
    });
    /**
     * GET /videos/:id/preview-link
     * Generate a temporary preview link with expiring JWT token
     */
    fastify.get('/:id/preview-link', {
        preHandler: auth_1.requireAdminRole,
    }, async (request, reply) => {
        const videoId = parseInt(request.params.id);
        try {
            const video = await database_1.prisma.video.findUnique({
                where: { id: videoId },
            });
            if (!video) {
                return reply.code(404).send({ message: 'Video not found' });
            }
            // Generate JWT token that expires in 24 hours
            const expiryHours = parseInt(process.env.VIDEO_PREVIEW_LINK_EXPIRY_HOURS || '24');
            const token = (0, jsonwebtoken_1.sign)({
                videoId,
                purpose: 'preview',
            }, env_1.env.JWT_ACCESS_SECRET, { expiresIn: `${expiryHours}h` });
            const previewUrl = `${env_1.env.MEDIA_API_PUBLIC_URL}/api/videos/${videoId}/preview?token=${token}`;
            logger_1.logger.info(`Generated preview link for video ${videoId}`, { expiresInHours: expiryHours });
            return {
                previewUrl,
                expiresAt: new Date(Date.now() + expiryHours * 60 * 60 * 1000).toISOString(),
                expiryHours,
            };
        }
        catch (error) {
            logger_1.logger.error('Failed to generate preview link', { error, videoId });
            return reply.code(500).send({ message: 'Failed to generate preview link' });
        }
    });
    /**
     * GET /videos/analytics/top
     * Get top performing videos
     */
    fastify.get('/analytics/top', {
        preHandler: auth_1.requireAdminRole,
    }, async (request, reply) => {
        const metric = request.query.metric || 'views';
        const limit = parseInt(request.query.limit || '10');
        try {
            const topVideos = await video_analytics_service_1.videoAnalyticsService.getTopVideos(metric, limit);
            return {
                metric,
                videos: topVideos,
            };
        }
        catch (error) {
            logger_1.logger.error('Failed to get top videos', { error, metric });
            return reply.code(500).send({ message: 'Failed to fetch top videos' });
        }
    });
    /**
     * GET /videos/analytics/overview
     * Get global analytics overview across all videos
     */
    fastify.get('/analytics/overview', {
        preHandler: auth_1.requireAdminRole,
    }, async (request, reply) => {
        try {
            const [totalVideos, totalViews, totalWatchTime, avgCompletionRate] = await Promise.all([
                database_1.prisma.video.count(),
                database_1.prisma.video.aggregate({
                    _sum: {
                        viewCount: true,
                    },
                }),
                database_1.prisma.video.aggregate({
                    _sum: {
                        totalWatchTimeSeconds: true,
                    },
                }),
                database_1.prisma.video.aggregate({
                    _avg: {
                        completionRate: true,
                    },
                }),
            ]);
            return {
                totalVideos,
                totalViews: totalViews._sum.viewCount || 0,
                totalWatchTimeSeconds: totalWatchTime._sum.totalWatchTimeSeconds || 0,
                averageCompletionRate: avgCompletionRate._avg.completionRate?.toNumber() || 0,
            };
        }
        catch (error) {
            logger_1.logger.error('Failed to get analytics overview', { error });
            return reply.code(500).send({ message: 'Failed to fetch analytics overview' });
        }
    });
    /**
     * POST /videos/bulk-access-level
     * Set access level on multiple videos at once
     */
    fastify.post('/bulk-access-level', {
        preHandler: auth_1.requireAdminRole,
    }, async (request, reply) => {
        const schema = zod_1.z.object({
            videoIds: zod_1.z.array(zod_1.z.number().int()).min(1).max(500),
            accessLevel: zod_1.z.enum(['free', 'member', 'premium']),
        });
        const parseResult = schema.safeParse(request.body);
        if (!parseResult.success) {
            return reply.code(400).send({ message: 'Invalid input', errors: parseResult.error.errors });
        }
        const { videoIds, accessLevel } = parseResult.data;
        try {
            const result = await database_1.prisma.video.updateMany({
                where: { id: { in: videoIds } },
                data: { accessLevel },
            });
            logger_1.logger.info(`Bulk updated access level to "${accessLevel}" for ${result.count} videos`, { videoIds });
            return {
                success: true,
                updatedCount: result.count,
            };
        }
        catch (error) {
            logger_1.logger.error('Failed to bulk update access level', { error, videoIds });
            return reply.code(500).send({ message: 'Failed to update access levels' });
        }
    });
}
//# sourceMappingURL=video-actions.routes.js.map