import { Application } from 'express';
import { Resource } from 'express-automatic-routes';
import { FileProvider } from '#providers/FileProvider';
import { Req, Res } from '#interfaces/IApi';
import { queryModifier } from '#middlewares/query-modifier';
import verify from '#middlewares/auth';
import { validateId } from '#middlewares/validators';
import { upload } from '#services/file-system/uploadService';
import deleteFileService from '#services/file-system/deleteService';
import { validateZod } from '#middlewares/validators';
import { FileMutateBodySchema } from '#contracts/file/schema';
import { sanitizeText } from '#utils/stringUtils';
import { getStorageAdapter } from '#services/storage/adapters/StorageFactory';
import { processAndUpload } from '#services/file-system/fileUploadPipelineService';
import { Errors } from '#interfaces/error';
import { sendSuccess } from '#utils/responseUtils';
import Config from '#config';

function isAdminUser(req: Req): boolean {
	return req.user?.roles?.some((r) => ['admin', 'system_admin'].includes(r)) ?? false;
}

export default (_express: Application) => {
	const fileProvider = FileProvider.getInstance();
	return <Resource>{
		get: {
			middleware: [verify, validateId],
			handler: async (req: Req, res: Res) => {
				try {
					const data = await fileProvider.getById({ id: req.params.id! });
					if (!data) {
						throw Errors.fileNotFound();
					}
					// P0-2: ownership / admin gate
					if (!isAdminUser(req) && data.owner_user_id !== req.user?.id) {
						throw Errors.forbidden();
					}
					return sendSuccess(res, data.toJSON());
				} catch (error) {
					return res.error(error);
				}
			},
		},
		put: {
			middleware: [verify, upload, validateId, validateZod(FileMutateBodySchema)],
			handler: async (req: Req, res: Res) => {
				const transaction = await fileProvider.transaction();
				const storageAdapter = getStorageAdapter();
				let uploaded: Awaited<ReturnType<typeof processAndUpload>> | undefined;

				try {
					const existingFile = await fileProvider.getById({ id: req.params.id! });
					if (!existingFile) {
						throw Errors.fileNotFound();
					}
					// P0-2: ownership / admin gate
					if (!isAdminUser(req) && existingFile.owner_user_id !== req.user?.id) {
						throw Errors.forbidden();
					}
					const file = req.file;

					if (file) {
						uploaded = await processAndUpload(file, storageAdapter);

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

						const data = await fileProvider.put(
							req.params.id!,
							{
								// P0-4: explicit whitelist — no req.body spread
								title,
								description,
								note,
								is_library:
									typeof req.body.is_library === 'boolean' ? req.body.is_library : req.body.is_library === 'true',
								provider: uploaded.storageProvider,
								bucket_name:
									uploaded.storageProvider !== 'local'
										? Config.storage.minio.bucket || Config.storage.minio.s3Bucket || ''
										: '',
								object_key: uploaded.storagePath,
								original_name: uploaded.decodedName,
								mime_type: file.mimetype,
								category: (uploaded.fileType || '').toLowerCase() || existingFile.category || null,
								size_bytes: file.size,
								checksum_sha_256: uploaded.checksum,
								is_deleted: false,
								updated_at: new Date(),
								updated_by: req.user?.id || null,
							} as any,
							{ transaction },
						);
						await transaction.commit();

						// Delete old physical file via its original adapter
						if (existingFile.provider === 'minio' || existingFile.provider === 's3') {
							await storageAdapter.delete(existingFile.object_key).catch(() => {});
						} else {
							await deleteFileService({
								filePath: existingFile.object_key,
								compressInfo: null,
							}).catch(() => {});
						}

						return sendSuccess(res, data.toJSON());
					}

					// No new file uploaded, just update metadata (P0-4: whitelist only)
					const title = sanitizeText(req.body.title, 150);
					const description = sanitizeText(req.body.description, 500);
					const note = sanitizeText(req.body.note, 255);
					const data = await fileProvider.put(
						req.params.id!,
						{
							title,
							description,
							note,
							is_library:
								typeof req.body.is_library === 'boolean' ? req.body.is_library : req.body.is_library === 'true',
							updated_at: new Date(),
							updated_by: req.user?.id || null,
						} as any,
						{ transaction },
					);

					await transaction.commit();

					return sendSuccess(res, data.toJSON());
				} catch (error) {
					await transaction.rollback().catch(() => {});
					if (uploaded) {
						await storageAdapter.delete(uploaded.storagePath).catch(() => {});
					}
					return res.error(error);
				}
			},
		},
		delete: {
			middleware: [verify, queryModifier, validateId],
			handler: async (req: Req, res: Res) => {
				try {
					const fileRecord = await fileProvider.getById({ id: req.params.id! });
					if (!fileRecord) {
						throw Errors.fileNotFound();
					}

					// P0-2: ownership / admin gate
					if (!isAdminUser(req) && fileRecord.owner_user_id !== req.user?.id) {
						throw Errors.forbidden();
					}

					// Hard delete — P0-1: admin only, P1-4: DB first then physical
					const isHardDelete = req.query.hard === 'true';

					if (isHardDelete) {
						// P0-1: only admins may hard-delete
						if (!isAdminUser(req)) {
							throw Errors.forbidden();
						}
						// P1-4: delete DB record first, then physical (best-effort) to avoid orphan records
						// MISS-02: Use hardDelete method for permanent deletion
						await fileProvider.hardDelete(req.params.id!);
						if (fileRecord.provider === 'minio' || fileRecord.provider === 's3') {
							const storageAdapter = getStorageAdapter();
							await storageAdapter.delete(fileRecord.object_key).catch(() => {});
						} else {
							await deleteFileService({
								filePath: fileRecord.object_key,
								compressInfo: null,
							}).catch(() => {});
						}

						return sendSuccess(res, true);
					}

					// Default: soft delete — keep physical file and DB record
					const success = await fileProvider.softDelete(req.params.id!, req.user!.id);
					return sendSuccess(res, success);
				} catch (error) {
					return res.error(error);
				}
			},
		},
	};
};
