"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.publicMediaRoutes = publicMediaRoutes;
const database_1 = require("../../../config/database");
const auth_1 = require("../middleware/auth");
const logger_1 = require("../../../utils/logger");
const session_service_1 = require("../services/session.service");
const public_media_schemas_1 = require("../schemas/public-media.schemas");
const fs_1 = require("fs");
const promises_1 = require("fs/promises");
/**
 * Public Media Gallery API Routes
 * Handles public video listing, upvotes, comments, and admin operations
 */
async function publicMediaRoutes(fastify) {
    /**
     * GET /videos (LEGACY ROUTE)
     * Compatibility endpoint for public-media app (port 3100)
     * Converts page-based pagination to offset-based and transforms response format
     */
    fastify.get('/videos', {
        preHandler: auth_1.optionalAuth,
    }, async (request, reply) => {
        try {
            // Convert page-based to offset-based pagination
            const page = parseInt(request.query.page || '1');
            const limit = parseInt(request.query.limit || '48');
            const offset = (page - 1) * limit;
            const { search, category, sort } = request.query;
            // Check if user is admin
            const ADMIN_ROLES = ['SUPER_ADMIN', 'INFLUENCE_ADMIN', 'MAP_ADMIN'];
            const isAdmin = request.user && ADMIN_ROLES.includes(request.user.role);
            // Build WHERE clause (same logic as /public endpoint)
            const where = {
                isPublished: true, // Only show published videos
            };
            if (!isAdmin) {
                where.isLocked = false;
            }
            if (category) {
                where.category = category;
            }
            if (search) {
                where.filename = {
                    contains: search,
                    mode: 'insensitive',
                };
            }
            // Determine sort order
            let orderBy = {};
            switch (sort) {
                case 'recent':
                    orderBy = { publishedAt: 'desc' };
                    break;
                case 'popular':
                    orderBy = { upvoteCount: 'desc' };
                    break;
                case 'most_viewed':
                    orderBy = { viewCount: 'desc' };
                    break;
                default:
                    orderBy = { publishedAt: 'desc' };
            }
            // Execute queries in parallel
            const [videos, total] = await Promise.all([
                database_1.prisma.video.findMany({
                    where,
                    select: {
                        id: true,
                        filename: true,
                        category: true,
                        durationSeconds: true,
                        quality: true,
                        orientation: true,
                        thumbnailPath: true,
                        fileSize: true,
                        viewCount: true,
                        upvoteCount: true,
                        commentCount: true,
                        createdAt: true,
                    },
                    orderBy,
                    take: limit,
                    skip: offset,
                }),
                database_1.prisma.video.count({ where }),
            ]);
            // Calculate total pages
            const totalPages = Math.ceil(total / limit);
            // Transform to legacy format expected by public-media app
            return {
                data: videos.map((video) => ({
                    id: video.id.toString(), // int → string
                    title: video.filename.replace(/\.[^.]+$/, ''), // filename without extension
                    fileName: video.filename, // camelCase
                    fileSize: Number(video.fileSize || 0), // BigInt → number
                    duration: video.durationSeconds || 0, // rename field
                    width: 0, // not stored, placeholder
                    height: 0, // not stored, placeholder
                    orientation: video.orientation || 'horizontal',
                    category: video.category,
                    viewCount: video.viewCount || 0,
                    createdAt: video.createdAt.toISOString(),
                    updatedAt: video.createdAt.toISOString(), // no updatedAt field, use createdAt
                    thumbnailUrl: video.thumbnailPath ? `/api/media/public/${video.id}/thumbnail` : undefined,
                    streamUrl: `/media/public/${video.category}/${video.filename}`, // static file path
                })),
                total,
                page,
                limit,
                totalPages,
            };
        }
        catch (error) {
            logger_1.logger.error('Error fetching videos (legacy endpoint):', error);
            return reply.code(500).send({
                message: 'Failed to fetch videos',
                error: error.message,
            });
        }
    });
    /**
     * GET /public
     * List public videos with filtering, sorting, and pagination
     */
    fastify.get('/public', {
        preHandler: auth_1.optionalAuth,
    }, async (request, reply) => {
        try {
            // Validate query params
            const parseResult = public_media_schemas_1.listPublicMediaSchema.safeParse(request.query);
            if (!parseResult.success) {
                return reply.code(400).send({
                    message: 'Invalid query parameters',
                    errors: parseResult.error.errors,
                });
            }
            const { limit, offset, sort, search, category } = parseResult.data;
            // Check if user is admin
            const ADMIN_ROLES = ['SUPER_ADMIN', 'INFLUENCE_ADMIN', 'MAP_ADMIN'];
            const isAdmin = request.user && ADMIN_ROLES.includes(request.user.role);
            // Build WHERE clause
            const where = {
                isPublished: true, // Only show published videos
            };
            // Non-admins can't see locked videos
            if (!isAdmin) {
                where.isLocked = false;
            }
            // Category filter
            if (category) {
                where.category = category;
            }
            // Search filter (searches filename)
            if (search) {
                where.filename = {
                    contains: search,
                    mode: 'insensitive',
                };
            }
            // Determine sort order
            let orderBy = {};
            switch (sort) {
                case 'recent':
                    orderBy = { publishedAt: 'desc' };
                    break;
                case 'popular':
                    orderBy = { upvoteCount: 'desc' };
                    break;
                case 'most_viewed':
                    orderBy = { viewCount: 'desc' };
                    break;
                default:
                    orderBy = { publishedAt: 'desc' };
            }
            // Execute queries in parallel
            const [videos, total] = await Promise.all([
                database_1.prisma.video.findMany({
                    where,
                    select: {
                        id: true,
                        filename: true,
                        category: true,
                        durationSeconds: true,
                        quality: true,
                        orientation: true,
                        thumbnailPath: true,
                        fileSize: true,
                        viewCount: true,
                        upvoteCount: true,
                        commentCount: true,
                        createdAt: true,
                        isLocked: true,
                        position: true,
                        publishedAt: true,
                    },
                    orderBy,
                    take: limit,
                    skip: offset,
                }),
                database_1.prisma.video.count({ where }),
            ]);
            return {
                videos,
                pagination: {
                    total,
                    limit,
                    offset,
                    hasMore: offset + limit < total,
                },
            };
        }
        catch (error) {
            logger_1.logger.error('Error listing public media:', error);
            return reply.code(500).send({
                message: 'Failed to list videos',
                error: error.message,
            });
        }
    });
    /**
     * GET /public/:id
     * Get single video details
     */
    fastify.get('/public/:id', {
        preHandler: auth_1.optionalAuth,
    }, async (request, reply) => {
        try {
            const videoId = parseInt(request.params.id);
            if (isNaN(videoId)) {
                return reply.code(400).send({ message: 'Invalid video ID' });
            }
            const video = await database_1.prisma.video.findFirst({
                where: {
                    id: videoId,
                    isPublished: true, // Only show published videos
                },
                select: {
                    id: true,
                    filename: true,
                    category: true,
                    durationSeconds: true,
                    quality: true,
                    orientation: true,
                    thumbnailPath: true,
                    fileSize: true,
                    viewCount: true,
                    upvoteCount: true,
                    commentCount: true,
                    finishCount: true,
                    totalWatchTime: true,
                    createdAt: true,
                    publishedAt: true,
                    isLocked: true,
                    position: true,
                    uploaderId: true,
                },
            });
            if (!video) {
                return reply.code(404).send({ message: 'Video not found' });
            }
            // Check if locked and user is not admin
            const ADMIN_ROLES = ['SUPER_ADMIN', 'INFLUENCE_ADMIN', 'MAP_ADMIN'];
            const isAdmin = request.user && ADMIN_ROLES.includes(request.user.role);
            if (video.isLocked && !isAdmin) {
                return reply.code(403).send({
                    message: 'This video is locked',
                });
            }
            return { video };
        }
        catch (error) {
            logger_1.logger.error('Error fetching public media:', error);
            return reply.code(500).send({
                message: 'Failed to fetch video',
                error: error.message,
            });
        }
    });
    /**
     * POST /public/:id/upvote
     * Toggle upvote for a video
     */
    fastify.post('/public/:id/upvote', async (request, reply) => {
        try {
            const videoId = parseInt(request.params.id);
            if (isNaN(videoId)) {
                return reply.code(400).send({ message: 'Invalid video ID' });
            }
            // Get or create session
            let sessionId;
            try {
                sessionId = await (0, session_service_1.getOrCreateSession)(request);
            }
            catch (error) {
                return reply.code(400).send({
                    message: 'Session required',
                    error: error.message,
                });
            }
            // Check if video exists and is published
            const video = await database_1.prisma.video.findFirst({
                where: {
                    id: videoId,
                    isPublished: true,
                },
                select: { id: true },
            });
            if (!video) {
                return reply.code(404).send({ message: 'Video not found' });
            }
            // Check if upvote already exists
            const existingUpvote = await database_1.prisma.upvote.findFirst({
                where: {
                    mediaId: videoId,
                    sessionId,
                },
            });
            if (existingUpvote) {
                // Remove upvote (toggle off)
                await database_1.prisma.$transaction([
                    database_1.prisma.upvote.delete({
                        where: { id: existingUpvote.id },
                    }),
                    database_1.prisma.video.update({
                        where: { id: videoId },
                        data: {
                            upvoteCount: {
                                decrement: 1,
                            },
                        },
                    }),
                ]);
                logger_1.logger.info(`Removed upvote for video ${videoId} from session ${sessionId}`);
                return { upvoted: false };
            }
            else {
                // Add upvote (toggle on)
                await database_1.prisma.$transaction([
                    database_1.prisma.upvote.create({
                        data: {
                            mediaId: videoId,
                            sessionId,
                        },
                    }),
                    database_1.prisma.video.update({
                        where: { id: videoId },
                        data: {
                            upvoteCount: {
                                increment: 1,
                            },
                        },
                    }),
                ]);
                logger_1.logger.info(`Added upvote for video ${videoId} from session ${sessionId}`);
                return { upvoted: true };
            }
        }
        catch (error) {
            logger_1.logger.error('Error toggling upvote:', error);
            return reply.code(500).send({
                message: 'Failed to toggle upvote',
                error: error.message,
            });
        }
    });
    /**
     * GET /public/:id/upvote-status
     * Check if current session has upvoted a video
     */
    fastify.get('/public/:id/upvote-status', async (request, reply) => {
        try {
            const videoId = parseInt(request.params.id);
            if (isNaN(videoId)) {
                return reply.code(400).send({ message: 'Invalid video ID' });
            }
            // Read sessionId from header (don't create if missing)
            const sessionId = request.headers['x-session-id'];
            if (!sessionId) {
                return { upvoted: false };
            }
            // Check if upvote exists
            const upvote = await database_1.prisma.upvote.findFirst({
                where: {
                    mediaId: videoId,
                    sessionId,
                },
            });
            return { upvoted: !!upvote };
        }
        catch (error) {
            logger_1.logger.error('Error checking upvote status:', error);
            return reply.code(500).send({
                message: 'Failed to check upvote status',
                error: error.message,
            });
        }
    });
    /**
     * GET /public/:id/comments
     * List comments for a video
     */
    fastify.get('/public/:id/comments', async (request, reply) => {
        try {
            const videoId = parseInt(request.params.id);
            const limit = parseInt(request.query.limit || '20');
            const offset = parseInt(request.query.offset || '0');
            if (isNaN(videoId)) {
                return reply.code(400).send({ message: 'Invalid video ID' });
            }
            // Check if video exists and is published
            const video = await database_1.prisma.video.findFirst({
                where: {
                    id: videoId,
                    isPublished: true,
                },
                select: { id: true },
            });
            if (!video) {
                return reply.code(404).send({ message: 'Video not found' });
            }
            // Fetch comments (hide hidden ones)
            const [comments, total] = await Promise.all([
                database_1.prisma.comment.findMany({
                    where: {
                        mediaId: videoId,
                        isHidden: false,
                    },
                    select: {
                        id: true,
                        content: true,
                        createdAt: true,
                        sessionId: true,
                        userId: true,
                        safetyStatus: true,
                        user: {
                            select: {
                                name: true,
                                email: true,
                            },
                        },
                    },
                    orderBy: {
                        createdAt: 'desc',
                    },
                    take: limit,
                    skip: offset,
                }),
                database_1.prisma.comment.count({
                    where: {
                        mediaId: videoId,
                        isHidden: false,
                    },
                }),
            ]);
            return {
                comments,
                pagination: {
                    total,
                    limit,
                    offset,
                    hasMore: offset + limit < total,
                },
            };
        }
        catch (error) {
            logger_1.logger.error('Error listing comments:', error);
            return reply.code(500).send({
                message: 'Failed to list comments',
                error: error.message,
            });
        }
    });
    /**
     * POST /public/:id/comments
     * Add a comment to a video
     */
    fastify.post('/public/:id/comments', async (request, reply) => {
        try {
            const videoId = parseInt(request.params.id);
            if (isNaN(videoId)) {
                return reply.code(400).send({ message: 'Invalid video ID' });
            }
            // Validate request body
            const parseResult = public_media_schemas_1.addCommentSchema.safeParse(request.body);
            if (!parseResult.success) {
                return reply.code(400).send({
                    message: 'Invalid comment data',
                    errors: parseResult.error.errors,
                });
            }
            const { content } = parseResult.data;
            // Get or create session
            let sessionId;
            try {
                sessionId = await (0, session_service_1.getOrCreateSession)(request);
            }
            catch (error) {
                return reply.code(400).send({
                    message: 'Session required',
                    error: error.message,
                });
            }
            // Check if video exists and is published
            const video = await database_1.prisma.video.findFirst({
                where: {
                    id: videoId,
                    isPublished: true,
                },
                select: { id: true },
            });
            if (!video) {
                return reply.code(404).send({ message: 'Video not found' });
            }
            // Create comment and increment counter in transaction
            const [comment] = await database_1.prisma.$transaction([
                database_1.prisma.comment.create({
                    data: {
                        mediaId: videoId,
                        sessionId,
                        userId: request.user?.id || null,
                        content,
                        safetyStatus: 'pending',
                    },
                    select: {
                        id: true,
                        content: true,
                        createdAt: true,
                        sessionId: true,
                        userId: true,
                        safetyStatus: true,
                    },
                }),
                database_1.prisma.video.update({
                    where: { id: videoId },
                    data: {
                        commentCount: {
                            increment: 1,
                        },
                    },
                }),
            ]);
            logger_1.logger.info(`Added comment ${comment.id} for video ${videoId} from session ${sessionId}`);
            return { comment };
        }
        catch (error) {
            logger_1.logger.error('Error adding comment:', error);
            return reply.code(500).send({
                message: 'Failed to add comment',
                error: error.message,
            });
        }
    });
    /**
     * GET /public/:id/thumbnail
     * Serve thumbnail image for a video
     */
    fastify.get('/public/:id/thumbnail', async (request, reply) => {
        try {
            const videoId = parseInt(request.params.id);
            if (isNaN(videoId)) {
                return reply.code(400).send({ message: 'Invalid video ID' });
            }
            // Fetch video with thumbnail path (published only)
            const video = await database_1.prisma.video.findFirst({
                where: {
                    id: videoId,
                    isPublished: true,
                },
                select: {
                    thumbnailPath: true,
                },
            });
            if (!video) {
                return reply.code(404).send({ message: 'Video not found' });
            }
            if (!video.thumbnailPath) {
                return reply.code(404).send({ message: 'Thumbnail not found' });
            }
            // Check if file exists
            try {
                await (0, promises_1.access)(video.thumbnailPath, promises_1.constants.R_OK);
            }
            catch {
                logger_1.logger.warn(`Thumbnail file not found: ${video.thumbnailPath}`);
                return reply.code(404).send({ message: 'Thumbnail file not found' });
            }
            // Stream the file
            const stream = (0, fs_1.createReadStream)(video.thumbnailPath);
            // Set content type based on file extension
            const ext = video.thumbnailPath.toLowerCase().split('.').pop();
            const contentType = ext === 'png' ? 'image/png' : ext === 'webp' ? 'image/webp' : 'image/jpeg';
            return reply.type(contentType).send(stream);
        }
        catch (error) {
            logger_1.logger.error('Error serving thumbnail:', error);
            return reply.code(500).send({
                message: 'Failed to serve thumbnail',
                error: error.message,
            });
        }
    });
    /**
     * POST /public/bulk-lock
     * Lock multiple videos (admin only)
     */
    fastify.post('/public/bulk-lock', {
        preHandler: auth_1.requireAdminRole,
    }, async (request, reply) => {
        try {
            // Validate request body
            const parseResult = public_media_schemas_1.bulkLockSchema.safeParse(request.body);
            if (!parseResult.success) {
                return reply.code(400).send({
                    message: 'Invalid request',
                    errors: parseResult.error.errors,
                });
            }
            const { ids } = parseResult.data;
            const userId = request.user?.id;
            // Update all videos at once (only published videos)
            const result = await database_1.prisma.video.updateMany({
                where: {
                    id: {
                        in: ids,
                    },
                    isPublished: true,
                },
                data: {
                    isLocked: true,
                    lockedAt: new Date(),
                    lockedById: userId,
                },
            });
            logger_1.logger.info(`Locked ${result.count} videos (IDs: ${ids.join(', ')}) by user ${userId}`);
            return {
                success: true,
                count: result.count,
            };
        }
        catch (error) {
            logger_1.logger.error('Error bulk locking videos:', error);
            return reply.code(500).send({
                message: 'Failed to lock videos',
                error: error.message,
            });
        }
    });
    /**
     * POST /public/bulk-unlock
     * Unlock multiple videos (admin only)
     */
    fastify.post('/public/bulk-unlock', {
        preHandler: auth_1.requireAdminRole,
    }, async (request, reply) => {
        try {
            // Validate request body
            const parseResult = public_media_schemas_1.bulkUnlockSchema.safeParse(request.body);
            if (!parseResult.success) {
                return reply.code(400).send({
                    message: 'Invalid request',
                    errors: parseResult.error.errors,
                });
            }
            const { ids } = parseResult.data;
            const userId = request.user?.id;
            // Update all videos at once (only published videos)
            const result = await database_1.prisma.video.updateMany({
                where: {
                    id: {
                        in: ids,
                    },
                    isPublished: true,
                },
                data: {
                    isLocked: false,
                    lockedAt: null,
                    lockedById: null,
                },
            });
            logger_1.logger.info(`Unlocked ${result.count} videos (IDs: ${ids.join(', ')}) by user ${userId}`);
            return {
                success: true,
                count: result.count,
            };
        }
        catch (error) {
            logger_1.logger.error('Error bulk unlocking videos:', error);
            return reply.code(500).send({
                message: 'Failed to unlock videos',
                error: error.message,
            });
        }
    });
    /**
     * DELETE /public/:id
     * Unpublish a video from public gallery (admin only)
     * NOTE: This unpublishes instead of deleting - interactions are preserved
     */
    fastify.delete('/public/:id', {
        preHandler: auth_1.requireAdminRole,
    }, async (request, reply) => {
        try {
            const videoId = parseInt(request.params.id);
            if (isNaN(videoId)) {
                return reply.code(400).send({ message: 'Invalid video ID' });
            }
            // Check if video exists and is published
            const video = await database_1.prisma.video.findFirst({
                where: {
                    id: videoId,
                    isPublished: true,
                },
                select: { id: true },
            });
            if (!video) {
                return reply.code(404).send({ message: 'Video not found' });
            }
            // Unpublish the video (preserves upvotes, comments, views)
            await database_1.prisma.video.update({
                where: { id: videoId },
                data: {
                    isPublished: false,
                    publishedAt: null,
                    // Keep category for re-publishing
                },
            });
            logger_1.logger.info(`Unpublished video ${videoId} by user ${request.user?.id}`);
            return { success: true };
        }
        catch (error) {
            logger_1.logger.error('Error unpublishing video:', error);
            return reply.code(500).send({
                message: 'Failed to unpublish video',
                error: error.message,
            });
        }
    });
}
//# sourceMappingURL=public-media.routes.js.map