"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.uploadRoutes = uploadRoutes;
const promises_1 = require("stream/promises");
const fs_1 = require("fs");
const promises_2 = require("fs/promises");
const path_1 = require("path");
const crypto_1 = require("crypto");
const database_1 = require("../../../config/database");
const ffprobe_service_1 = require("../services/ffprobe.service");
const thumbnail_service_1 = require("../services/thumbnail.service");
const logger_1 = require("../../../utils/logger");
const zod_1 = require("zod");
const auth_1 = require("../middleware/auth");
// Allowed video extensions
const ALLOWED_EXTENSIONS = ['.mp4', '.mov', '.avi', '.mkv', '.webm', '.m4v', '.flv'];
// Zod schema for upload metadata
const UploadMetadataSchema = zod_1.z.object({
    title: zod_1.z.string().optional(),
    producer: zod_1.z.string().optional(),
    creator: zod_1.z.string().optional(),
});
/**
 * Upload a single video file
 */
async function uploadVideo(request, reply) {
    let tempFilePath = null;
    try {
        // Get the uploaded file
        const data = await request.file();
        if (!data) {
            return reply.code(400).send({ message: 'No file uploaded' });
        }
        // Validate file extension
        const ext = (0, path_1.extname)(data.filename).toLowerCase();
        if (!ALLOWED_EXTENSIONS.includes(ext)) {
            return reply.code(400).send({
                message: `Invalid file type. Allowed: ${ALLOWED_EXTENSIONS.join(', ')}`,
            });
        }
        // Generate unique filename
        const filename = `${(0, crypto_1.randomUUID)()}${ext}`;
        const inboxDir = '/media/local/inbox';
        // Ensure inbox directory exists
        await (0, promises_2.mkdir)(inboxDir, { recursive: true });
        const filePath = (0, path_1.join)(inboxDir, filename);
        tempFilePath = filePath;
        // Stream file to disk (must consume file stream BEFORE reading metadata fields,
        // because Fastify multipart/busboy only makes fields available after the file stream ends)
        logger_1.logger.info(`Uploading video to ${filePath}`);
        await (0, promises_1.pipeline)(data.file, (0, fs_1.createWriteStream)(filePath));
        // Extract metadata fields from form data (now available after file stream consumed)
        const metadataFields = data.fields;
        const metadata = {
            title: metadataFields.title?.value,
            producer: metadataFields.producer?.value,
            creator: metadataFields.creator?.value,
        };
        // Validate metadata
        const validatedMetadata = UploadMetadataSchema.parse(metadata);
        // Validate video file
        logger_1.logger.info(`Validating video file: ${filePath}`);
        const isValid = await (0, ffprobe_service_1.validateVideoFile)(filePath);
        if (!isValid) {
            await (0, promises_2.unlink)(filePath);
            return reply.code(400).send({ message: 'Invalid or corrupted video file' });
        }
        // Extract metadata
        logger_1.logger.info(`Extracting metadata from: ${filePath}`);
        const videoMetadata = await (0, ffprobe_service_1.extractVideoMetadata)(filePath);
        // Insert into database
        const video = await database_1.prisma.video.create({
            data: {
                path: filePath, // Store full path: /media/local/inbox/uuid.mp4
                filename,
                originalFilename: data.filename,
                title: validatedMetadata.title || data.filename,
                durationSeconds: videoMetadata.durationSeconds,
                width: videoMetadata.width,
                height: videoMetadata.height,
                orientation: videoMetadata.orientation,
                quality: videoMetadata.quality,
                hasAudio: videoMetadata.hasAudio,
                fileSize: videoMetadata.fileSize,
                directoryType: 'inbox',
                isValid: true,
                isShort: videoMetadata.durationSeconds != null && videoMetadata.durationSeconds <= 60,
                producer: validatedMetadata.producer || null,
                creator: validatedMetadata.creator || null,
            },
        });
        logger_1.logger.info(`Video uploaded successfully: ${video.id}`);
        // Generate thumbnail
        try {
            const thumbnailPath = await thumbnail_service_1.ThumbnailService.generateThumbnail({
                videoPath: filePath,
                videoId: video.id,
                duration: videoMetadata.durationSeconds,
                orientation: videoMetadata.orientation,
            });
            // Update video with thumbnail path
            await database_1.prisma.video.update({
                where: { id: video.id },
                data: { thumbnailPath },
            });
            logger_1.logger.info(`Thumbnail generated for video ${video.id}`);
        }
        catch (thumbnailError) {
            // Log error but don't fail the upload
            logger_1.logger.error(`Failed to generate thumbnail for video ${video.id}:`, thumbnailError);
        }
        return reply.code(201).send({
            message: 'Video uploaded successfully',
            video,
        });
    }
    catch (error) {
        // Clean up file on error
        if (tempFilePath) {
            try {
                await (0, promises_2.unlink)(tempFilePath);
            }
            catch (cleanupError) {
                logger_1.logger.error('Failed to clean up file after error:', cleanupError);
            }
        }
        logger_1.logger.error('Video upload failed:', error);
        if (error instanceof zod_1.z.ZodError) {
            return reply.code(400).send({
                message: 'Invalid metadata',
                errors: error.errors,
            });
        }
        return reply.code(500).send({
            message: error instanceof Error ? error.message : 'Upload failed',
        });
    }
}
/**
 * Upload multiple video files in batch
 */
