import { randomUUID } from 'crypto';
import path from 'path';
import { Application } from 'express';
import { Resource } from 'express-automatic-routes';
import { Req, Res } from '#interfaces/IApi';
import verify from '#middlewares/auth';
import { getStorageAdapter } from '#services/storage/adapters/StorageFactory';
import { sendSuccess } from '#utils/responseUtils';
import { MinioStorageAdapter } from '#services/storage/adapters/MinioStorageAdapter';
import { Errors } from '#interfaces/error';
import RedisService from '#services/storage/redisService';
import { ALLOWED_MIME_TYPES } from '#services/file-system/uploadService';
import { isBlockedExtension, validateMimeExtensionMatch } from '#utils/fileSecurityUtils';
import { uploadRateLimit } from '#middlewares/rate-limiter';

const PRESIGN_TTL = 300; // 5 minutes
const MAX_BATCH_SIZE = 50; // Maximum files per batch request
/** CQ-01: exported so confirm.ts can import instead of duplicating */
export const presignKey = (token: string) => `presign_token:${token}`;

export interface PresignTokenData {
	file_key: string;
	user_id: string;
	file_name: string;
	file_size: number;
	mime_type: string;
	expires_at: number;
}

export interface PresignRequest {
	file_name: string;
	file_size: number;
	mime_type: string;
}

export interface PresignResponse {
	upload_url: string;
	file_key: string;
	upload_token: string;
	expires_in: number;
}

export default (_express: Application) => {
	const redis = RedisService.getInstance();
	return <Resource>{
		post: {
			middleware: [verify, uploadRateLimit],
			handler: async (req: Req, res: Res) => {
				try {
					const storageAdapter = getStorageAdapter();

					// Presigned direct upload only supported on MinIO
					if (!(storageAdapter instanceof MinioStorageAdapter)) {
						throw Errors.validation([
							{
								field: 'storage',
								code: 'VALIDATION_ERROR',
								message: {
									vi: 'Direct upload chỉ hỗ trợ với MinIO',
									en: 'Direct upload is only supported with MinIO storage',
								},
							},
						]);
					}

					// PERF-HIGH-02: Support both single file and batch requests
					const files = Array.isArray(req.body) ? (req.body as PresignRequest[]) : ([req.body] as PresignRequest[]);

					if (files.length === 0) {
						throw Errors.validation([
							{
								field: 'files',
								code: 'MISSING_FILE_FIELDS',
								message: { vi: 'Không có file nào được yêu cầu', en: 'No files requested' },
							},
						]);
					}

					if (files.length > MAX_BATCH_SIZE) {
						throw Errors.validation([
							{
								field: 'files',
								code: 'VALIDATION_ERROR',
								message: {
									vi: `Tối đa ${MAX_BATCH_SIZE} file mỗi lần yêu cầu`,
									en: `Maximum ${MAX_BATCH_SIZE} files per request`,
								},
							},
						]);
					}

					// Validate all files first
					const MAX_SIZE = 100 * 1024 * 1024; // 100 MB
					for (const file of files) {
						const { file_name, file_size, mime_type } = file;

						if (!file_name || !file_size || !mime_type) {
							throw Errors.validation([
								{
									field: 'file',
									code: 'MISSING_FILE_FIELDS',
									message: {
										vi: 'Thiếu thông tin file (file_name, file_size, mime_type)',
										en: 'Missing required fields: file_name, file_size, mime_type',
									},
								},
							]);
						}

						// SEC-01: validate MIME type against the same allowed list as multer fileFilter
						if (!ALLOWED_MIME_TYPES.includes(mime_type)) {
							throw Errors.validation([
								{
									field: 'mime_type',
									code: 'INVALID_MIME_TYPE',
									message: {
										vi: `Định dạng file không được hỗ trợ: ${mime_type}`,
										en: `MIME type not allowed: ${mime_type}`,
									},
								},
							]);
						}

						// SEC-01: block dangerous extensions
						if (isBlockedExtension(file_name)) {
							throw Errors.validation([
								{
									field: 'file_name',
									code: 'INVALID_FILE_TYPE',
									message: { vi: 'Loại file không được phép', en: 'File extension is not allowed' },
								},
							]);
						}

						// SEC-01: cross-validate extension vs declared MIME
						if (!validateMimeExtensionMatch(file_name, mime_type)) {
							throw Errors.validation([
								{
									field: 'file',
									code: 'FILE_EXTENSION_MISMATCH',
									message: {
										vi: 'Extension file không khớp với định dạng khai báo',
										en: 'File extension does not match declared MIME type',
									},
								},
							]);
						}

						if (typeof file_size !== 'number' || file_size <= 0) {
							throw Errors.validation([
								{
									field: 'file_size',
									code: 'INVALID_NUMBER_TYPE',
									message: { vi: 'file_size phải là số nguyên dương', en: 'file_size must be a positive integer' },
								},
							]);
						}

						if (file_size > MAX_SIZE) {
							throw Errors.validation([
								{
									field: 'file_size',
									code: 'FILE_TOO_LARGE',
									message: { vi: 'File vượt quá giới hạn 100 MB', en: 'File size exceeds maximum limit of 100 MB' },
								},
							]);
						}
					}

					// Generate presigned URLs for all files
					const results: PresignResponse[] = [];
					const tokenDataMap = new Map<string, PresignTokenData>();

					for (const file of files) {
						const { file_name, file_size, mime_type } = file;

						// Build a unique storage key: {type-folder}/{timestamp}-{uuid}.{ext}
						const ext = path.extname(file_name).toLowerCase() || '';
						const uniqueName = `${Date.now()}-${randomUUID()}${ext}`;
						const mimePrefix = mime_type.split('/')[0] ?? 'other';
						const folder = mimePrefix === 'image' ? 'images' : mimePrefix === 'video' ? 'videos' : 'documents';
						const file_key = `${folder}/${uniqueName}`;

						const upload_url = await (storageAdapter as MinioStorageAdapter).getPresignedPutUrl(file_key, PRESIGN_TTL);
						const upload_token = randomUUID();

						const tokenData: PresignTokenData = {
							file_key,
							user_id: req.user!.id,
							file_name,
							file_size,
							mime_type,
							expires_at: Date.now() + PRESIGN_TTL * 1000,
						};

						tokenDataMap.set(upload_token, tokenData);
						results.push({ upload_url, file_key, upload_token, expires_in: PRESIGN_TTL });
					}

					// Store all tokens in Redis
					const client = redis.getClient();
					if (!client) {
						throw Errors.internalError();
					}
					const multi = client.multi();
					for (const [upload_token, tokenData] of tokenDataMap.entries()) {
						multi.set(presignKey(upload_token), JSON.stringify(tokenData), 'EX', PRESIGN_TTL);
					}
					await multi.exec();

					return sendSuccess(res, files.length === 1 ? results[0] : results);
				} catch (error) {
					return res.error(error);
				}
			},
		},
	};
};
