import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { MinioService } from '@d8d/file-module';
import { Client } from 'minio';
import { logger } from '@d8d/shared-utils';

// Mock dependencies
vi.mock('minio');
vi.mock('@d8d/shared-utils');

// Mock process.env using vi.stubEnv for proper isolation
beforeEach(() => {
  vi.stubEnv('MINIO_HOST', 'localhost');
  vi.stubEnv('MINIO_PORT', '9000');
  vi.stubEnv('MINIO_USE_SSL', 'false');
  vi.stubEnv('MINIO_ACCESS_KEY', 'minioadmin');
  vi.stubEnv('MINIO_SECRET_KEY', 'minioadmin');
  vi.stubEnv('MINIO_BUCKET_NAME', 'test-bucket');
});

afterEach(() => {
  vi.unstubAllEnvs();
});

describe('MinIO Integration Tests', () => {
  let minioService: MinioService;
  let mockClient: Client;

  beforeEach(() => {
    mockClient = new Client({} as any);
    (Client as any).mockClear();
    (Client as any).mockImplementation(() => mockClient);

    // Create MinioService with mock client
    minioService = new MinioService();
  });

  afterEach(() => {
    vi.clearAllMocks();
  });

  describe('Bucket Operations', () => {
    it('should ensure bucket exists and set policy', async () => {
      // Mock bucket doesn't exist
      mockClient.bucketExists = vi.fn().mockResolvedValue(false);
      mockClient.makeBucket = vi.fn().mockResolvedValue(undefined);
      mockClient.setBucketPolicy = vi.fn().mockResolvedValue(undefined);

      const result = await minioService.ensureBucketExists();

      expect(result).toBe(true);
      expect(mockClient.bucketExists).toHaveBeenCalledWith('test-bucket');
      expect(mockClient.makeBucket).toHaveBeenCalledWith('test-bucket');
      expect(mockClient.setBucketPolicy).toHaveBeenCalled();
      expect(logger.db).toHaveBeenCalledWith('Created new bucket: test-bucket');
    });

    it('should handle existing bucket', async () => {
      mockClient.bucketExists = vi.fn().mockResolvedValue(true);

      const result = await minioService.ensureBucketExists();

      expect(result).toBe(true);
      expect(mockClient.bucketExists).toHaveBeenCalledWith('test-bucket');
      expect(mockClient.makeBucket).not.toHaveBeenCalled();
      expect(mockClient.setBucketPolicy).not.toHaveBeenCalled();
    });
  });

  describe('File Operations', () => {
    it('should upload and download file successfully', async () => {
      const testContent = Buffer.from('Hello, MinIO!');
      const mockUrl = 'http://localhost:9000/test-bucket/test.txt';

      // Mock bucket operations
      mockClient.bucketExists = vi.fn().mockResolvedValue(true);
      mockClient.putObject = vi.fn().mockResolvedValue(undefined);
      mockClient.statObject = vi.fn().mockResolvedValue({ size: testContent.length } as any);
      mockClient.getObject = vi.fn().mockReturnValue({
        on: (event: string, callback: Function) => {
          if (event === 'data') callback(testContent);
          if (event === 'end') callback();
        }
      } as any);

      // Upload file
      const uploadUrl = await minioService.createObject('test-bucket', 'test.txt', testContent, 'text/plain');
      expect(uploadUrl).toBe(mockUrl);
      expect(mockClient.putObject).toHaveBeenCalledWith(
        'test-bucket',
        'test.txt',
        testContent,
        testContent.length,
        { 'Content-Type': 'text/plain' }
      );

      // Check file exists
      const exists = await minioService.objectExists('test-bucket', 'test.txt');
      expect(exists).toBe(true);
      expect(mockClient.statObject).toHaveBeenCalledWith('test-bucket', 'test.txt');
    });

    it('should handle file not found', async () => {
      const notFoundError = new Error('Object not found');
      notFoundError.message = 'not found';
      mockClient.statObject = vi.fn().mockRejectedValue(notFoundError);

      const exists = await minioService.objectExists('test-bucket', 'nonexistent.txt');
      expect(exists).toBe(false);
    });

    it('should delete file successfully', async () => {
      mockClient.removeObject = vi.fn().mockResolvedValue(undefined);

      await minioService.deleteObject('test-bucket', 'test.txt');

      expect(mockClient.removeObject).toHaveBeenCalledWith('test-bucket', 'test.txt');
      expect(logger.db).toHaveBeenCalledWith('Deleted object: test-bucket/test.txt');
    });
  });

  describe('Presigned URL Operations', () => {
    it('should generate presigned URLs correctly', async () => {
      const mockPresignedUrl = 'https://minio.example.com/presigned-url';
      mockClient.presignedGetObject = vi.fn().mockResolvedValue(mockPresignedUrl);

      // Test regular presigned URL
      const url = await minioService.getPresignedFileUrl('test-bucket', 'file.txt', 3600);
      expect(url).toBe(mockPresignedUrl);
      expect(mockClient.presignedGetObject).toHaveBeenCalledWith('test-bucket', 'file.txt', 3600);

      // Test download URL with content disposition
      const downloadUrl = await minioService.getPresignedFileDownloadUrl(
        'test-bucket',
        'file.txt',
        '测试文件.txt',
        1800
      );
      expect(downloadUrl).toBe(mockPresignedUrl);
      expect(mockClient.presignedGetObject).toHaveBeenCalledWith(
        'test-bucket',
        'file.txt',
        1800,
        {
          'response-content-disposition': 'attachment; filename="%E6%B5%8B%E8%AF%95%E6%96%87%E4%BB%B6.txt"',
          'response-content-type': 'application/octet-stream'
        }
      );
    });
  });

  describe('Multipart Upload Operations', () => {
    it('should handle multipart upload workflow', async () => {
      const mockUploadId = 'upload-123';
      const mockPartUrls = ['url1', 'url2', 'url3'];
      const mockStat = { size: 3072 };

      // Mock multipart operations
      mockClient.initiateNewMultipartUpload = vi.fn().mockResolvedValue(mockUploadId);
      mockClient.presignedUrl = vi.fn()
        .mockResolvedValueOnce('url1')
        .mockResolvedValueOnce('url2')
        .mockResolvedValueOnce('url3');
      mockClient.completeMultipartUpload = vi.fn().mockResolvedValue(undefined);
      mockClient.statObject = vi.fn().mockResolvedValue(mockStat as any);

      // Create multipart upload
      const uploadId = await minioService.createMultipartUpload('test-bucket', 'large-file.zip');
      expect(uploadId).toBe(mockUploadId);
      expect(mockClient.initiateNewMultipartUpload).toHaveBeenCalledWith(
        'test-bucket',
        'large-file.zip',
        {}
      );

      // Generate part URLs
      const partUrls = await minioService.generateMultipartUploadUrls(
        'test-bucket',
        'large-file.zip',
        mockUploadId,
        3
      );
      expect(partUrls).toEqual(mockPartUrls);
      expect(mockClient.presignedUrl).toHaveBeenCalledTimes(3);

      // Complete multipart upload
      const parts = [
        { ETag: 'etag1', PartNumber: 1 },
        { ETag: 'etag2', PartNumber: 2 },
        { ETag: 'etag3', PartNumber: 3 }
      ];
      const result = await minioService.completeMultipartUpload(
        'test-bucket',
        'large-file.zip',
        mockUploadId,
        parts
      );
      expect(result).toEqual({ size: 3072 });
      expect(mockClient.completeMultipartUpload).toHaveBeenCalledWith(
        'test-bucket',
        'large-file.zip',
        mockUploadId,
        [{ part: 1, etag: 'etag1' }, { part: 2, etag: 'etag2' }, { part: 3, etag: 'etag3' }]
      );
    });
  });

  describe('Error Handling', () => {
    it('should handle MinIO connection errors', async () => {
      const connectionError = new Error('Connection refused');
      mockClient.bucketExists = vi.fn().mockRejectedValue(connectionError);

      await expect(minioService.ensureBucketExists()).rejects.toThrow(connectionError);
      expect(logger.error).toHaveBeenCalledWith(
        'Failed to ensure bucket exists: test-bucket',
        connectionError
      );
    });

    it('should handle file operation errors', async () => {
      const operationError = new Error('Operation failed');

      // 确保桶存在成功
      mockClient.bucketExists = vi.fn().mockResolvedValue(true);
      // 但文件操作失败
      mockClient.putObject = vi.fn().mockRejectedValue(operationError);

      await expect(minioService.createObject(
        'test-bucket',
        'test.txt',
        Buffer.from('test'),
        'text/plain'
      )).rejects.toThrow(operationError);
      expect(logger.error).toHaveBeenCalledWith(
        'Failed to create object test-bucket/test.txt:',
        operationError
      );
    });

    it('should handle permission errors gracefully', async () => {
      const permissionError = new Error('Permission denied');
      mockClient.statObject = vi.fn().mockRejectedValue(permissionError);

      await expect(minioService.objectExists('test-bucket', 'file.txt')).rejects.toThrow(permissionError);
      expect(logger.error).toHaveBeenCalledWith(
        'Error checking existence of object test-bucket/file.txt:',
        permissionError
      );
    });
  });

  describe('Configuration Validation', () => {
    it('should validate MinIO configuration', () => {
      expect(minioService.bucketName).toBe('test-bucket');

      // Test URL generation with different configurations
      const url = minioService.getFileUrl('test-bucket', 'file.txt');
      expect(url).toBe('http://localhost:9000/test-bucket/file.txt');
    });

    it('should handle SSL configuration', async () => {
      // Create new instance with SSL
      vi.stubEnv('MINIO_USE_SSL', 'true');
      vi.stubEnv('MINIO_PORT', '443');

      const sslService = new MinioService();
      const url = sslService.getFileUrl('test-bucket', 'file.txt');
      expect(url).toBe('https://localhost:443/test-bucket/file.txt');
    });
  });

  describe('Performance Testing', () => {
    it('should handle concurrent operations', async () => {
      mockClient.presignedGetObject = vi.fn().mockResolvedValue('https://minio.example.com/file');

      // Test concurrent URL generation with smaller concurrency
      const promises = Array(5).fill(0).map((_, i) =>
        minioService.getPresignedFileUrl('test-bucket', `file${i}.txt`)
      );

      const results = await Promise.all(promises);
      expect(results).toHaveLength(5);
      expect(results.every(url => url === 'https://minio.example.com/file')).toBe(true);
    });

    it('should handle large file operations', async () => {
      // Use smaller buffer size to avoid memory issues
      const largeBuffer = Buffer.alloc(1 * 1024 * 1024); // 1MB instead of 10MB
      mockClient.bucketExists = vi.fn().mockResolvedValue(true);
      mockClient.putObject = vi.fn().mockResolvedValue({ etag: 'etag123', versionId: null });

      await minioService.createObject('test-bucket', 'large-file.bin', largeBuffer, 'application/octet-stream');

      expect(mockClient.putObject).toHaveBeenCalledWith(
        'test-bucket',
        'large-file.bin',
        largeBuffer,
        largeBuffer.length,
        { 'Content-Type': 'application/octet-stream' }
      );
    });
  });
});