import { Application } from 'express';
import { Resource } from 'express-automatic-routes';
import { Req, Res } from '#interfaces/IApi';
import verify from '#middlewares/auth';
import { Errors } from '#interfaces/error';
import RedisService from '#services/storage/redisService';
import { FileProvider } from '#providers/FileProvider';
import { getFileTypeFromMimetype } from '#services/file-system/uploadService';
import { getStorageAdapter } from '#services/storage/adapters/StorageFactory';
import { MinioStorageAdapter } from '#services/storage/adapters/MinioStorageAdapter';
import { presignKey, type PresignTokenData } from './presign';
import { sendSuccess } from '#utils/responseUtils';
import Config from '#config';

export default (_express: Application) => {
	const fileProvider = FileProvider.getInstance();
	const redis = RedisService.getInstance();
	return <Resource>{
		post: {
			middleware: [verify],
			handler: async (req: Req, res: Res) => {
				try {
					const { file_key, file_name, file_size, mime_type, upload_token, title, is_library } = req.body as {
						file_key?: string;
						file_name?: string;
						file_size?: number;
						mime_type?: string;
						upload_token?: string;
						title?: string;
						is_library?: boolean;
					};

					// Explicit null check + separate positive-number validation
					if (!file_key || !file_name || file_size == null || !mime_type || !upload_token) {
						throw Errors.validation([
							{
								field: 'body',
								code: 'MISSING_FILE_FIELDS',
								message: {
									vi: 'Thiếu thông tin bắt buộc',
									en: 'Missing required fields: file_key, file_name, file_size, mime_type, upload_token',
								},
							},
						]);
					}

					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' },
							},
						]);
					}

					// SEC-03: Add file size validation (max 100 MB)
					const MAX_SIZE = 100 * 1024 * 1024;
					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' },
							},
						]);
					}

					// Validate upload_token from Redis
					const tokenData = await redis.get<PresignTokenData>(presignKey(upload_token));
					if (!tokenData) {
						throw Errors.validation([
							{
								field: 'upload_token',
								code: 'UPLOAD_TOKEN_INVALID',
								message: { vi: 'upload_token không hợp lệ hoặc đã hết hạn', en: 'Invalid or expired upload_token' },
							},
						]);
					}

					// Security: token must belong to the requesting user
					if (tokenData.user_id !== req.user!.id) {
						throw Errors.forbidden();
					}

					// Security: file_key must match what was presigned
					if (tokenData.file_key !== file_key) {
						throw Errors.validation([
							{
								field: 'file_key',
								code: 'UPLOAD_TOKEN_INVALID',
								message: { vi: 'file_key không khớp với upload_token', en: 'file_key does not match the upload_token' },
							},
						]);
					}

					// SEC-02: verify the file was actually uploaded to MinIO before creating a DB record
					const storageAdapter = getStorageAdapter();
					if (storageAdapter instanceof MinioStorageAdapter) {
						const exists = await storageAdapter.objectExists(file_key);
						if (!exists) {
							throw Errors.validation([
								{
									field: 'file_key',
									code: 'FILE_NOT_UPLOADED',
									message: {
										vi: 'File chưa được upload lên storage. Vui lòng upload trước khi confirm.',
										en: 'File not found in storage. Please upload the file before confirming.',
									},
								},
							]);
						}

						// SEC-04: Verify actual file size matches declared size
						const actualSize = await storageAdapter.getObjectSize(file_key);
						if (actualSize !== file_size) {
							throw Errors.validation([
								{
									field: 'file_size',
									code: 'FILE_SIZE_MISMATCH',
									message: {
										vi: `Kích thước file thực tế (${actualSize} bytes) không khớp với kích thước khai báo (${file_size} bytes)`,
										en: `Actual file size (${actualSize} bytes) does not match declared size (${file_size} bytes)`,
									},
								},
							]);
						}
					}

					// CQ-02: derive provider/bucket from adapter instead of re-reading env
					const rawProvider =
						storageAdapter instanceof MinioStorageAdapter
							? 'minio'
							: Config.storage.provider === 'minio'
								? 's3'
								: 'local';
					const provider = rawProvider as 'local' | 'minio' | 's3';
					const bucketName =
						provider !== 'local' ? Config.storage.minio.bucket || Config.storage.minio.s3Bucket || '' : '';
					const category = (getFileTypeFromMimetype(mime_type) || '').toLowerCase() || null;

					// DB insert BEFORE token deletion — allows retry if insert fails
					const data = await fileProvider.create({
						id: crypto.randomUUID(),
						visibility: 'private',
						created_at: new Date(),
						updated_at: new Date(),
						original_name: file_name,
						provider,
						bucket_name: bucketName,
						object_key: file_key,
						mime_type,
						category,
						size_bytes: file_size,
						title: title || null,
						is_library: is_library ?? false,
						tenant_id: (req.user as any)?.tenantId || null,
						owner_user_id: req.user!.id,
						status: 'uploaded',

						created_by: req.user!.id,
					});

					// Consume token only after successful DB insert
					await redis.delete(presignKey(upload_token));

					return sendSuccess(res, data.toJSON());
				} catch (error) {
					return res.error(error);
				}
			},
		},
	};
};
