import { describe, it, expect, beforeEach, vi } from 'vitest';
import { documentRepository } from '../documentRepository';
import { prisma } from '../../prisma';

// Mock de Prisma
vi.mock('../../prisma', () => ({
  prisma: {
    document: {
      findMany: vi.fn(),
      findUnique: vi.fn(),
      create: vi.fn(),
      update: vi.fn(),
      delete: vi.fn(),
      deleteMany: vi.fn(),
      count: vi.fn(),
    },
  },
}));

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

  describe('findMany', () => {
    it('debería buscar documentos con filtros para usuario normal', async () => {
      const mockDocuments = [
        {
          id: '1',
          fileName: 'test.pdf',
          fileUrl: 'abc123',
          ownerId: 'user1',
        },
      ];

      vi.mocked(prisma.document.findMany).mockResolvedValue(mockDocuments as any);

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

      expect(result).toHaveLength(1);
      expect(prisma.document.findMany).toHaveBeenCalledWith({
        where: {
          ownerId: 'user1',
        },
        include: {
          owner: { select: { id: true, name: true, username: true } },
          category: { select: { id: true, name: true, icon: true, color: true, expenseType: true, paymentMethod: true } },
        },
        orderBy: { createdAt: 'desc' },
      });
    });

    it('debería buscar todos los documentos para ADMIN', async () => {
      vi.mocked(prisma.document.findMany).mockResolvedValue([]);

      await documentRepository.findMany({ userId: 'admin1', role: 'ADMIN' });

      expect(prisma.document.findMany).toHaveBeenCalledWith({
        where: {},
        include: {
          owner: { select: { id: true, name: true, username: true } },
          category: { select: { id: true, name: true, icon: true, color: true, expenseType: true, paymentMethod: true } },
        },
        orderBy: { createdAt: 'desc' },
      });
    });

    it('debería aplicar filtro de búsqueda por nombre de archivo', async () => {
      vi.mocked(prisma.document.findMany).mockResolvedValue([]);

      await documentRepository.findMany({
        userId: 'user1',
        role: 'USER',
        query: 'factura',
      });

      expect(prisma.document.findMany).toHaveBeenCalledWith({
        where: {
          ownerId: 'user1',
          fileName: { contains: 'factura' },
        },
        include: expect.any(Object),
        orderBy: { createdAt: 'desc' },
      });
    });

    it('debería aplicar filtro por categoría', async () => {
      vi.mocked(prisma.document.findMany).mockResolvedValue([]);

      await documentRepository.findMany({
        userId: 'user1',
        role: 'USER',
        categoryId: 'cat1',
      });

      expect(prisma.document.findMany).toHaveBeenCalledWith({
        where: {
          ownerId: 'user1',
          categoryId: 'cat1',
        },
        include: expect.any(Object),
        orderBy: { createdAt: 'desc' },
      });
    });
  });

  describe('findById', () => {
    it('debería buscar un documento por ID', async () => {
      const mockDocument = {
        id: '1',
        fileName: 'test.pdf',
        fileUrl: 'abc123',
        ownerId: 'user1',
      };

      vi.mocked(prisma.document.findUnique).mockResolvedValue(mockDocument as any);

      const result = await documentRepository.findById('1');

      expect(result).toBeDefined();
      expect(prisma.document.findUnique).toHaveBeenCalledWith({
        where: { id: '1' },
        include: {
          owner: { select: { id: true, name: true, username: true } },
          category: true,
        },
      });
    });

    it('debería retornar null si no existe', async () => {
      vi.mocked(prisma.document.findUnique).mockResolvedValue(null);

      const result = await documentRepository.findById('nonexistent');

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

  describe('findAll', () => {
    it('debería retornar todos los documentos', async () => {
      const mockDocuments = [
        { id: '1', fileName: 'test1.pdf' },
        { id: '2', fileName: 'test2.pdf' },
      ];

      vi.mocked(prisma.document.findMany).mockResolvedValue(mockDocuments as any);

      const result = await documentRepository.findAll();

      expect(result).toHaveLength(2);
      expect(prisma.document.findMany).toHaveBeenCalledWith({
        include: {
          owner: { select: { id: true, name: true, username: true } },
          category: true,
        },
        orderBy: { createdAt: 'desc' },
      });
    });
  });

  describe('create', () => {
    it('debería crear un documento', async () => {
      const mockDocument = {
        id: '1',
        fileName: 'test.pdf',
        fileUrl: 'abc123',
        ownerId: 'user1',
        extractedData: { nombre: 'Test' },
      };

      vi.mocked(prisma.document.create).mockResolvedValue(mockDocument as any);

      const result = await documentRepository.create({
        fileName: 'test.pdf',
        fileUrl: 'abc123',
        extractedData: { nombre: 'Test' },
        owner: { connect: { id: 'user1' } },
      });

      expect(result.id).toBe('1');
      expect(prisma.document.create).toHaveBeenCalledWith({
        data: {
          fileName: 'test.pdf',
          fileUrl: 'abc123',
          extractedData: { nombre: 'Test' },
          owner: { connect: { id: 'user1' } },
        },
        include: {
          owner: { select: { id: true, name: true, username: true } },
          category: true,
        },
      });
    });
  });

  describe('update', () => {
    it('debería actualizar un documento', async () => {
      const mockDocument = {
        id: '1',
        fileName: 'updated.pdf',
        extractedData: { nombre: 'Updated' },
      };

      vi.mocked(prisma.document.update).mockResolvedValue(mockDocument as any);

      const result = await documentRepository.update('1', {
        fileName: 'updated.pdf',
        extractedData: { nombre: 'Updated' },
      });

      expect(result.fileName).toBe('updated.pdf');
      expect(prisma.document.update).toHaveBeenCalledWith({
        where: { id: '1' },
        data: {
          fileName: 'updated.pdf',
          extractedData: { nombre: 'Updated' },
        },
        include: {
          owner: { select: { id: true, name: true, username: true } },
          category: true,
        },
      });
    });
  });

  describe('delete', () => {
    it('debería eliminar un documento', async () => {
      const mockDocument = { id: '1', fileName: 'test.pdf' };

      vi.mocked(prisma.document.delete).mockResolvedValue(mockDocument as any);

      const result = await documentRepository.delete('1');

      expect(result.id).toBe('1');
      expect(prisma.document.delete).toHaveBeenCalledWith({
        where: { id: '1' },
      });
    });
  });

  describe('deleteMany', () => {
    it('debería eliminar todos los documentos', async () => {
      vi.mocked(prisma.document.deleteMany).mockResolvedValue({ count: 5 } as any);

      const result = await documentRepository.deleteMany();

      expect(result.count).toBe(5);
      expect(prisma.document.deleteMany).toHaveBeenCalledWith();
    });
  });

  describe('countByOwner', () => {
    it('debería contar documentos por propietario', async () => {
      vi.mocked(prisma.document.count).mockResolvedValue(10);

      const result = await documentRepository.countByOwner('user1');

      expect(result).toBe(10);
      expect(prisma.document.count).toHaveBeenCalledWith({
        where: { ownerId: 'user1' },
      });
    });
  });

  describe('exists', () => {
    it('debería retornar true si el documento existe', async () => {
      vi.mocked(prisma.document.findUnique).mockResolvedValue({ id: '1' } as any);

      const result = await documentRepository.exists('1');

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

    it('debería retornar false si el documento no existe', async () => {
      vi.mocked(prisma.document.findUnique).mockResolvedValue(null);

      const result = await documentRepository.exists('nonexistent');

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