import request from 'supertest';
import express from 'express';
import { fileStorageService } from '../../services/fileStorageService';
import { documentController } from '../../controllers/documentController';
import { unifiedDocumentProcessor } from '../../services/unifiedDocumentProcessor';
import { uploadMonitoringService } from '../../services/uploadMonitoringService';
import { handleFileUpload } from '../../middleware/upload';
import { verifyFirebaseToken } from '../../middleware/firebaseAuth';
import { addCorrelationId } from '../../middleware/validation';

// Mock all external dependencies
jest.mock('../../services/fileStorageService');
jest.mock('../../services/unifiedDocumentProcessor');
jest.mock('../../services/uploadMonitoringService');
jest.mock('../../middleware/firebaseAuth');
jest.mock('../../middleware/upload');

// Mock Firebase Admin
jest.mock('firebase-admin', () => ({
  auth: () => ({
    verifyIdToken: jest.fn().mockResolvedValue({
      uid: 'test-user-id',
      email: 'test@example.com',
    }),
  }),
}));

// Mock database
jest.mock('../../models/DocumentModel', () => ({
  DocumentModel: {
    create: jest.fn(),
    findById: jest.fn(),
    findByUserId: jest.fn(),
    updateById: jest.fn(),
    deleteById: jest.fn(),
  },
}));

