import { fileStorageService } from '../fileStorageService';


// Mock Google Cloud Storage
const mockBucket = {
  file: jest.fn(),
  upload: jest.fn(),
  getFiles: jest.fn(),
  deleteFiles: jest.fn(),
};

const mockFile = {
  save: jest.fn(),
  download: jest.fn(),
  delete: jest.fn(),
  getMetadata: jest.fn(),
  exists: jest.fn(),
  getSignedUrl: jest.fn(),
  copy: jest.fn(),
  move: jest.fn(),
};

const mockStorage = {
  bucket: jest.fn(() => mockBucket),
};

jest.mock('@google-cloud/storage', () => ({
  Storage: jest.fn(() => mockStorage),
}));

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

// Mock upload monitoring service
jest.mock('../uploadMonitoringService', () => ({
  uploadMonitoringService: {
    trackUploadEvent: jest.fn(),
  },
}));

// Mock config
jest.mock('../../config/env', () => ({
  config: {
    googleCloud: {
      gcsBucketName: 'test-bucket',
      applicationCredentials: 'test-credentials.json',
      projectId: 'test-project',
    },
  },
}));

describe('FileStorageService - GCS Implementation', () => {
  const testFile = {
    originalname: 'test-document.pdf',
    filename: '1234567890-abc123.pdf',
    path: '/tmp/1234567890-abc123.pdf',
    size: 1024,
    mimetype: 'application/pdf',
    buffer: Buffer.from('test file content'),
  } as any;

  beforeEach(() => {
    jest.clearAllMocks();
    mockBucket.file.mockReturnValue(mockFile);
    mockFile.exists.mockResolvedValue([true]);
    mockFile.getMetadata.mockResolvedValue([{
      size: 1024,
      contentType: 'application/pdf',
      timeCreated: new Date(),
      timeUpdated: new Date(),
    }]);
    mockFile.getSignedUrl.mockResolvedValue(['https://storage.googleapis.com/test-bucket/test-file.pdf']);
  });

  describe('storeFile', () => {
    it('should store file in GCS successfully', async () => {
      const userId = 'test-user-id';
      mockFile.save.mockResolvedValue([{}]);

      const result = await fileStorageService.storeFile(testFile, userId);

      expect(result.success).toBe(true);
      expect(result.fileInfo).toBeDefined();
      expect(result.fileInfo?.originalName).toBe('test-document.pdf');
      expect(result.fileInfo?.size).toBe(1024);
      expect(result.fileInfo?.gcsPath).toContain(`uploads/${userId}/`);
      expect(mockBucket.file).toHaveBeenCalled();
      expect(mockFile.save).toHaveBeenCalled();
    });

    it('should handle GCS upload errors gracefully', async () => {
      const userId = 'test-user-id';
      mockFile.save.mockRejectedValue(new Error('GCS upload failed'));

      const result = await fileStorageService.storeFile(testFile, userId);

      expect(result.success).toBe(false);
      expect(result.error).toContain('Failed to store file');
    });

    it('should retry failed uploads', async () => {
      const userId = 'test-user-id';
      mockFile.save
        .mockRejectedValueOnce(new Error('Network error'))
        .mockResolvedValueOnce([{}]);

      const result = await fileStorageService.storeFile(testFile, userId);

      expect(result.success).toBe(true);
      expect(mockFile.save).toHaveBeenCalledTimes(2);
    });
  });

  describe('getFile', () => {
    it('should download file from GCS successfully', async () => {
      const filePath = 'uploads/test-user/test-file.pdf';
      const mockBuffer = Buffer.from('test file content');
      mockFile.download.mockResolvedValue([mockBuffer]);

      const result = await fileStorageService.getFile(filePath);

      expect(result).toEqual(mockBuffer);
      expect(mockBucket.file).toHaveBeenCalledWith(filePath);
      expect(mockFile.download).toHaveBeenCalled();
    });

    it('should return null when file does not exist', async () => {
      const filePath = 'uploads/test-user/nonexistent.pdf';
      mockFile.exists.mockResolvedValue([false]);

      const result = await fileStorageService.getFile(filePath);

      expect(result).toBeNull();
      expect(mockFile.download).not.toHaveBeenCalled();
    });

    it('should handle download errors gracefully', async () => {
      const filePath = 'uploads/test-user/test-file.pdf';
      mockFile.download.mockRejectedValue(new Error('Download failed'));

      const result = await fileStorageService.getFile(filePath);

      expect(result).toBeNull();
    });
  });

  describe('deleteFile', () => {
    it('should delete file from GCS successfully', async () => {
      const filePath = 'uploads/test-user/test-file.pdf';
      mockFile.delete.mockResolvedValue([{}]);

      const result = await fileStorageService.deleteFile(filePath);

      expect(result).toBe(true);
      expect(mockBucket.file).toHaveBeenCalledWith(filePath);
      expect(mockFile.delete).toHaveBeenCalled();
    });

    it('should return false when file does not exist', async () => {
      const filePath = 'uploads/test-user/nonexistent.pdf';
      mockFile.exists.mockResolvedValue([false]);

      const result = await fileStorageService.deleteFile(filePath);

      expect(result).toBe(false);
      expect(mockFile.delete).not.toHaveBeenCalled();
    });

    it('should handle deletion errors gracefully', async () => {
      const filePath = 'uploads/test-user/test-file.pdf';
      mockFile.delete.mockRejectedValue(new Error('Delete failed'));

      const result = await fileStorageService.deleteFile(filePath);

      expect(result).toBe(false);
    });
  });

  describe('getFileInfo', () => {
    it('should return file info from GCS metadata', async () => {
      const filePath = 'uploads/test-user/test-file.pdf';
      const mockMetadata = {
        size: 1024,
        contentType: 'application/pdf',
        timeCreated: new Date('2023-01-01'),
        timeUpdated: new Date('2023-01-01'),
      };
      mockFile.getMetadata.mockResolvedValue([mockMetadata]);

      const result = await fileStorageService.getFileInfo(filePath);

      expect(result).toBeDefined();
      expect(result?.size).toBe(1024);
      expect(result?.mimetype).toBe('application/pdf');
      expect(result?.path).toBe(filePath);
      expect(mockFile.getMetadata).toHaveBeenCalled();
    });

    it('should return null when file does not exist', async () => {
      const filePath = 'uploads/test-user/nonexistent.pdf';
      mockFile.exists.mockResolvedValue([false]);

      const result = await fileStorageService.getFileInfo(filePath);

      expect(result).toBeNull();
    });
  });

  describe('fileExists', () => {
    it('should return true when file exists in GCS', async () => {
      const filePath = 'uploads/test-user/test-file.pdf';
      mockFile.exists.mockResolvedValue([true]);

      const result = await fileStorageService.fileExists(filePath);

      expect(result).toBe(true);
      expect(mockFile.exists).toHaveBeenCalled();
    });

    it('should return false when file does not exist', async () => {
      const filePath = 'uploads/test-user/nonexistent.pdf';
      mockFile.exists.mockResolvedValue([false]);

      const result = await fileStorageService.fileExists(filePath);

      expect(result).toBe(false);
    });
  });

  describe('getFileSize', () => {
    it('should return file size from GCS metadata', async () => {
      const filePath = 'uploads/test-user/test-file.pdf';
      const mockMetadata = { size: 1024 };
      mockFile.getMetadata.mockResolvedValue([mockMetadata]);

      const result = await fileStorageService.getFileSize(filePath);

      expect(result).toBe(1024);
    });

    it('should return null when file does not exist', async () => {
      const filePath = 'uploads/test-user/nonexistent.pdf';
      mockFile.exists.mockResolvedValue([false]);

      const result = await fileStorageService.getFileSize(filePath);

      expect(result).toBeNull();
    });
  });

  describe('listFiles', () => {
    it('should list files from GCS bucket', async () => {
      const mockFiles = [
        {
          name: 'uploads/test-user/file1.pdf',
          size: 1024,
          contentType: 'application/pdf',
          timeCreated: new Date(),
          timeUpdated: new Date(),
        },
        {
          name: 'uploads/test-user/file2.pdf',
          size: 2048,
          contentType: 'application/pdf',
          timeCreated: new Date(),
          timeUpdated: new Date(),
        },
      ];
      mockBucket.getFiles.mockResolvedValue([mockFiles]);

      const result = await fileStorageService.listFiles('uploads/test-user/', 10);

      expect(result).toHaveLength(2);
      expect(result[0]?.name).toBe('uploads/test-user/file1.pdf');
      expect(result[0]?.size).toBe(1024);
      expect(mockBucket.getFiles).toHaveBeenCalledWith({
        prefix: 'uploads/test-user/',
        maxResults: 10,
      });
    });

    it('should handle empty results', async () => {
      mockBucket.getFiles.mockResolvedValue([[]]);

      const result = await fileStorageService.listFiles('uploads/test-user/');

      expect(result).toHaveLength(0);
    });
  });

  describe('cleanupOldFiles', () => {
    it('should clean up old files from GCS', async () => {
      const mockFiles = [
        {
          name: 'uploads/test-user/old-file.pdf',
          metadata: {
            timeCreated: new Date(Date.now() - 10 * 24 * 60 * 60 * 1000), // 10 days old
          },
        },
        {
          name: 'uploads/test-user/new-file.pdf',
          metadata: {
            timeCreated: new Date(), // today
          },
        },
      ];
      mockBucket.getFiles.mockResolvedValue([mockFiles]);
      mockBucket.deleteFiles.mockResolvedValue([{}]);

      const result = await fileStorageService.cleanupOldFiles('uploads/test-user/', 7);

      expect(result).toBe(1); // Only old file should be deleted
      expect(mockBucket.deleteFiles).toHaveBeenCalledWith(['uploads/test-user/old-file.pdf']);
    });
  });

  describe('getStorageStats', () => {
    it('should return storage statistics from GCS', async () => {
      const mockFiles = [
        {
          name: 'uploads/test-user/file1.pdf',
          size: 1024,
        },
        {
          name: 'uploads/test-user/file2.pdf',
          size: 2048,
        },
      ];
      mockBucket.getFiles.mockResolvedValue([mockFiles]);

      const result = await fileStorageService.getStorageStats('uploads/test-user/');

      expect(result.totalFiles).toBe(2);
      expect(result.totalSize).toBe(3072);
      expect(result.averageFileSize).toBe(1536);
    });
  });

  describe('generateSignedUrl', () => {
    it('should generate signed URL for file access', async () => {
      const filePath = 'uploads/test-user/test-file.pdf';
      const signedUrl = 'https://storage.googleapis.com/test-bucket/test-file.pdf?signature=abc123';
      mockFile.getSignedUrl.mockResolvedValue([signedUrl]);

      const result = await fileStorageService.generateSignedUrl(filePath, 60);

      expect(result).toBe(signedUrl);
      expect(mockFile.getSignedUrl).toHaveBeenCalledWith({
        action: 'read',
        expires: expect.any(Date),
      });
    });

    it('should return null on error', async () => {
      const filePath = 'uploads/test-user/test-file.pdf';
      mockFile.getSignedUrl.mockRejectedValue(new Error('URL generation failed'));

      const result = await fileStorageService.generateSignedUrl(filePath);

      expect(result).toBeNull();
    });
  });

  describe('copyFile', () => {
    it('should copy file within GCS bucket', async () => {
      const sourcePath = 'uploads/test-user/source.pdf';
      const destPath = 'uploads/test-user/copy.pdf';
      mockFile.copy.mockResolvedValue([{}]);

      const result = await fileStorageService.copyFile(sourcePath, destPath);

      expect(result).toBe(true);
      expect(mockFile.copy).toHaveBeenCalledWith(destPath);
    });

    it('should handle copy errors', async () => {
      const sourcePath = 'uploads/test-user/source.pdf';
      const destPath = 'uploads/test-user/copy.pdf';
      mockFile.copy.mockRejectedValue(new Error('Copy failed'));

      const result = await fileStorageService.copyFile(sourcePath, destPath);

      expect(result).toBe(false);
    });
  });

  describe('moveFile', () => {
    it('should move file within GCS bucket', async () => {
      const sourcePath = 'uploads/test-user/source.pdf';
      const destPath = 'uploads/test-user/moved.pdf';
      mockFile.move.mockResolvedValue([{}]);

      const result = await fileStorageService.moveFile(sourcePath, destPath);

      expect(result).toBe(true);
      expect(mockFile.move).toHaveBeenCalledWith(destPath);
    });

    it('should handle move errors', async () => {
      const sourcePath = 'uploads/test-user/source.pdf';
      const destPath = 'uploads/test-user/moved.pdf';
      mockFile.move.mockRejectedValue(new Error('Move failed'));

      const result = await fileStorageService.moveFile(sourcePath, destPath);

      expect(result).toBe(false);
    });
  });

  describe('testConnection', () => {
    it('should test GCS connection successfully', async () => {
      mockBucket.getFiles.mockResolvedValue([[]]);

      const result = await fileStorageService.testConnection();

      expect(result).toBe(true);
      expect(mockBucket.getFiles).toHaveBeenCalledWith({ maxResults: 1 });
    });

    it('should return false on connection failure', async () => {
      mockBucket.getFiles.mockRejectedValue(new Error('Connection failed'));

      const result = await fileStorageService.testConnection();

      expect(result).toBe(false);
    });
  });
}); 