import { describe, it, expect, beforeEach, vi } from 'vitest';
import { documentService } from '../documentService';
import { documentRepository } from '../../repositories/documentRepository';
import { storageAdapter } from '../../adapters/storage';

// Mock de repositories y adapters
vi.mock('../../repositories/documentRepository', () => ({
  documentRepository: {
    findMany: vi.fn(),
    findById: vi.fn(),
    findAll: vi.fn(),
    create: vi.fn(),
    update: vi.fn(),
    delete: vi.fn(),
    deleteMany: vi.fn(),
  },
}));

vi.mock('../../adapters/storage', () => ({
  storageAdapter: {
    save: vi.fn(),
    delete: vi.fn(),
    get: vi.fn(),
    exists: vi.fn(),
  },
}));

describe('DocumentService', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  describe('getDocuments', () => {
    it('debería retornar documentos con filtros para usuario normal', async () => {
      const mockDocuments = [
        {
          id: '1',
          fileName: 'test.pdf',
          fileUrl: 'abc123.pdf',
          extractedData: { nombre: 'Test' },
          ownerId: 'user1',
          categoryId: null,
          createdAt: new Date(),
          updatedAt: new Date(),
          owner: { id: 'user1', name: 'User', username: 'user1' },
        },
      ];

      vi.mocked(documentRepository.findMany).mockResolvedValue(mockDocuments as any);

      const result = await documentService.getDocuments({
        userId: 'user1',
        role: 'USER',
      });

      expect(result).toHaveLength(1);
      expect(result[0].fileUrl).toBe('/api/files/abc123.pdf');
      expect(documentRepository.findMany).toHaveBeenCalledWith({
        userId: 'user1',
        role: 'USER',
      });
    });

    it('debería retornar todos los documentos para ADMIN', async () => {
      const mockDocuments = [
        {
          id: '1',
          fileName: 'test1.pdf',
          fileUrl: 'abc123.pdf',
          extractedData: {},
          ownerId: 'user1',
          categoryId: null,
          createdAt: new Date(),
          updatedAt: new Date(),
          owner: { id: 'user1', name: 'User', username: 'user1' },
        },
        {
          id: '2',
          fileName: 'test2.pdf',
          fileUrl: 'def456.pdf',
          extractedData: {},
          ownerId: 'user2',
          categoryId: null,
          createdAt: new Date(),
          updatedAt: new Date(),
          owner: { id: 'user2', name: 'User2', username: 'user2' },
        },
      ];

      vi.mocked(documentRepository.findMany).mockResolvedValue(mockDocuments as any);

      const result = await documentService.getDocuments({
        userId: 'admin1',
        role: 'ADMIN',
      });

      expect(result).toHaveLength(2);
      expect(documentRepository.findMany).toHaveBeenCalledWith({
        userId: 'admin1',
        role: 'ADMIN',
      });
    });
  });

  describe('createDocument', () => {
    it('debería crear un documento exitosamente', async () => {
      const mockFileData = {
        fileName: 'test.pdf',
        fileData: Buffer.from('test').toString('base64'),
        mimeType: 'application/pdf',
        extractedData: { nombre: 'Test', total: '100' },
        ownerId: 'user1',
        categoryId: 'cat1',
      };

      vi.mocked(storageAdapter.save).mockResolvedValue({ name: 'unique-name.pdf' });
      vi.mocked(documentRepository.create).mockResolvedValue({
        id: 'doc1',
        ...mockFileData,
        fileUrl: 'unique-name.pdf',
        createdAt: new Date(),
        updatedAt: new Date(),
        owner: { id: 'user1', name: 'User', username: 'user1' },
      } as any);

      const result = await documentService.createDocument(mockFileData);

      expect(result.id).toBe('doc1');
      expect(storageAdapter.save).toHaveBeenCalled();
      expect(documentRepository.create).toHaveBeenCalledWith({
        fileName: 'test.pdf',
        fileUrl: 'unique-name.pdf',
        extractedData: mockFileData.extractedData,
        owner: { connect: { id: 'user1' } },
        category: { connect: { id: 'cat1' } },
      });
    });

    it('debería crear documento sin categoría', async () => {
      const mockFileData = {
        fileName: 'test.pdf',
        fileData: Buffer.from('test').toString('base64'),
        mimeType: 'application/pdf',
        extractedData: {},
        ownerId: 'user1',
      };

      vi.mocked(storageAdapter.save).mockResolvedValue({ name: 'unique-name.pdf' });
      vi.mocked(documentRepository.create).mockResolvedValue({
        id: 'doc1',
        ...mockFileData,
        fileUrl: 'unique-name.pdf',
        createdAt: new Date(),
        updatedAt: new Date(),
        owner: { id: 'user1', name: 'User', username: 'user1' },
      } as any);

      const result = await documentService.createDocument(mockFileData);

      expect(result.id).toBe('doc1');
      expect(documentRepository.create).toHaveBeenCalledWith(
        expect.objectContaining({
          category: undefined,
        })
      );
    });
  });

  describe('updateDocument', () => {
    it('debería actualizar documento si es el propietario', async () => {
      const mockDocument = {
        id: 'doc1',
        fileName: 'test.pdf',
        fileUrl: 'abc123.pdf',
        extractedData: { old: 'data' },
        ownerId: 'user1',
        categoryId: null,
        createdAt: new Date(),
        updatedAt: new Date(),
        owner: { id: 'user1', name: 'User', username: 'user1' },
      };

      vi.mocked(documentRepository.findById).mockResolvedValue(mockDocument as any);
      vi.mocked(documentRepository.update).mockResolvedValue(mockDocument as any);

      await documentService.updateDocument(
        'doc1',
        { nombre: 'Nuevo Nombre' },
        'user1',
        'USER'
      );

      expect(documentRepository.update).toHaveBeenCalledWith('doc1', {
        extractedData: { nombre: 'Nuevo Nombre' },
      });
    });

    it('debería actualizar documento si es ADMIN', async () => {
      const mockDocument = {
        id: 'doc1',
        ownerId: 'user2',
        fileName: 'test.pdf',
        fileUrl: 'abc123.pdf',
        extractedData: {},
        categoryId: null,
        createdAt: new Date(),
        updatedAt: new Date(),
        owner: { id: 'user2', name: 'User', username: 'user2' },
      };

      vi.mocked(documentRepository.findById).mockResolvedValue(mockDocument as any);
      vi.mocked(documentRepository.update).mockResolvedValue(mockDocument as any);

      await documentService.updateDocument(
        'doc1',
        { nombre: 'Nuevo' },
        'admin1',
        'ADMIN'
      );

      expect(documentRepository.update).toHaveBeenCalled();
    });

    it('debería lanzar error si no es propietario ni admin', async () => {
      const mockDocument = {
        id: 'doc1',
        ownerId: 'user2',
        fileName: 'test.pdf',
        fileUrl: 'abc123.pdf',
        extractedData: {},
        categoryId: null,
        createdAt: new Date(),
        updatedAt: new Date(),
        owner: { id: 'user2', name: 'User', username: 'user2' },
      };

      vi.mocked(documentRepository.findById).mockResolvedValue(mockDocument as any);

      await expect(
        documentService.updateDocument('doc1', {}, 'user1', 'USER')
      ).rejects.toThrow('No tienes permisos para actualizar este documento');
    });

    it('debería lanzar error si el documento no existe', async () => {
      vi.mocked(documentRepository.findById).mockResolvedValue(null);

      await expect(
        documentService.updateDocument('doc1', {}, 'user1', 'USER')
      ).rejects.toThrow('Documento no encontrado');
    });
  });

  describe('deleteDocument', () => {
    it('debería eliminar documento si es el propietario', async () => {
      const mockDocument = {
        id: 'doc1',
        ownerId: 'user1',
        fileName: 'test.pdf',
        fileUrl: 'abc123.pdf',
        extractedData: {},
        categoryId: null,
        createdAt: new Date(),
        updatedAt: new Date(),
        owner: { id: 'user1', name: 'User', username: 'user1' },
      };

      vi.mocked(documentRepository.findById).mockResolvedValue(mockDocument as any);
      vi.mocked(storageAdapter.delete).mockResolvedValue();
      vi.mocked(documentRepository.delete).mockResolvedValue(mockDocument as any);

      await documentService.deleteDocument('doc1', 'user1', 'USER');

      expect(storageAdapter.delete).toHaveBeenCalledWith('abc123.pdf');
      expect(documentRepository.delete).toHaveBeenCalledWith('doc1');
    });

    it('debería eliminar documento si es ADMIN', async () => {
      const mockDocument = {
        id: 'doc1',
        ownerId: 'user2',
        fileName: 'test.pdf',
        fileUrl: 'abc123.pdf',
        extractedData: {},
        categoryId: null,
        createdAt: new Date(),
        updatedAt: new Date(),
        owner: { id: 'user2', name: 'User', username: 'user2' },
      };

      vi.mocked(documentRepository.findById).mockResolvedValue(mockDocument as any);
      vi.mocked(storageAdapter.delete).mockResolvedValue();
      vi.mocked(documentRepository.delete).mockResolvedValue(mockDocument as any);

      await documentService.deleteDocument('doc1', 'admin1', 'ADMIN');

      expect(storageAdapter.delete).toHaveBeenCalled();
      expect(documentRepository.delete).toHaveBeenCalled();
    });

    it('debería lanzar error si no tiene permisos', async () => {
      const mockDocument = {
        id: 'doc1',
        ownerId: 'user2',
        fileName: 'test.pdf',
        fileUrl: 'abc123.pdf',
        extractedData: {},
        categoryId: null,
        createdAt: new Date(),
        updatedAt: new Date(),
        owner: { id: 'user2', name: 'User', username: 'user2' },
      };

      vi.mocked(documentRepository.findById).mockResolvedValue(mockDocument as any);

      await expect(
        documentService.deleteDocument('doc1', 'user1', 'USER')
      ).rejects.toThrow('No tienes permisos para eliminar este documento');
    });
  });

  describe('deleteAllDocuments', () => {
    it('debería eliminar todos los documentos en desarrollo', async () => {
      const originalEnv = process.env.NODE_ENV;
      process.env.NODE_ENV = 'development';

      const mockDocuments = [
        { id: '1', fileUrl: 'file1.pdf' },
        { id: '2', fileUrl: 'file2.pdf' },
      ];

      vi.mocked(documentRepository.findAll).mockResolvedValue(mockDocuments as any);
      vi.mocked(storageAdapter.delete).mockResolvedValue();
      vi.mocked(documentRepository.deleteMany).mockResolvedValue({ count: 2 } as any);

      const result = await documentService.deleteAllDocuments();

      expect(result).toBe(2);
      expect(storageAdapter.delete).toHaveBeenCalledTimes(2);

      process.env.NODE_ENV = originalEnv;
    });

    it('debería lanzar error en producción', async () => {
      const originalEnv = process.env.NODE_ENV;
      process.env.NODE_ENV = 'production';

      await expect(documentService.deleteAllDocuments()).rejects.toThrow(
        'Esta acción solo está permitida en desarrollo'
      );

      process.env.NODE_ENV = originalEnv;
    });
  });
});
