import { Application } from 'express';
import { Resource } from 'express-automatic-routes';
import { FileProvider } from '#providers/FileProvider';
import { Req, Res } from '#interfaces/IApi';
import verify from '#middlewares/auth';
import { uploadMultiple } from '#services/file-system/uploadService';
import { sanitizeText } from '#utils/stringUtils';
import { getStorageAdapter } from '#services/storage/adapters/StorageFactory';
import { processAndUpload } from '#services/file-system/fileUploadPipelineService';
import { uploadRateLimit } from '#middlewares/rate-limiter';
import { validateZod } from '#middlewares/validators';
import { FileUploadMultipleBodyValidationSchema } from '#contracts/file/schema';
import { Errors, ValidationError } from '#interfaces/error';
import { FileVariant } from '#models/FileVariant';
import { sendSuccess } from '#utils/responseUtils';
import Config from '#config';
import { getErrorMessages } from '#constants/errors';

export default (_express: Application) => {
	const fileProvider = FileProvider.getInstance();

	return <Resource>{
		post: {
			middleware: [verify, uploadRateLimit, uploadMultiple, validateZod(FileUploadMultipleBodyValidationSchema)],
			handler: async (req: Req, res: Res) => {
				const files = req.files as Express.Multer.File[];
				if (!files || files.length === 0) {
					return res.error(
						Errors.validation([
							{
								field: 'files',
								code: 'MISSING_FILE_FIELDS',
								message: { vi: 'Không có file được upload', en: 'No files were uploaded' },
							},
						]),
					);
				}

				const storageAdapter = getStorageAdapter();
				const title = sanitizeText(req.body.title, 150);
				const description = sanitizeText(req.body.description, 500);
				const note = sanitizeText(req.body.note, 255);
				const isInLibrary = req.body.is_library;

				// PERF-04: process all files in parallel — pipeline is I/O-bound (ClamAV, MinIO)
				const results = await Promise.all(
					files.map(async (file) => {
						let uploaded: Awaited<ReturnType<typeof processAndUpload>> | undefined;
						let storagePathToDelete: string | undefined;
						const decodedName = Buffer.from(file.originalname, 'latin1').toString('utf8');
						try {
							uploaded = await processAndUpload(file, storageAdapter);
							// Track storage path for cleanup if DB insert fails
							storagePathToDelete = uploaded.storagePath;

							const rawProvider = uploaded.storageProvider;
							const bucketName =
								rawProvider !== 'local' ? Config.storage.minio.bucket || Config.storage.minio.s3Bucket || '' : '';
							const category = (uploaded.fileType || '').toLowerCase() || null;

							const saved = await fileProvider.create({
								id: crypto.randomUUID(),
								visibility: 'private',
								created_at: new Date(),
								updated_at: new Date(),
								original_name: uploaded.decodedName,
								provider: rawProvider,
								bucket_name: bucketName,
								object_key: uploaded.storagePath,
								mime_type: file.mimetype,
								category,
								size_bytes: file.size,
								title,
								description,
								note,
								checksum_sha_256: uploaded.checksum,
								is_library: isInLibrary,
								tenant_id: req.user?.tenantId || null,
								owner_user_id: req.user?.id || null,
								status: 'uploaded',
								created_by: req.user?.id || null,
							});

							// Clear storage path after successful DB insert - no cleanup needed
							storagePathToDelete = undefined;

							// SEC-04: Only create variants if compression actually succeeded (compressInfo has valid paths)
							if (uploaded.compressInfo) {
								const validVariants = Object.entries(uploaded.compressInfo).filter(
									([, object_key]) => object_key !== null && object_key !== undefined,
								);
								if (validVariants.length > 0) {
									await FileVariant.bulkCreate(
										validVariants.map(([variant_name, object_key]) => ({
											id: crypto.randomUUID(),
											file_id: saved.id,
											variant_name,
											provider: rawProvider,
											bucket_name: bucketName,
											object_key,
											mime_type: 'image/webp',
											created_at: new Date(),
										})),
										{ ignoreDuplicates: true },
									);
								}
							}

							return { success: true, file: saved, name: uploaded.decodedName };
						} catch (err) {
							// Cleanup orphaned MinIO object if DB insert failed after upload
							if (storagePathToDelete) {
								await storageAdapter.delete(storagePathToDelete).catch((error) => {
									console.error('[UploadMultiple] Failed to cleanup orphaned storage object:', error);
								});
							}
							console.error('[UploadMultiple] File upload failed:', decodedName, err);
							console.error('[UploadMultiple] Error details:', JSON.stringify(err, null, 2));

							// Extract detailed error message
							let errorMessage = (err as Error).message;

							// Handle ValidationError
							if (err instanceof ValidationError && err.errors.length > 0) {
								const firstError = err.errors[0];
								if (firstError) {
									errorMessage = firstError.code || 'VALIDATION_ERROR';
								}
							}
							// Handle Sequelize unique constraint error (duplicate file)
							else if ((err as any).name === 'SequelizeUniqueConstraintError') {
								const sequelizeErr = err as any;
								if (sequelizeErr.parent?.constraint === 'uq_files_content_dedupe') {
									errorMessage = 'FILE_EXISTS';
								} else {
									errorMessage = 'CONFLICT';
								}
							}

							return { success: false, name: decodedName, error: errorMessage };
						}
					}),
				);

				const succeeded = results.filter((r) => r.success && r.file).map((r) => r.file!.toJSON());
				const failed = results.filter((r) => !r.success);

				// Hybrid response based on success/failure pattern
				if (failed.length === 0) {
					// All files succeeded → 200 with successful array
					return sendSuccess(res, succeeded);
				} else if (succeeded.length === 0) {
					// All files failed → 422 with standard error format
					return res.error(
						Errors.validation(
							failed.map((f) => ({
								field: 'files',
								code: f.error || 'FILE_UPLOAD_ERROR',
								message: getErrorMessages(f.error || 'FILE_UPLOAD_ERROR'),
							})),
						),
					);
				} else {
					// Partial success → 207 Multi-Status with both arrays
					return res.status(207).json({
						success: true,
						trace_id: null,
						timestamp: new Date().toISOString(),
						errors: null,
						data: {
							successful: succeeded,
							failed: failed.map((f) => ({
								name: f.name,
								error: f.error,
							})),
						},
					});
				}
			},
		},
	};
};
