"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.uploadMediaDataset = exports.uploadMediaDatasetMiddleware = void 0;
const drizzle_orm_1 = require("drizzle-orm");
const extract_zip_1 = __importDefault(require("extract-zip"));
const fs = __importStar(require("fs"));
const multer_1 = __importDefault(require("multer"));
const path = __importStar(require("path"));
const uuid_1 = require("uuid");
const constants_1 = require("../../../constants");
const database_1 = require("../../../database");
const tables_1 = require("../../../database/tables");
const logger_1 = __importDefault(require("../../../logger"));
const uploadFilesToAzure_1 = require("../../../util/uploadFilesToAzure");
// Configure multer for file uploads
const storage = multer_1.default.diskStorage({
    destination: (req, file, cb) => {
        const tempDir = path.resolve('temp_media');
        if (!fs.existsSync(tempDir)) {
            fs.mkdirSync(tempDir, { recursive: true });
        }
        cb(null, tempDir);
    },
    filename: (req, file, cb) => {
        cb(null, `${(0, uuid_1.v4)()}-${file.originalname}`);
    },
});
const upload = (0, multer_1.default)({
    storage,
    fileFilter: (req, file, cb) => {
        if (file.mimetype === 'application/zip' || file.originalname.endsWith('.zip')) {
            cb(null, true);
        }
        else {
            cb(new Error('Only ZIP files are allowed'));
        }
    },
    limits: {
        fileSize: 50 * 1024 * 1024, // 50MB limit
    },
});
// Multer middleware for handling file uploads
exports.uploadMediaDatasetMiddleware = upload.single('file');
// Helper function for cleanup
const cleanupTempFiles = (extractPath, zipFilePath) => {
    try {
        // Clean up extracted files
        if (extractPath && fs.existsSync(extractPath)) {
            const cleanup = (dir) => {
                if (fs.existsSync(dir)) {
                    const files = fs.readdirSync(dir);
                    for (const file of files) {
                        const filePath = path.join(dir, file);
                        const stat = fs.statSync(filePath);
                        if (stat.isDirectory()) {
                            cleanup(filePath);
                        }
                        else {
                            fs.unlinkSync(filePath);
                        }
                    }
                    fs.rmdirSync(dir);
                }
            };
            cleanup(extractPath);
        }
        // Clean up zip file
        if (zipFilePath && fs.existsSync(zipFilePath)) {
            fs.unlinkSync(zipFilePath);
        }
    }
    catch (cleanupError) {
        logger_1.default.error(`Error during cleanup: ${cleanupError}`);
    }
};
// Helper function to get file extension
const getFileExtension = (filename) => {
    return path.extname(filename).toLowerCase();
};
// Helper function to get content type based on file extension
const getContentType = (extension) => {
    const contentTypes = {
        '.mp4': 'video/mp4',
        '.mpg': 'video/mpeg',
        '.mpeg': 'video/mpeg',
        '.webm': 'video/webm',
        '.avi': 'video/x-msvideo',
        '.jpg': 'image/jpeg',
        '.jpeg': 'image/jpeg',
        '.png': 'image/png',
        '.webp': 'image/webp',
        '.txt': 'text/plain',
        '.json': 'application/json',
        '.log': 'text/plain',
    };
    return contentTypes[extension] || 'application/octet-stream';
};
// Helper function to validate OpenAI conversation format
const isValidOpenAIConversation = (content) => {
    try {
        // Trim whitespace and check if content is empty
        const trimmedContent = content.trim();
        if (!trimmedContent) {
            return false;
        }
        const parsed = JSON.parse(trimmedContent);
        // Only support direct array format: [{role: 'user', content: '...'}, ...]
        if (!Array.isArray(parsed)) {
            return false;
        }
        const messages = parsed;
        // Check if messages array is empty
        if (messages.length === 0) {
            return false;
        }
        // Validate each message in the conversation
        for (let i = 0; i < messages.length; i++) {
            const message = messages[i];
            // Each message must be an object
            if (typeof message !== 'object' || message === null) {
                return false;
            }
            // Must have exactly 2 fields: role and content
            const keys = Object.keys(message);
            if (keys.length !== 2 || !keys.includes('role') || !keys.includes('content')) {
                return false;
            }
            // Both role and content must be strings
            if (typeof message.role !== 'string' || typeof message.content !== 'string') {
                return false;
            }
            // Role must be one of the valid OpenAI roles
            const validRoles = ['system', 'user', 'assistant'];
            if (!validRoles.includes(message.role)) {
                return false;
            }
            // Content must not be empty
            if (message.content.trim() === '') {
                return false;
            }
        }
        return true;
    }
    catch (error) {
        logger_1.default.error(`Error validating OpenAI conversation: ${error}`);
        return false;
    }
};
// Helper function to find files by type
const findFilesByType = (dir, extensions, isConversation = false) => {
    const files = [];
    const findFiles = (currentDir) => {
        const items = fs.readdirSync(currentDir);
        for (const item of items) {
            const itemPath = path.join(currentDir, item);
            const stat = fs.statSync(itemPath);
            if (stat.isDirectory()) {
                findFiles(itemPath);
            }
            else {
                const ext = getFileExtension(item);
                if (extensions.includes(ext)) {
                    // For conversation files, validate the format
                    if (isConversation) {
                        try {
                            const content = fs.readFileSync(itemPath, 'utf-8');
                            if (isValidOpenAIConversation(content)) {
                                files.push(itemPath);
                                logger_1.default.info(`Valid conversation file: ${item}`);
                            }
                            else {
                                logger_1.default.warn(`Invalid conversation format: ${item}`);
                            }
                        }
                        catch (error) {
                            logger_1.default.warn(`Error reading conversation file ${item}: ${error}`);
                        }
                    }
                    else {
                        // For non-conversation files, just add them
                        files.push(itemPath);
                    }
                }
            }
        }
    };
    findFiles(dir);
    return files;
};
// Main handler function
const uploadMediaDataset = async (req, res) => {
    let extractPath;
    let zipFilePath;
    try {
        const token = req.headers['x-access-token'] || req.headers.authorization;
        const { name, projectId, orgId, status, variable_name, datasetId, dataType } = req.body;
        const zipFile = req.file;
        if (!zipFile) {
            return res.status(400).json({ error: 'No zip file uploaded' });
        }
        zipFilePath = zipFile.path;
        logger_1.default.info(`Processing media dataset ${datasetId ? 'update' : 'upload'}: ${zipFile.originalname}`);
        if (!name || !projectId || !orgId || !dataType) {
            // Clean up zip file on validation error
            cleanupTempFiles(undefined, zipFilePath);
            return res.status(400).json({ error: 'Missing required fields' });
        }
        const db = await (0, database_1.getDb)();
        const tempDir = path.resolve('temp_media');
        // Create temp directory if it doesn't exist
        if (!fs.existsSync(tempDir)) {
            fs.mkdirSync(tempDir, { recursive: true });
        }
        try {
            // Extract zip file using extract-zip
            extractPath = path.resolve(tempDir, (0, uuid_1.v4)());
            fs.mkdirSync(extractPath, { recursive: true });
            await (0, extract_zip_1.default)(zipFile.path, { dir: extractPath });
            logger_1.default.info(`Extracted zip file to: ${extractPath}`);
            // Define file extensions based on data type
            let allowedExtensions = [];
            let mediaType = '';
            switch (Number.parseInt(dataType)) {
                case 4: // VIDEO
                    allowedExtensions = ['.mp4', '.mpg', '.mpeg', '.webm', '.avi'];
                    mediaType = 'video';
                    break;
                case 5: // IMAGE
                    allowedExtensions = ['.jpg', '.jpeg', '.png', '.webp'];
                    mediaType = 'image';
                    break;
                case 6: // CONVERSATION
                    allowedExtensions = ['.txt', '.json', '.log'];
                    mediaType = 'conversation';
                    break;
                default:
                    cleanupTempFiles(extractPath, zipFilePath);
                    return res.status(400).json({ error: 'Invalid data type' });
            }
            // Find all files of the specified type recursively
            const isConversation = Number.parseInt(dataType) === 6; // CONVERSATION type
            const mediaFiles = findFilesByType(extractPath, allowedExtensions, isConversation);
            logger_1.default.info(`Found ${mediaFiles.length} valid ${mediaType} files in zip`);
            if (mediaFiles.length === 0) {
                // Clean up temp files when no valid files found
                cleanupTempFiles(extractPath, zipFilePath);
                const errorMessage = isConversation
                    ? `No valid conversation files found in zip. Expected extensions: ${allowedExtensions.join(', ')} (files must follow OpenAI conversation format)`
                    : `No ${mediaType} files found in zip. Expected extensions: ${allowedExtensions.join(', ')}`;
                return res.status(400).json({
                    error: errorMessage,
                });
            }
            // Prepare files for upload
            const filesToUpload = mediaFiles.map((filePath, index) => {
                const extension = getFileExtension(filePath);
                const contentType = getContentType(extension);
                return {
                    filePath,
                    fileName: `${index + 1}-${path.basename(filePath)}`,
                    contentType,
                    addUuid: false,
                    cleanupAfterUpload: true,
                    privateContainer: true,
                    generateSasToken: true,
                };
            });
            // Upload files in batches
            const batchSize = 10;
            const batches = [];
            for (let i = 0; i < filesToUpload.length; i += batchSize) {
                const batch = filesToUpload.slice(i, i + batchSize);
                batches.push((0, uploadFilesToAzure_1.uploadFilesToAzure)(batch, token, `workspace/${orgId}/project/${projectId}/dataset/${datasetId || 'new'}`));
            }
            // Wait for all batches to complete
            const batchResults = await Promise.all(batches);
            const urlMap = batchResults.reduce((acc, curr) => ({ ...acc, ...curr }), {});
            // Create dataset data with media URLs
            const datasetData = mediaFiles.map((filePath, index) => {
                const fileName = path.basename(filePath);
                const extension = getFileExtension(filePath);
                const mediaUrl = urlMap[`${index + 1}-${fileName}`] || null;
                return {
                    [variable_name]: {
                        type: mediaType,
                        value: path.basename(filePath, extension),
                        [`${mediaType}Url`]: mediaUrl,
                        extension: extension.substring(1), // Remove the dot
                        originalName: fileName,
                    },
                };
            });
            // Create or update dataset in database
            let resultDataset;
            if (datasetId) {
                // Update existing dataset
                const [updatedDataset] = await db
                    .update(tables_1.trusysDatasetsTable)
                    .set({
                    name,
                    projectId,
                    status: Number.parseInt(status),
                    type: constants_1.DATASET_TYPE.UPLOAD,
                    fields: [variable_name],
                    config: {
                        variable_name,
                        dataType: Number.parseInt(dataType),
                        mediaType,
                    },
                    updatedAt: new Date(),
                })
                    .where((0, drizzle_orm_1.eq)(tables_1.trusysDatasetsTable.id, datasetId))
                    .returning();
                if (!updatedDataset) {
                    // Clean up temp files when dataset not found
                    cleanupTempFiles(extractPath, zipFilePath);
                    res.status(404).json({ error: 'Dataset not found' });
                    return;
                }
                // Delete existing records and insert new ones
                await db.delete(tables_1.datasetRecordsTable).where((0, drizzle_orm_1.eq)(tables_1.datasetRecordsTable.datasetId, datasetId));
                if (datasetData.length > 0) {
                    const recordInserts = datasetData.map((record, index) => ({
                        id: (0, uuid_1.v4)(),
                        datasetId,
                        recordData: record,
                        recordIndex: index,
                    }));
                    await db.insert(tables_1.datasetRecordsTable).values(recordInserts);
                }
                resultDataset = updatedDataset;
            }
            else {
                // Create new dataset
                const newDatasetId = (0, uuid_1.v4)();
                const [createdDataset] = await db
                    .insert(tables_1.trusysDatasetsTable)
                    .values({
                    id: newDatasetId,
                    name,
                    projectId,
                    status: Number.parseInt(status),
                    type: constants_1.DATASET_TYPE.UPLOAD,
                    fields: [variable_name],
                    config: {
                        variable_name,
                        dataType: Number.parseInt(dataType),
                        mediaType,
                    },
                    createdAt: new Date(),
                    updatedAt: new Date(),
                })
                    .returning();
                // Insert records into datasetRecordsTable
                if (datasetData.length > 0) {
                    const recordInserts = datasetData.map((record, index) => ({
                        id: (0, uuid_1.v4)(),
                        datasetId: newDatasetId,
                        recordData: record,
                        recordIndex: index,
                    }));
                    await db.insert(tables_1.datasetRecordsTable).values(recordInserts);
                }
                resultDataset = createdDataset;
            }
            // Clean up temp files
            cleanupTempFiles(extractPath, zipFilePath);
            res.json({ data: resultDataset });
        }
        catch (error) {
            logger_1.default.error(`Error processing media dataset: ${error}`);
            // Clean up temp files on error
            cleanupTempFiles(extractPath, zipFilePath);
            res.status(500).json({ error: 'Failed to process media dataset' });
        }
    }
    catch (error) {
        logger_1.default.error(`Error uploading media dataset: ${error}`);
        // Clean up temp files on error
        cleanupTempFiles(extractPath, zipFilePath);
        res.status(500).json({ error: 'Failed to upload media dataset' });
    }
};
exports.uploadMediaDataset = uploadMediaDataset;
//# sourceMappingURL=upload-media-dataset.js.map