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

vi.mock('../../prisma', () => ({
  prisma: {
    category: {
      findMany: vi.fn(),
      findUnique: vi.fn(),
      create: vi.fn(),
      update: vi.fn(),
      delete: vi.fn(),
    },
  },
}));

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

  describe('findMany', () => {
    it('debería retornar todas las categorías ordenadas por nombre', async () => {
      const mockCategories = [
        { id: '1', name: 'Facturas', _count: { documents: 5 } },
        { id: '2', name: 'Recibos', _count: { documents: 3 } },
      ];

      vi.mocked(prisma.category.findMany).mockResolvedValue(mockCategories as any);

      const result = await categoryRepository.findMany();

      expect(result).toHaveLength(2);
      expect(prisma.category.findMany).toHaveBeenCalledWith({
        include: {
          _count: { select: { documents: true } },
        },
        orderBy: { name: 'asc' },
      });
    });

    it('debería retornar array vacío si no hay categorías', async () => {
      vi.mocked(prisma.category.findMany).mockResolvedValue([]);

      const result = await categoryRepository.findMany();

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

  describe('findById', () => {
    it('debería retornar una categoría por ID', async () => {
      const mockCategory = {
        id: '1',
        name: 'Facturas',
        description: 'Facturas de compra',
        _count: { documents: 5 },
      };

      vi.mocked(prisma.category.findUnique).mockResolvedValue(mockCategory as any);

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

      expect(result).toBeDefined();
      expect(result?.name).toBe('Facturas');
      expect(prisma.category.findUnique).toHaveBeenCalledWith({
        where: { id: '1' },
        include: {
          _count: { select: { documents: true } },
        },
      });
    });

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

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

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

  describe('findByName', () => {
    it('debería retornar una categoría por nombre', async () => {
      const mockCategory = {
        id: '1',
        name: 'Facturas',
        description: 'Facturas de compra',
      };

      vi.mocked(prisma.category.findUnique).mockResolvedValue(mockCategory as any);

      const result = await categoryRepository.findByName('Facturas');

      expect(result).toBeDefined();
      expect(result?.name).toBe('Facturas');
      expect(prisma.category.findUnique).toHaveBeenCalledWith({
        where: { name: 'Facturas' },
      });
    });

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

      const result = await categoryRepository.findByName('NonExistent');

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

  describe('create', () => {
    it('debería crear una categoría', async () => {
      const mockCategory = {
        id: '1',
        name: 'Nueva Categoría',
        description: 'Descripción de prueba',
        color: '#3B82F6',
        icon: '📄',
        _count: { documents: 0 },
      };

      vi.mocked(prisma.category.create).mockResolvedValue(mockCategory as any);

      const result = await categoryRepository.create({
        name: 'Nueva Categoría',
        description: 'Descripción de prueba',
        color: '#3B82F6',
        icon: '📄',
      });

      expect(result.id).toBe('1');
      expect(prisma.category.create).toHaveBeenCalledWith({
        data: {
          name: 'Nueva Categoría',
          description: 'Descripción de prueba',
          color: '#3B82F6',
          icon: '📄',
        },
        include: {
          _count: { select: { documents: true } },
        },
      });
    });
  });

  describe('update', () => {
    it('debería actualizar una categoría', async () => {
      const mockCategory = {
        id: '1',
        name: 'Actualizada',
        color: '#EF4444',
      };

      vi.mocked(prisma.category.update).mockResolvedValue(mockCategory as any);

      const result = await categoryRepository.update('1', {
        name: 'Actualizada',
        color: '#EF4444',
      });

      expect(result.name).toBe('Actualizada');
      expect(prisma.category.update).toHaveBeenCalledWith({
        where: { id: '1' },
        data: {
          name: 'Actualizada',
          color: '#EF4444',
        },
      });
    });
  });

  describe('delete', () => {
    it('debería eliminar una categoría', async () => {
      const mockCategory = { id: '1', name: 'A Eliminar' };

      vi.mocked(prisma.category.delete).mockResolvedValue(mockCategory as any);

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

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

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

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

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

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

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

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

  describe('countDocuments', () => {
    it('debería retornar el número de documentos de una categoría', async () => {
      const mockCategory = {
        id: '1',
        _count: { documents: 5 },
      };

      vi.mocked(prisma.category.findUnique).mockResolvedValue(mockCategory as any);

      const result = await categoryRepository.countDocuments('1');

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

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

      const result = await categoryRepository.countDocuments('nonexistent');

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

    it('debería retornar 0 si no tiene documentos', async () => {
      const mockCategory = {
        id: '1',
        _count: { documents: 0 },
      };

      vi.mocked(prisma.category.findUnique).mockResolvedValue(mockCategory as any);

      const result = await categoryRepository.countDocuments('1');

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