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

vi.mock('../../repositories/categoryRepository', () => ({
  categoryRepository: {
    findMany: vi.fn(),
    findById: vi.fn(),
    findByName: vi.fn(),
    create: vi.fn(),
    update: vi.fn(),
    delete: vi.fn(),
    countDocuments: vi.fn(),
  },
}));

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

  describe('getCategories', () => {
    it('debería retornar todas las categorías', async () => {
      const mockCategories = [
        {
          id: '1',
          name: 'Facturas',
          description: 'Facturas de compra',
          color: '#3B82F6',
          icon: '📄',
          createdAt: new Date(),
          updatedAt: new Date(),
          _count: { documents: 5 },
        },
        {
          id: '2',
          name: 'Recibos',
          description: null,
          color: '#10B981',
          icon: '📋',
          createdAt: new Date(),
          updatedAt: new Date(),
          _count: { documents: 3 },
        },
      ];

      vi.mocked(categoryRepository.findMany).mockResolvedValue(mockCategories as any);

      const result = await categoryService.getCategories();

      expect(result).toHaveLength(2);
      expect(result[0].name).toBe('Facturas');
      expect(result[1].name).toBe('Recibos');
      expect(categoryRepository.findMany).toHaveBeenCalled();
    });

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

      const result = await categoryService.getCategories();

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

  describe('getCategoryById', () => {
    it('debería retornar una categoría por ID', async () => {
      const mockCategory = {
        id: '1',
        name: 'Facturas',
        description: 'Facturas de compra',
        color: '#3B82F6',
        icon: '📄',
        createdAt: new Date(),
        updatedAt: new Date(),
        _count: { documents: 5 },
      };

      vi.mocked(categoryRepository.findById).mockResolvedValue(mockCategory as any);

      const result = await categoryService.getCategoryById('1');

      expect(result).toBeDefined();
      expect(result?.name).toBe('Facturas');
    });

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

      const result = await categoryService.getCategoryById('nonexistent');

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

  describe('createCategory', () => {
    it('debería crear una categoría exitosamente para ADMIN', async () => {
      const inputData = {
        name: 'Nueva Categoría',
        description: 'Descripción de prueba',
        color: '#EF4444',
        icon: '📁',
      };

      vi.mocked(categoryRepository.findByName).mockResolvedValue(null);
      vi.mocked(categoryRepository.create).mockResolvedValue({
        id: 'cat1',
        ...inputData,
        createdAt: new Date(),
        updatedAt: new Date(),
        _count: { documents: 0 },
      } as any);

      const result = await categoryService.createCategory(inputData, 'ADMIN');

      expect(result.id).toBe('cat1');
      expect(categoryRepository.create).toHaveBeenCalledWith({
        name: 'Nueva Categoría',
        description: 'Descripción de prueba',
        color: '#EF4444',
        icon: '📁',
      });
    });

    it('debería usar valores por defecto si no se proporcionan', async () => {
      const inputData = {
        name: 'Categoría Simple',
      };

      vi.mocked(categoryRepository.findByName).mockResolvedValue(null);
      vi.mocked(categoryRepository.create).mockResolvedValue({
        id: 'cat1',
        ...inputData,
        color: '#3B82F6',
        icon: '📄',
        createdAt: new Date(),
        updatedAt: new Date(),
        _count: { documents: 0 },
      } as any);

      await categoryService.createCategory(inputData, 'ADMIN');

      expect(categoryRepository.create).toHaveBeenCalledWith({
        name: 'Categoría Simple',
        description: undefined,
        color: '#3B82F6',
        icon: '📄',
      });
    });

    it('debería lanzar error si el usuario no es ADMIN', async () => {
      const inputData = { name: 'Test' };

      await expect(
        categoryService.createCategory(inputData, 'USER')
      ).rejects.toThrow('No tienes permisos para crear categorías');
    });

    it('debería lanzar error si el nombre ya existe', async () => {
      const inputData = { name: 'Existente' };

      vi.mocked(categoryRepository.findByName).mockResolvedValue({
        id: 'existing',
        name: 'Existente',
      } as any);

      await expect(
        categoryService.createCategory(inputData, 'ADMIN')
      ).rejects.toThrow('Ya existe una categoría con ese nombre');
    });
  });

  describe('updateCategory', () => {
    it('debería actualizar una categoría para ADMIN', async () => {
      const existingCategory = {
        id: '1',
        name: 'Original',
        description: 'Original desc',
        color: '#3B82F6',
        icon: '📄',
        createdAt: new Date(),
        updatedAt: new Date(),
      };

      vi.mocked(categoryRepository.findById).mockResolvedValue(existingCategory as any);
      vi.mocked(categoryRepository.findByName).mockResolvedValue(null);
      vi.mocked(categoryRepository.update).mockResolvedValue(existingCategory as any);

      await categoryService.updateCategory(
        '1',
        { name: 'Actualizado', color: '#EF4444' },
        'ADMIN'
      );

      expect(categoryRepository.update).toHaveBeenCalledWith('1', {
        name: 'Actualizado',
        color: '#EF4444',
      });
    });

    it('debería lanzar error si el usuario no es ADMIN', async () => {
      await expect(
        categoryService.updateCategory('1', { name: 'Test' }, 'USER')
      ).rejects.toThrow('No tienes permisos para actualizar categorías');
    });

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

      await expect(
        categoryService.updateCategory('1', { name: 'Test' }, 'ADMIN')
      ).rejects.toThrow('Categoría no encontrada');
    });

    it('debería lanzar error si otro nombre ya existe', async () => {
      const existingCategory = { id: '1', name: 'Original' };
      const otherCategory = { id: '2', name: 'Nuevo' };

      vi.mocked(categoryRepository.findById).mockResolvedValue(existingCategory as any);
      vi.mocked(categoryRepository.findByName).mockResolvedValue(otherCategory as any);

      await expect(
        categoryService.updateCategory('1', { name: 'Nuevo' }, 'ADMIN')
      ).rejects.toThrow('Ya existe otra categoría con ese nombre');
    });
  });

  describe('deleteCategory', () => {
    it('debería eliminar una categoría sin documentos para ADMIN', async () => {
      const mockCategory = {
        id: '1',
        name: 'A eliminar',
        _count: { documents: 0 },
      };

      vi.mocked(categoryRepository.findById).mockResolvedValue(mockCategory as any);
      vi.mocked(categoryRepository.countDocuments).mockResolvedValue(0);
      vi.mocked(categoryRepository.delete).mockResolvedValue(mockCategory as any);

      await categoryService.deleteCategory('1', 'ADMIN');

      expect(categoryRepository.delete).toHaveBeenCalledWith('1');
    });

    it('debería lanzar error si el usuario no es ADMIN', async () => {
      await expect(
        categoryService.deleteCategory('1', 'USER')
      ).rejects.toThrow('No tienes permisos para eliminar categorías');
    });

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

      await expect(
        categoryService.deleteCategory('1', 'ADMIN')
      ).rejects.toThrow('Categoría no encontrada');
    });

    it('debería lanzar error si tiene documentos asociados', async () => {
      const mockCategory = {
        id: '1',
        name: 'Con Documentos',
        _count: { documents: 5 },
      };

      vi.mocked(categoryRepository.findById).mockResolvedValue(mockCategory as any);
      vi.mocked(categoryRepository.countDocuments).mockResolvedValue(5);

      await expect(
        categoryService.deleteCategory('1', 'ADMIN')
      ).rejects.toThrow('No se puede eliminar. Esta categoría tiene 5 documento(s) asociado(s)');
    });
  });
});
