import { Request, Response, NextFunction } from 'express';
import multer from 'multer';
import fs from 'fs';
import { handleFileUpload, handleUploadError, cleanupUploadedFile, getFileInfo } from '../upload';

// Mock the logger
jest.mock('../../utils/logger', () => ({
  logger: {
    info: jest.fn(),
    warn: jest.fn(),
    error: jest.fn(),
  },
}));

// Mock fs
jest.mock('fs', () => ({
  existsSync: jest.fn(),
  mkdirSync: jest.fn(),
}));

describe('Upload Middleware', () => {
  let mockReq: Partial<Request>;
  let mockRes: Partial<Response>;
  let mockNext: NextFunction;

  beforeEach(() => {
    mockReq = {
      ip: '127.0.0.1',
    } as any;
    mockRes = {
      status: jest.fn().mockReturnThis(),
      json: jest.fn(),
    };
    mockNext = jest.fn();

    // Reset mocks
    jest.clearAllMocks();
  });

  describe('handleUploadError', () => {
    it('should handle LIMIT_FILE_SIZE error', () => {
      const error = new multer.MulterError('LIMIT_FILE_SIZE', 'document');
      error.code = 'LIMIT_FILE_SIZE';

      handleUploadError(error, mockReq as Request, mockRes as Response, mockNext);

      expect(mockRes.status).toHaveBeenCalledWith(400);
      expect(mockRes.json).toHaveBeenCalledWith({
        success: false,
        error: 'File too large',
        message: expect.stringContaining('File size must be less than'),
      });
    });

    it('should handle LIMIT_FILE_COUNT error', () => {
      const error = new multer.MulterError('LIMIT_FILE_COUNT', 'document');
      error.code = 'LIMIT_FILE_COUNT';

      handleUploadError(error, mockReq as Request, mockRes as Response, mockNext);

      expect(mockRes.status).toHaveBeenCalledWith(400);
      expect(mockRes.json).toHaveBeenCalledWith({
        success: false,
        error: 'Too many files',
        message: 'Only one file can be uploaded at a time',
      });
    });

    it('should handle LIMIT_UNEXPECTED_FILE error', () => {
      const error = new multer.MulterError('LIMIT_UNEXPECTED_FILE', 'document');
      error.code = 'LIMIT_UNEXPECTED_FILE';

      handleUploadError(error, mockReq as Request, mockRes as Response, mockNext);

      expect(mockRes.status).toHaveBeenCalledWith(400);
      expect(mockRes.json).toHaveBeenCalledWith({
        success: false,
        error: 'Unexpected file field',
        message: 'File must be uploaded using the correct field name',
      });
    });

    it('should handle generic multer errors', () => {
      const error = new multer.MulterError('LIMIT_FILE_SIZE', 'document');
      error.code = 'LIMIT_FILE_SIZE';

      handleUploadError(error, mockReq as Request, mockRes as Response, mockNext);

      expect(mockRes.status).toHaveBeenCalledWith(400);
      expect(mockRes.json).toHaveBeenCalledWith({
        success: false,
        error: 'File too large',
        message: expect.stringContaining('File size must be less than'),
      });
    });

    it('should handle non-multer errors', () => {
      const error = new Error('Custom upload error');

      handleUploadError(error, mockReq as Request, mockRes as Response, mockNext);

      expect(mockRes.status).toHaveBeenCalledWith(400);
      expect(mockRes.json).toHaveBeenCalledWith({
        success: false,
        error: 'File upload failed',
        message: 'Custom upload error',
      });
    });

    it('should call next when no error', () => {
      handleUploadError(null, mockReq as Request, mockRes as Response, mockNext);

      expect(mockNext).toHaveBeenCalled();
      expect(mockRes.status).not.toHaveBeenCalled();
      expect(mockRes.json).not.toHaveBeenCalled();
    });
  });

  describe('cleanupUploadedFile', () => {
    it('should delete existing file', () => {
      const filePath = '/test/path/file.pdf';
      const mockUnlinkSync = jest.fn();
      
      (fs.existsSync as jest.Mock).mockReturnValue(true);
      (fs.unlinkSync as jest.Mock) = mockUnlinkSync;

      cleanupUploadedFile(filePath);

      expect(fs.existsSync).toHaveBeenCalledWith(filePath);
      expect(mockUnlinkSync).toHaveBeenCalledWith(filePath);
    });

    it('should not delete non-existent file', () => {
      const filePath = '/test/path/file.pdf';
      const mockUnlinkSync = jest.fn();
      
      (fs.existsSync as jest.Mock).mockReturnValue(false);
      (fs.unlinkSync as jest.Mock) = mockUnlinkSync;

      cleanupUploadedFile(filePath);

      expect(fs.existsSync).toHaveBeenCalledWith(filePath);
      expect(mockUnlinkSync).not.toHaveBeenCalled();
    });

    it('should handle deletion errors gracefully', () => {
      const filePath = '/test/path/file.pdf';
      const mockUnlinkSync = jest.fn().mockImplementation(() => {
        throw new Error('Permission denied');
      });
      
      (fs.existsSync as jest.Mock).mockReturnValue(true);
      (fs.unlinkSync as jest.Mock) = mockUnlinkSync;

      // Should not throw error
      expect(() => cleanupUploadedFile(filePath)).not.toThrow();
    });
  });

  describe('getFileInfo', () => {
    it('should return correct file info', () => {
      const mockFile = {
        originalname: 'test-document.pdf',
        filename: '1234567890-abc123.pdf',
        path: '/uploads/test-user-id/1234567890-abc123.pdf',
        size: 1024,
        mimetype: 'application/pdf',
      };

      const fileInfo = getFileInfo(mockFile as any);

      expect(fileInfo).toEqual({
        originalName: 'test-document.pdf',
        filename: '1234567890-abc123.pdf',
        path: '/uploads/test-user-id/1234567890-abc123.pdf',
        size: 1024,
        mimetype: 'application/pdf',
        uploadedAt: expect.any(Date),
      });
    });
  });

  describe('handleFileUpload middleware', () => {
    it('should be an array with uploadMiddleware and handleUploadError', () => {
      expect(Array.isArray(handleFileUpload)).toBe(true);
      expect(handleFileUpload).toHaveLength(2);
    });
  });
}); 