async function uploadBatch(request, reply) {
    try {
        const files = request.files();
        const results = [];
        for await (const file of files) {
            let tempFilePath = null;
            try {
                // Validate file extension
                const ext = (0, path_1.extname)(file.filename).toLowerCase();
                if (!ALLOWED_EXTENSIONS.includes(ext)) {
                    results.push({
                        filename: file.filename,
                        success: false,
                        error: `Invalid file type. Allowed: ${ALLOWED_EXTENSIONS.join(', ')}`,
                    });
                    continue;
                }
                // Generate unique filename
                const filename = `${(0, crypto_1.randomUUID)()}${ext}`;
                const inboxDir = '/media/local/inbox';
                // Ensure inbox directory exists
                await (0, promises_2.mkdir)(inboxDir, { recursive: true });
                const filePath = (0, path_1.join)(inboxDir, filename);
                tempFilePath = filePath;
                // Stream file to disk
                await (0, promises_1.pipeline)(file.file, (0, fs_1.createWriteStream)(filePath));
                // Validate video file
                const isValid = await (0, ffprobe_service_1.validateVideoFile)(filePath);
                if (!isValid) {
                    await (0, promises_2.unlink)(filePath);
                    results.push({
                        filename: file.filename,
                        success: false,
                        error: 'Invalid or corrupted video file',
                    });
                    continue;
                }
                // Extract metadata
                const videoMetadata = await (0, ffprobe_service_1.extractVideoMetadata)(filePath);
                // Insert into database
                const video = await database_1.prisma.video.create({
                    data: {
                        path: filePath, // Store full path: /media/local/inbox/uuid.mp4
                        filename,
                        originalFilename: file.filename,
                        title: file.filename,
                        durationSeconds: videoMetadata.durationSeconds,
                        width: videoMetadata.width,
                        height: videoMetadata.height,
                        orientation: videoMetadata.orientation,
                        quality: videoMetadata.quality,
                        hasAudio: videoMetadata.hasAudio,
                        fileSize: videoMetadata.fileSize,
                        directoryType: 'inbox',
                        isValid: true,
                        isShort: videoMetadata.durationSeconds != null && videoMetadata.durationSeconds <= 60,
                    },
                });
                // Generate thumbnail
                try {
                    const thumbnailPath = await thumbnail_service_1.ThumbnailService.generateThumbnail({
                        videoPath: filePath,
                        videoId: video.id,
                        duration: videoMetadata.durationSeconds,
                        orientation: videoMetadata.orientation,
                    });
                    // Update video with thumbnail path
                    await database_1.prisma.video.update({
                        where: { id: video.id },
                        data: { thumbnailPath },
                    });
                    logger_1.logger.info(`Thumbnail generated for video ${video.id}`);
                }
                catch (thumbnailError) {
                    // Log error but don't fail the upload
                    logger_1.logger.error(`Failed to generate thumbnail for video ${video.id}:`, thumbnailError);
                }
                results.push({
                    filename: file.filename,
                    success: true,
                    video,
                });
                logger_1.logger.info(`Batch upload successful: ${file.filename} -> ${video.id}`);
            }
            catch (error) {
                // Clean up file on error
                if (tempFilePath) {
                    try {
                        await (0, promises_2.unlink)(tempFilePath);
                    }
                    catch (cleanupError) {
                        logger_1.logger.error('Failed to clean up file after error:', cleanupError);
                    }
                }
                logger_1.logger.error(`Batch upload failed for ${file.filename}:`, error);
                results.push({
                    filename: file.filename,
                    success: false,
                    error: error instanceof Error ? error.message : 'Upload failed',
                });
            }
        }
        const successCount = results.filter((r) => r.success).length;
        const failCount = results.length - successCount;
        return reply.code(207).send({
            message: `Batch upload complete: ${successCount} succeeded, ${failCount} failed`,
            results,
        });
    }
    catch (error) {
        logger_1.logger.error('Batch upload failed:', error);
        return reply.code(500).send({
            message: error instanceof Error ? error.message : 'Batch upload failed',
        });
    }
}
async function uploadRoutes(fastify) {
    // Single file upload
    fastify.post('/upload', {
        preHandler: auth_1.requireAdminRole,
    }, uploadVideo);
    // Batch upload
    fastify.post('/upload/batch', {
        preHandler: auth_1.requireAdminRole,
    }, uploadBatch);
}
//# sourceMappingURL=upload.routes.js.map