import { Request, Response, NextFunction } from 'express';
import multer, { FileFilterCallback, StorageEngine } from 'multer';
import path from 'path';
import fs from 'fs';
import { v4 as uuidv4 } from 'uuid';
import httpStatus from 'http-status';
import ApiError from '@/shared/utils/errors/ApiError.js';

interface UploadOptions {
  destination?: string;
  fileTypes?: string[];
  maxFileSize?: number;
  fileNamePrefix?: string;
  fieldName?: string;
  multiple?: boolean;
  maxFiles?: number;
}

const defaultOptions: UploadOptions = {
  destination: 'uploads/',
  fileTypes: [
    'application/vnd.ms-excel', // .xls
    'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // .xlsx
    'text/csv', // .csv
  ],
  maxFileSize: 5 * 1024 * 1024,
  fileNamePrefix: '',
  fieldName: 'file',
  multiple: false,
  maxFiles: 10,
};

const createStorage = (destination: string): StorageEngine => {
  const uploadDir = path.resolve(process.cwd(), destination);
  if (!fs.existsSync(uploadDir)) fs.mkdirSync(uploadDir, { recursive: true });

  return multer.diskStorage({
    destination: (_req, _file, cb) => {
      cb(null, uploadDir);
    },
    filename: (_req, file, cb) => {
      const uniqueFilename = `${uuidv4()}${path.extname(file.originalname)}`;
      cb(null, uniqueFilename);
    },
  });
};

const createFileFilter =
  (fileTypes: string[]) => (_req: Request, file: { mimetype: string; }, cb: FileFilterCallback) => {
    if (fileTypes.includes(file.mimetype)) cb(null, true);
    else
      cb(
        new ApiError(
          httpStatus.BAD_REQUEST,
          `File type not allowed. Allowed types: ${fileTypes.join(', ')}`,
        ),
      );
  };

const upload = (options: UploadOptions = {}) => {
  const config = { ...defaultOptions, ...options };

  const multerInstance = multer({
    storage: createStorage(config.destination),
    fileFilter: createFileFilter(config.fileTypes),
    limits: {
      fileSize: config.maxFileSize,
      files: config.maxFiles,
    },
  });

  if (config.multiple)
    return (req: Request, res: Response, next: NextFunction) => {
      const uploadMiddleware = multerInstance.array(
        config.fieldName,
        config.maxFiles,
      );
      uploadMiddleware(req, res, (err) => {
        if (err instanceof multer.MulterError) {
          if (err.code === 'LIMIT_FILE_SIZE')
            return next(
              new ApiError(
                httpStatus.BAD_REQUEST,
                `File too large. Maximum size is ${config.maxFileSize / (1024 * 1024)}MB`,
              ),
            );
          else if (err.code === 'LIMIT_FILE_COUNT')
            return next(
              new ApiError(
                httpStatus.BAD_REQUEST,
                `Too many files. Maximum allowed is ${config.maxFiles}`,
              ),
            );

          return next(new ApiError(httpStatus.BAD_REQUEST, err.message));
        } else if (err) {
          return next(err);
        }
        next();
      });
    };
  else
    return (req: Request, res: Response, next: NextFunction) => {
      const uploadMiddleware = multerInstance.single(config.fieldName);
      uploadMiddleware(req, res, (err) => {
        if (err instanceof multer.MulterError) {
          if (err.code === 'LIMIT_FILE_SIZE')
            return next(
              new ApiError(
                httpStatus.BAD_REQUEST,
                `File too large. Maximum size is ${config.maxFileSize / (1024 * 1024)}MB`,
              ),
            );

          return next(new ApiError(httpStatus.BAD_REQUEST, err.message));
        } else if (err) {
          return next(err);
        }
        next();
      });
    };
};

export default upload;
