import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
import { testClient } from 'hono/testing';
import { FileService } from '../../src/modules/files/file.service';
import { authMiddleware } from '../../src/middleware/auth.middleware';
import { fileApiRoutes } from '../../src/api';
import { ConcreteCrudService } from '../../src/utils/concrete-crud.service';

vi.mock('../../src/modules/files/file.service');
vi.mock('../../src/middleware/auth.middleware');
vi.mock('../../src/data-source');
vi.mock('../../src/utils/concrete-crud.service');

describe('File API Integration Tests', () => {
  let client: ReturnType<typeof testClient<typeof fileApiRoutes>>['api']['v1'];
  const user1 = {
    id: 1,
    username: 'testuser',
    password: 'password123',
    phone: null,
    email: null,
    nickname: null,
    name: null,
    avatarFileId: null,
    avatarFile: null,
    isDisabled: 0,
    isDeleted: 0,
    registrationSource: 'web',
    roles: [],
    createdAt: new Date(),
    updatedAt: new Date()
  };
  const user1Response = {
    ...user1,
    createdAt: (user1.createdAt).toISOString(),
    updatedAt: (user1.updatedAt).toISOString()
  }

  beforeEach(async () => {
    vi.clearAllMocks();

    // Mock auth middleware to bypass authentication
    vi.mocked(authMiddleware).mockImplementation(async (c: any, next: any) => {
      const authHeader = c.req.header('Authorization');
      if (!authHeader) {
        return c.json({ message: 'Authorization header missing' }, 401);
      }
      c.set('user', user1)
      await next();
    });



    client = testClient(fileApiRoutes).api.v1;
  });

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

  describe('POST /api/v1/files/upload-policy', () => {
    it('should generate upload policy successfully', async () => {
      const mockFileData = {
        name: 'test.txt',
        type: 'text/plain',
        size: 1024,
        path: '/uploads/test.txt',
        description: 'Test file',
        uploadUserId: 1
      };

      const mockResponse = {
        file: {
          id: 1,
          ...mockFileData,
          path: '1/test-uuid-123-test.txt',
          uploadTime: (new Date()).toISOString(),
          createdAt: (new Date()).toISOString(),
          updatedAt: (new Date()).toISOString(),
          fullUrl: 'https://minio.example.com/d8dai/1/test-uuid-123-test.txt',
          uploadUser: user1Response,
          lastUpdated: null
        },
        uploadPolicy: {
          'x-amz-algorithm': 'AWS4-HMAC-SHA256',
          'x-amz-credential': 'test-credential',
          'x-amz-date': '20250101T120000Z',
          'x-amz-security-token': 'test-token',
          policy: 'test-policy',
          'x-amz-signature': 'test-signature',
          host: 'https://minio.example.com',
          key: '1/test-uuid-123-test.txt',
          bucket: 'd8dai'
        }
      };


      const mockCreateFile = vi.fn().mockResolvedValue(mockResponse);
      vi.mocked(FileService).mockImplementation(() => ({
        createFile: mockCreateFile
      } as unknown as FileService));
      const response = await client.files['upload-policy'].$post({
        json: mockFileData
      },
      {
        headers: {
          'Authorization': 'Bearer test-token'
        }
      });

      if (response.status !== 200) {
        const error = await response.json();
        console.debug('Error response:', JSON.stringify(error, null, 2));
        console.debug('Response status:', response.status);
      }
      expect(response.status).toBe(200);
      const result = await response.json();
      expect(result).toEqual(mockResponse);
      expect(mockCreateFile).toHaveBeenCalledWith({
        ...mockFileData,
        uploadTime: expect.any(Date),
        uploadUserId: 1
      });
    });

    it('should return 400 for invalid request data', async () => {
      const invalidData = {
        name: '', // Empty name
        type: 'text/plain'
      };

      const response = await client.files['upload-policy'].$post({
        json: invalidData as any
      },
      {
        headers: {
          'Authorization': 'Bearer test-token'
        }
      });

      expect(response.status).toBe(400);
    });

    it('should handle service errors gracefully', async () => {
      const mockFileData = {
        name: 'test.txt',
        type: 'text/plain',
        path: '/uploads/test.txt',
        uploadUserId: 1
      };

      const mockCreateFile = vi.fn().mockRejectedValue(new Error('Service error'));
      vi.mocked(FileService).mockImplementation(() => ({
        createFile: mockCreateFile
      } as unknown as FileService));

      const response = await client.files['upload-policy'].$post({
        json: mockFileData as any
      },
      {
        headers: {
          'Authorization': 'Bearer test-token'
        }
      });

      expect(response.status).toBe(500);
    });
  });

  describe('GET /api/v1/files/{id}/url', () => {
    it('should generate file access URL successfully', async () => {
      const mockUrl = 'https://minio.example.com/presigned-url';
      const mockGetFileUrl = vi.fn().mockResolvedValue(mockUrl);
      vi.mocked(FileService).mockImplementation(() => ({
        getFileUrl: mockGetFileUrl
      } as unknown as FileService));

      const response = await client.files[':id']['url'].$get({
        param: { id: 1 }
      },
      {
        headers: {
          'Authorization': 'Bearer test-token'
        }
      });

      expect(response.status).toBe(200);
      const result = await response.json();
      expect(result).toEqual({ url: mockUrl });
    });

    it('should return 404 when file not found', async () => {
      const mockGetFileUrl = vi.fn().mockRejectedValue(new Error('文件不存在'));
      vi.mocked(FileService).mockImplementation(() => ({
        getFileUrl: mockGetFileUrl
      } as unknown as FileService));

      const response = await client.files[':id']['url'].$get({
        param: { id: 999 }
      },
      {
        headers: {
          'Authorization': 'Bearer test-token'
        }
      });
      expect(response.status).toBe(404);
    });
  });

  describe('GET /api/v1/files/{id}/download', () => {
    it('should generate file download URL successfully', async () => {
      const mockDownloadInfo = {
        url: 'https://minio.example.com/download-url',
        filename: 'test.txt'
      };
      const mockGetFileDownloadUrl = vi.fn().mockResolvedValue(mockDownloadInfo);
      vi.mocked(FileService).mockImplementation(() => ({
        getFileDownloadUrl: mockGetFileDownloadUrl
      } as unknown as FileService));

      const response = await client.files[':id']['download'].$get({
        param: { id: 1 }
      },
      {
        headers: {
          'Authorization': 'Bearer test-token'
        }
      });

      expect(response.status).toBe(200);
      const result = await response.json();
      expect(result).toEqual(mockDownloadInfo);
      expect(mockGetFileDownloadUrl).toHaveBeenCalledWith(1);
    });

    it('should return 404 when file not found for download', async () => {
      const mockGetFileDownloadUrl = vi.fn().mockRejectedValue(new Error('文件不存在'));
      vi.mocked(FileService).mockImplementation(() => ({
        getFileDownloadUrl: mockGetFileDownloadUrl
      } as unknown as FileService));

      const response = await client.files[':id']['download'].$get({
        param: { id: 999 }
      },
      {
        headers: {
          'Authorization': 'Bearer test-token'
        }
      });

      expect(response.status).toBe(404);
    });
  });

  describe('DELETE /api/v1/files/{id}', () => {
    it('should delete file successfully', async () => {
      const mockDeleteFile = vi.fn().mockResolvedValue(true);
      vi.mocked(FileService).mockImplementation(() => ({
        deleteFile: mockDeleteFile
      } as unknown as FileService));

      const response = await client.files[':id'].$delete({
        param: { id: 1 }
      },
      {
        headers: {
          'Authorization': 'Bearer test-token'
        }
      });

      expect(response.status).toBe(200);
      const result = await response.json();
      expect(result).toEqual({ success: true, message: '文件删除成功' });
      expect(mockDeleteFile).toHaveBeenCalledWith(1);
    });

    it('should return 404 when file not found for deletion', async () => {
      const mockDeleteFile = vi.fn().mockRejectedValue(new Error('文件不存在'));
      vi.mocked(FileService).mockImplementation(() => ({
        deleteFile: mockDeleteFile
      } as unknown as FileService));

      const response = await client.files[':id'].$delete({
        param: { id: 999 }
      },
      {
        headers: {
          'Authorization': 'Bearer test-token'
        }
      });

      expect(response.status).toBe(404);
    });

    it('should handle deletion errors', async () => {
      const mockDeleteFile = vi.fn().mockRejectedValue(new Error('删除失败'));
      vi.mocked(FileService).mockImplementation(() => ({
        deleteFile: mockDeleteFile
      } as unknown as FileService));

      const response = await client.files[':id'].$delete({
        param: { id: 1 }
      },
      {
        headers: {
          'Authorization': 'Bearer test-token'
        }
      });

      expect(response.status).toBe(500);
    });
  });

  describe('POST /api/v1/files/multipart-policy', () => {
    it('should generate multipart upload policy successfully', async () => {
      const mockRequestData = {
        fileKey: 'large-file.zip',
        totalSize: 1024 * 1024 * 100, // 100MB
        partSize: 1024 * 1024 * 20, // 20MB
        name: 'large-file.zip',
        type: 'application/zip',
        uploadUserId: 1
      };

      const mockServiceResponse = {
        file: {
          id: 1,
          name: 'large-file.zip',
          type: 'application/zip',
          size: 104857600,
          uploadUserId: 1,
          path: '1/test-uuid-123-large-file.zip',
          description: null,
          uploadTime: new Date(),
          lastUpdated: null,
          createdAt: new Date(),
          updatedAt: new Date(),
          fullUrl: Promise.resolve('https://minio.example.com/d8dai/1/test-uuid-123-large-file.zip')
        },
        uploadId: 'upload-123',
        uploadUrls: ['url1', 'url2', 'url3', 'url4', 'url5'],
        bucket: 'd8dai',
        key: '1/test-uuid-123-large-file.zip'
      };

      const mockCreateMultipartUploadPolicy = vi.fn().mockResolvedValue(mockServiceResponse);
      vi.mocked(FileService).mockImplementation(() => ({
        createMultipartUploadPolicy: mockCreateMultipartUploadPolicy
      } as unknown as FileService));

      const response = await client.files['multipart-policy'].$post({
        json: mockRequestData
      },
      {
        headers: {
          'Authorization': 'Bearer test-token'
        }
      });

      expect(response.status).toBe(200);
      const result = await response.json();
      expect(result).toEqual({
        uploadId: 'upload-123',
        bucket: 'd8dai',
        key: '1/test-uuid-123-large-file.zip',
        host: 'http://undefined:undefined',
        partUrls: ['url1', 'url2', 'url3', 'url4', 'url5']
      });
      expect(mockCreateMultipartUploadPolicy).toHaveBeenCalledWith(
        {
          fileKey: 'large-file.zip',
          totalSize: 104857600,
          partSize: 20971520,
          name: 'large-file.zip',
          type: 'application/zip',
          uploadUserId: 1
        },
        5
      );
    });

    it('should validate multipart policy request data', async () => {
      const invalidData = {
        name: 'test.zip'
        // Missing required fields: fileKey, totalSize, partSize
      };

      const response = await client.files['multipart-policy'].$post({
        json: invalidData as any
      },
      {
        headers: {
          'Authorization': 'Bearer test-token'
        }
      });

      expect(response.status).toBe(400);
    });
  });

  describe('POST /api/v1/files/multipart-complete', () => {
    it('should complete multipart upload successfully', async () => {
      const mockCompleteData = {
        uploadId: 'upload-123',
        bucket: 'd8dai',
        key: '1/test-file.zip',
        parts: [
          { partNumber: 1, etag: 'etag1' },
          { partNumber: 2, etag: 'etag2' }
        ]
      };

      const mockResponse = {
        fileId: 1,
        url: 'https://minio.example.com/file.zip',
        key: '1/test-file.zip',
        size: 2048,
        host: 'http://undefined:undefined',
        bucket: 'd8dai'
      };

      const mockCompleteMultipartUpload = vi.fn().mockResolvedValue(mockResponse);
      vi.mocked(FileService).mockImplementation(() => ({
        completeMultipartUpload: mockCompleteMultipartUpload
      } as unknown as FileService));

      const response = await client.files['multipart-complete'].$post({
        json: mockCompleteData
      },
      {
        headers: {
          'Authorization': 'Bearer test-token'
        }
      });

      expect(response.status).toBe(200);
      const result = await response.json();
      expect(result).toEqual(mockResponse);
      expect(mockCompleteMultipartUpload).toHaveBeenCalledWith(mockCompleteData);
    });

    it('should validate complete multipart request data', async () => {
      const invalidData = {
        uploadId: 'upload-123',
        // Missing required fields: bucket, key, parts
      };

      const response = await client.files['multipart-complete'].$post({
        json: invalidData as any
      },
      {
        headers: {
          'Authorization': 'Bearer test-token'
        }
      });

      expect(response.status).toBe(400);
    });

    it('should handle completion errors', async () => {
      const completeData = {
        uploadId: 'upload-123',
        bucket: 'd8dai',
        key: '1/test-file.zip',
        parts: [{ partNumber: 1, etag: 'etag1' }]
      };

      const mockCompleteMultipartUpload = vi.fn().mockRejectedValue(new Error('Completion failed'));
      vi.mocked(FileService).mockImplementation(() => ({
        completeMultipartUpload: mockCompleteMultipartUpload
      } as unknown as FileService));

      const response = await client.files['multipart-complete'].$post({
        json: completeData
      },
      {
        headers: {
          'Authorization': 'Bearer test-token'
        }
      });

      expect(response.status).toBe(500);
    });
  });

  describe('CRUD Operations', () => {
    it('should list files successfully', async () => {
      const mockFiles = [
        {
          id: 1,
          name: 'file1.txt',
          type: 'text/plain',
          size: 1024,
          path: '/uploads/file1.txt',
          fullUrl: 'https://minio.example.com/d8dai/uploads/file1.txt',
          description: null,
          uploadUserId: 1,
          uploadUser: user1Response,
          uploadTime: new Date(),
          lastUpdated: null,
          createdAt: new Date(),
          updatedAt: new Date()
        },
        {
          id: 2,
          name: 'file2.txt',
          type: 'text/plain',
          size: 2048,
          path: '/uploads/file2.txt',
          fullUrl: 'https://minio.example.com/d8dai/uploads/file2.txt',
          description: null,
          uploadUserId: 1,
          uploadUser: user1Response,
          uploadTime: new Date(),
          lastUpdated: null,
          createdAt: new Date(),
          updatedAt: new Date()
        }
      ];

      // 设置ConcreteCrudService的mock返回数据
      vi.mocked(ConcreteCrudService).mockImplementation(() => ({
        getList: vi.fn().mockResolvedValue([mockFiles, mockFiles.length])
      } as unknown as ConcreteCrudService<any>));

      const response = await client.files.$get({
        query: {}
      },
      {
        headers: {
          'Authorization': 'Bearer test-token'
        }
      });

      if (response.status !== 200) {
        const error = await response.json();
        console.debug('Error response:', JSON.stringify(error, null, 2));
        console.debug('Response status:', response.status);
      }
      expect(response.status).toBe(200);
      const result = await response.json();
      expect(result).toEqual({
        data: mockFiles.map(file => ({
          ...file,
          createdAt: file.createdAt.toISOString(),
          updatedAt: file.updatedAt.toISOString(),
          uploadTime: file.uploadTime.toISOString()
        })),
        pagination: {
          current: 1,
          pageSize: 10,
          total: mockFiles.length
        }
      });
    });

    it('should get file by ID successfully', async () => {
      const mockFile = {
        id: 1,
        name: 'file.txt',
        type: 'text/plain',
        size: 1024,
        path: '/uploads/file.txt',
        fullUrl: 'https://minio.example.com/d8dai/uploads/file.txt',
        description: null,
        uploadUserId: 1,
        uploadUser: user1Response,
        uploadTime: new Date(),
        lastUpdated: null,
        createdAt: new Date(),
        updatedAt: new Date()
      };

      // 设置ConcreteCrudService的mock返回数据
      vi.mocked(ConcreteCrudService).mockImplementation(() => ({
        getById: vi.fn().mockResolvedValue(mockFile)
      } as unknown as ConcreteCrudService<any>));

      const response = await client.files[':id'].$get({
        param: { id: 1 }
      },
      {
        headers: {
          'Authorization': 'Bearer test-token'
        }
      });

      if (response.status !== 200) {
        const error = await response.json();
        console.debug('Error response:', JSON.stringify(error, null, 2));
        console.debug('Response status:', response.status);
      }
      expect(response.status).toBe(200);
      const result = await response.json();
      expect(result).toEqual({
        ...mockFile,
        createdAt: mockFile.createdAt.toISOString(),
        updatedAt: mockFile.updatedAt.toISOString(),
        uploadTime: mockFile.uploadTime.toISOString()
      });
    });

    it('should search files successfully', async () => {
      const mockFiles = [
        {
          id: 1,
          name: 'document.pdf',
          type: 'application/pdf',
          size: 1024,
          path: '/uploads/document.pdf',
          fullUrl: 'https://minio.example.com/d8dai/uploads/document.pdf',
          description: null,
          uploadUserId: 1,
          uploadUser: user1Response,
          uploadTime: new Date(),
          lastUpdated: null,
          createdAt: new Date(),
          updatedAt: new Date()
        }
      ];

      // 设置ConcreteCrudService的mock返回数据
      vi.mocked(ConcreteCrudService).mockImplementation(() => ({
        getList: vi.fn().mockResolvedValue([mockFiles, mockFiles.length])
      } as unknown as ConcreteCrudService<any>));

      const response = await client.files.$get({
        query: { keyword: 'document' }
      },
      {
        headers: {
          'Authorization': 'Bearer test-token'
        }
      });

      expect(response.status).toBe(200);
      const result = await response.json();
      expect(result).toEqual({
        data: mockFiles.map(file => ({
          ...file,
          createdAt: file.createdAt.toISOString(),
          updatedAt: file.updatedAt.toISOString(),
          uploadTime: file.uploadTime.toISOString()
        })),
        pagination: {
          current: 1,
          pageSize: 10,
          total: mockFiles.length
        }
      });
      expect(vi.mocked(ConcreteCrudService).mock.results[0].value.getList).toHaveBeenCalledWith(1, 10, 'document', ['name', 'type', 'description'], undefined, ['uploadUser'], { id: 'DESC' }, undefined);
    });
  });
});