describe('Upload Pipeline Integration Tests', () => {
  let app: express.Application;

  const mockFile = {
    originalname: 'test-document.pdf',
    filename: '1234567890-abc123.pdf',
    path: '/tmp/1234567890-abc123.pdf',
    size: 1024,
    mimetype: 'application/pdf',
    buffer: Buffer.from('test file content'),
  };

  const mockUser = {
    uid: 'test-user-id',
    email: 'test@example.com',
  };

  beforeEach(() => {
    jest.clearAllMocks();
    
    // Setup mocks
    (verifyFirebaseToken as jest.Mock).mockImplementation((req: any, res: any, next: any) => {
      req.user = mockUser;
      next();
    });

    (handleFileUpload as jest.Mock).mockImplementation((req: any, res: any, next: any) => {
      req.file = mockFile;
      next();
    });

    (fileStorageService.storeFile as jest.Mock).mockResolvedValue({
      success: true,
      fileInfo: {
        originalName: 'test-document.pdf',
        filename: '1234567890-abc123.pdf',
        path: 'uploads/test-user-id/1234567890-abc123.pdf',
        size: 1024,
        mimetype: 'application/pdf',
        uploadedAt: new Date(),
        gcsPath: 'uploads/test-user-id/1234567890-abc123.pdf',
      },
    });

    (unifiedDocumentProcessor.processDocument as jest.Mock).mockResolvedValue({
      success: true,
      documentId: '123e4567-e89b-12d3-a456-426614174000',
      status: 'processing',
    });

    (uploadMonitoringService.trackUploadEvent as jest.Mock).mockResolvedValue(undefined);

    // Create test app
    app = express();
    app.use(express.json());
    app.use(verifyFirebaseToken);
    app.use(addCorrelationId);
    app.post('/upload', handleFileUpload, documentController.uploadDocument);
  });

  describe('Complete Upload Pipeline', () => {
    it('should successfully process a complete file upload', async () => {
      const response = await request(app)
        .post('/upload')
        .attach('file', Buffer.from('test content'), 'test-document.pdf')
        .expect(200);

      expect(response.body.success).toBe(true);
      expect(response.body.documentId).toBeDefined();
      expect(response.body.status).toBe('processing');

      // Verify file storage was called
      expect(fileStorageService.storeFile).toHaveBeenCalledWith(mockFile, mockUser.uid);

      // Verify document processing was called
      expect(unifiedDocumentProcessor.processDocument).toHaveBeenCalledWith(
        expect.objectContaining({
          userId: mockUser.uid,
          fileInfo: expect.objectContaining({
            originalName: 'test-document.pdf',
            gcsPath: 'uploads/test-user-id/1234567890-abc123.pdf',
          }),
        })
      );

      // Verify monitoring was called
      expect(uploadMonitoringService.trackUploadEvent).toHaveBeenCalled();
    });

    it('should handle file storage failures gracefully', async () => {
      (fileStorageService.storeFile as jest.Mock).mockResolvedValue({
        success: false,
        error: 'GCS upload failed',
      });

      const response = await request(app)
        .post('/upload')
        .attach('file', Buffer.from('test content'), 'test-document.pdf')
        .expect(500);

      expect(response.body.error).toContain('Failed to store file');
      expect(unifiedDocumentProcessor.processDocument).not.toHaveBeenCalled();
    });

    it('should handle document processing failures gracefully', async () => {
      (unifiedDocumentProcessor.processDocument as jest.Mock).mockResolvedValue({
        success: false,
        error: 'Processing failed',
      });

      const response = await request(app)
        .post('/upload')
        .attach('file', Buffer.from('test content'), 'test-document.pdf')
        .expect(500);

      expect(response.body.error).toContain('Failed to process document');
    });

    it('should handle large file uploads', async () => {
      const largeFile = {
        ...mockFile,
        size: 50 * 1024 * 1024, // 50MB
      };

      (handleFileUpload as jest.Mock).mockImplementation((req: any, res: any, next: any) => {
        req.file = largeFile;
        next();
      });

      await request(app)
        .post('/upload')
        .attach('file', Buffer.alloc(50 * 1024 * 1024), 'large-document.pdf')
        .expect(200);

      expect(fileStorageService.storeFile).toHaveBeenCalledWith(largeFile, mockUser.uid);
    });

    it('should handle unsupported file types', async () => {
      const unsupportedFile = {
        ...mockFile,
        mimetype: 'application/exe',
        originalname: 'malicious.exe',
      };

      (handleFileUpload as jest.Mock).mockImplementation((req: any, res: any, next: any) => {
        req.file = unsupportedFile;
        next();
      });

      const response = await request(app)
        .post('/upload')
        .attach('file', Buffer.from('test content'), 'malicious.exe')
        .expect(400);

      expect(response.body.error).toContain('Unsupported file type');
    });

    it('should track upload progress correctly', async () => {
      const response = await request(app)
        .post('/upload')
        .attach('file', Buffer.from('test content'), 'test-document.pdf')
        .expect(200);

      // Verify monitoring events were tracked
      expect(uploadMonitoringService.trackUploadEvent).toHaveBeenCalledWith(
        expect.objectContaining({
          userId: mockUser.uid,
          fileInfo: expect.objectContaining({
            originalName: 'test-document.pdf',
            size: 1024,
          }),
          status: 'success',
          stage: 'file_storage',
        })
      );
    });
  });

  describe('Error Scenarios and Recovery', () => {
    it('should handle GCS connection failures', async () => {
      (fileStorageService.storeFile as jest.Mock).mockRejectedValue(
        new Error('GCS connection timeout')
      );

      const response = await request(app)
        .post('/upload')
        .attach('file', Buffer.from('test content'), 'test-document.pdf')
        .expect(500);

      expect(response.body.error).toContain('Internal server error');
    });

    it('should handle partial upload failures', async () => {
      // Mock storage success but processing failure
      (fileStorageService.storeFile as jest.Mock).mockResolvedValue({
        success: true,
        fileInfo: {
          originalName: 'test-document.pdf',
          filename: '1234567890-abc123.pdf',
          path: 'uploads/test-user-id/1234567890-abc123.pdf',
          size: 1024,
          mimetype: 'application/pdf',
          uploadedAt: new Date(),
          gcsPath: 'uploads/test-user-id/1234567890-abc123.pdf',
        },
      });

      (unifiedDocumentProcessor.processDocument as jest.Mock).mockRejectedValue(
        new Error('Processing service unavailable')
      );

      const response = await request(app)
        .post('/upload')
        .attach('file', Buffer.from('test content'), 'test-document.pdf')
        .expect(500);

      expect(response.body.error).toContain('Failed to process document');
    });

    it('should handle authentication failures', async () => {
      (verifyFirebaseToken as jest.Mock).mockImplementation((req: any, res: any, next: any) => {
        res.status(401).json({ error: 'Invalid token' });
      });

      const response = await request(app)
        .post('/upload')
        .attach('file', Buffer.from('test content'), 'test-document.pdf')
        .expect(401);

      expect(response.body.error).toBe('Invalid token');
    });

    it('should handle missing file uploads', async () => {
      (handleFileUpload as jest.Mock).mockImplementation((req: any, res: any, next: any) => {
        req.file = undefined;
        next();
      });

      const response = await request(app)
        .post('/upload')
        .expect(400);

      expect(response.body.error).toContain('No file uploaded');
    });
  });

  describe('Performance and Scalability', () => {
    it('should handle concurrent uploads', async () => {
      const concurrentRequests = 5;
      const promises = [];

      for (let i = 0; i < concurrentRequests; i++) {
        promises.push(
          request(app)
            .post('/upload')
            .attach('file', Buffer.from(`test content ${i}`), `test-document-${i}.pdf`)
        );
      }

      const responses = await Promise.all(promises);

      responses.forEach(response => {
        expect(response.status).toBe(200);
        expect(response.body.success).toBe(true);
      });

      expect(fileStorageService.storeFile).toHaveBeenCalledTimes(concurrentRequests);
    });

    it('should handle upload timeout scenarios', async () => {
      (fileStorageService.storeFile as jest.Mock).mockImplementation(
        () => new Promise(resolve => setTimeout(() => resolve({
          success: true,
          fileInfo: {
            originalName: 'test-document.pdf',
            filename: '1234567890-abc123.pdf',
            path: 'uploads/test-user-id/1234567890-abc123.pdf',
            size: 1024,
            mimetype: 'application/pdf',
            uploadedAt: new Date(),
            gcsPath: 'uploads/test-user-id/1234567890-abc123.pdf',
          },
        }), 30000)) // 30 second delay
      );

      await request(app)
        .post('/upload')
        .attach('file', Buffer.from('test content'), 'test-document.pdf')
        .timeout(35000) // 35 second timeout
        .expect(200);
    });
  });

  describe('Data Integrity and Validation', () => {
    it('should validate file metadata correctly', async () => {
      const response = await request(app)
        .post('/upload')
        .attach('file', Buffer.from('test content'), 'test-document.pdf')
        .expect(200);

      // Verify file metadata is preserved
      expect(fileStorageService.storeFile).toHaveBeenCalledWith(
        expect.objectContaining({
          originalname: 'test-document.pdf',
          size: 1024,
          mimetype: 'application/pdf',
        }),
        mockUser.uid
      );
    });

    it('should generate unique file paths for each upload', async () => {
      const uploads = [];
      for (let i = 0; i < 3; i++) {
        uploads.push(
          request(app)
            .post('/upload')
            .attach('file', Buffer.from(`test content ${i}`), `test-document-${i}.pdf`)
        );
      }

      const responses = await Promise.all(uploads);
      
      // Verify each upload was called
      expect(fileStorageService.storeFile).toHaveBeenCalledTimes(3);
    });
  });
}); 