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 { 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');

// Mock Firebase Admin
jest.mock('firebase-admin', () => ({
  apps: [],
  initializeApp: jest.fn(),
  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('Firebase Storage Direct Upload Pipeline Tests', () => {
  let app: express.Application;

  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();
    });

    // Mock file storage service for new upload flow
    (fileStorageService.generateSignedUploadUrl as jest.Mock).mockResolvedValue(
      'https://storage.googleapis.com/test-bucket/uploads/test-user-id/1234567890-test-document.pdf?signature=...'
    );

    (fileStorageService.getFile as jest.Mock).mockResolvedValue(Buffer.from('test file content'));

    // Mock document model
    const { DocumentModel } = require('../../models/DocumentModel');
    DocumentModel.create.mockResolvedValue({
      id: '123e4567-e89b-12d3-a456-426614174000',
      user_id: mockUser.uid,
      original_file_name: 'test-document.pdf',
      file_path: 'uploads/test-user-id/1234567890-test-document.pdf',
      file_size: 1024,
      status: 'uploading',
      created_at: new Date(),
      updated_at: new Date()
    });

    DocumentModel.findById.mockResolvedValue({
      id: '123e4567-e89b-12d3-a456-426614174000',
      user_id: mockUser.uid,
      original_file_name: 'test-document.pdf',
      file_path: 'uploads/test-user-id/1234567890-test-document.pdf',
      file_size: 1024,
      status: 'uploading',
      created_at: new Date(),
      updated_at: new Date()
    });

    DocumentModel.updateById.mockResolvedValue(true);

    // Mock unified document processor
    (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);
    
    // Add routes for testing
    app.post('/upload-url', documentController.getUploadUrl);
    app.post('/:id/confirm-upload', documentController.confirmUpload);
  });

  describe('Upload URL Generation', () => {
    it('should successfully get upload URL', async () => {
      const response = await request(app)
        .post('/upload-url')
        .send({
          fileName: 'test-document.pdf',
          fileSize: 1024,
          contentType: 'application/pdf'
        })
        .expect(200);

      expect(response.body.documentId).toBeDefined();
      expect(response.body.uploadUrl).toBeDefined();
      expect(response.body.filePath).toBeDefined();
    });

    it('should reject non-PDF files', async () => {
      const response = await request(app)
        .post('/upload-url')
        .send({
          fileName: 'test-document.txt',
          fileSize: 1024,
          contentType: 'text/plain'
        })
        .expect(400);

      expect(response.body.error).toBe('Only PDF files are supported');
    });

    it('should reject files larger than 50MB', async () => {
      const response = await request(app)
        .post('/upload-url')
        .send({
          fileName: 'large-document.pdf',
          fileSize: 60 * 1024 * 1024, // 60MB
          contentType: 'application/pdf'
        })
        .expect(400);

      expect(response.body.error).toBe('File size exceeds 50MB limit');
    });

    it('should handle missing required fields', async () => {
      const response = await request(app)
        .post('/upload-url')
        .send({
          fileName: 'test-document.pdf'
          // Missing fileSize and contentType
        })
        .expect(400);

      expect(response.body.error).toBe('Missing required fields: fileName, fileSize, contentType');
    });
  });

  describe('Upload Confirmation', () => {
    it('should successfully confirm upload and trigger processing', async () => {
      // First create a document record
      const { DocumentModel } = require('../../models/DocumentModel');
      const document = await DocumentModel.create({
        user_id: mockUser.uid,
        original_file_name: 'test-document.pdf',
        file_path: 'uploads/test-user-id/1234567890-test-document.pdf',
        file_size: 1024,
        status: 'uploading'
      });

      const response = await request(app)
        .post(`/${document.id}/confirm-upload`)
        .expect(200);

      expect(response.body.success).toBe(true);
      expect(response.body.documentId).toBe(document.id);
      expect(response.body.status).toBe('processing');
    });

    it('should handle confirm upload for non-existent document', async () => {
      const fakeId = '12345678-1234-1234-1234-123456789012';
      
      const response = await request(app)
        .post(`/${fakeId}/confirm-upload`)
        .expect(404);

      expect(response.body.error).toBe('Document not found');
    });
  });

  describe('Error Handling', () => {
    it('should handle GCS connection failures during URL generation', async () => {
      (fileStorageService.generateSignedUploadUrl as jest.Mock).mockRejectedValue(
        new Error('GCS connection timeout')
      );

      const response = await request(app)
        .post('/upload-url')
        .send({
          fileName: 'test-document.pdf',
          fileSize: 1024,
          contentType: 'application/pdf'
        })
        .expect(500);

      expect(response.body.error).toBe('Failed to generate upload URL');
    });

    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-url')
        .send({
          fileName: 'test-document.pdf',
          fileSize: 1024,
          contentType: 'application/pdf'
        })
        .expect(401);

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

  describe('Performance and Scalability', () => {
    it('should handle concurrent upload URL requests', async () => {
      const concurrentRequests = 5;
      const promises: any[] = [];

      for (let i = 0; i < concurrentRequests; i++) {
        promises.push(
          request(app)
            .post('/upload-url')
            .send({
              fileName: `test-document-${i}.pdf`,
              fileSize: 1024,
              contentType: 'application/pdf'
            })
        );
      }

      const responses = await Promise.all(promises);

      responses.forEach((response: any) => {
        expect(response.status).toBe(200);
        expect(response.body.documentId).toBeDefined();
        expect(response.body.uploadUrl).toBeDefined();
      });

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