/**
 * Tests unitaires pour tagsService
 */

/* eslint-disable @typescript-eslint/no-explicit-any */
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { tagsService } from '../tags';
import { prisma } from '@/services/core/database';

// Mock de prisma
vi.mock('@/services/core/database', () => ({
  prisma: {
    tag: {
      findMany: vi.fn(),
      findFirst: vi.fn(),
      create: vi.fn(),
      update: vi.fn(),
      delete: vi.fn(),
    },
    taskTag: {
      deleteMany: vi.fn(),
    },
  },
}));

describe('tagsService', () => {
  const mockUserId = 'user-123';

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

  describe('getTags', () => {
    it('devrait récupérer tous les tags avec leur usage', async () => {
      const mockTags = [
        {
          id: 'tag-1',
          name: 'Tag 1',
          color: '#ff0000',
          isPinned: false,
          _count: { taskTags: 5 },
        },
        {
          id: 'tag-2',
          name: 'Tag 2',
          color: '#00ff00',
          isPinned: true,
          _count: { taskTags: 2 },
        },
      ];

      vi.mocked(prisma.tag.findMany).mockResolvedValue(mockTags as any);

      const tags = await tagsService.getTags(mockUserId);

      expect(tags).toHaveLength(2);
      expect(tags[0]).toEqual({
        id: 'tag-1',
        name: 'Tag 1',
        color: '#ff0000',
        isPinned: false,
        usage: 5,
      });
      expect(prisma.tag.findMany).toHaveBeenCalledWith({
        where: { ownerId: mockUserId },
        include: {
          _count: {
            select: {
              taskTags: true,
            },
          },
        },
        orderBy: { name: 'asc' },
      });
    });
  });

  describe('getTagById', () => {
    it('devrait récupérer un tag par son ID', async () => {
      const mockTag = {
        id: 'tag-1',
        name: 'Tag 1',
        color: '#ff0000',
        isPinned: false,
      };

      vi.mocked(prisma.tag.findFirst).mockResolvedValue(mockTag as any);

      const tag = await tagsService.getTagById('tag-1', mockUserId);

      expect(tag).toEqual(mockTag);
      expect(prisma.tag.findFirst).toHaveBeenCalledWith({
        where: {
          id: 'tag-1',
          ownerId: mockUserId,
        },
      });
    });

    it("devrait retourner null si le tag n'existe pas", async () => {
      vi.mocked(prisma.tag.findFirst).mockResolvedValue(null);

      const tag = await tagsService.getTagById('non-existent', mockUserId);

      expect(tag).toBeNull();
    });
  });

  describe('getTagByName', () => {
    it('devrait récupérer un tag par son nom', async () => {
      const mockTag = {
        id: 'tag-1',
        name: 'Tag 1',
        color: '#ff0000',
        isPinned: false,
      };

      vi.mocked(prisma.tag.findFirst).mockResolvedValue(mockTag as any);

      const tag = await tagsService.getTagByName('Tag 1', mockUserId);

      expect(tag).toEqual(mockTag);
      expect(prisma.tag.findFirst).toHaveBeenCalledWith({
        where: {
          name: {
            equals: 'Tag 1',
          },
          ownerId: mockUserId,
        },
      });
    });
  });

  describe('createTag', () => {
    it('devrait créer un nouveau tag', async () => {
      const mockTag = {
        id: 'tag-1',
        name: 'New Tag',
        color: '#ff0000',
        isPinned: false,
      };

      vi.mocked(prisma.tag.findFirst).mockResolvedValue(null); // Pas de tag existant
      vi.mocked(prisma.tag.create).mockResolvedValue(mockTag as any);

      const tag = await tagsService.createTag({
        name: 'New Tag',
        color: '#ff0000',
        userId: mockUserId,
      });

      expect(tag).toEqual(mockTag);
      expect(prisma.tag.create).toHaveBeenCalledWith({
        data: {
          name: 'New Tag',
          color: '#ff0000',
          isPinned: false,
          ownerId: mockUserId,
        },
      });
    });

    it('devrait lancer une erreur si le tag existe déjà', async () => {
      const existingTag = {
        id: 'tag-1',
        name: 'Existing Tag',
        color: '#ff0000',
        isPinned: false,
      };

      vi.mocked(prisma.tag.findFirst).mockResolvedValue(existingTag as any);

      await expect(
        tagsService.createTag({
          name: 'Existing Tag',
          color: '#ff0000',
          userId: mockUserId,
        })
      ).rejects.toThrow('existe déjà');
    });

    it('devrait trimmer le nom du tag', async () => {
      const mockTag = {
        id: 'tag-1',
        name: 'Trimmed Tag',
        color: '#ff0000',
        isPinned: false,
      };

      vi.mocked(prisma.tag.findFirst).mockResolvedValue(null);
      vi.mocked(prisma.tag.create).mockResolvedValue(mockTag as any);

      await tagsService.createTag({
        name: '  Trimmed Tag  ',
        color: '#ff0000',
        userId: mockUserId,
      });

      expect(prisma.tag.create).toHaveBeenCalledWith({
        data: {
          name: 'Trimmed Tag',
          color: '#ff0000',
          isPinned: false,
          ownerId: mockUserId,
        },
      });
    });
  });

  describe('updateTag', () => {
    it('devrait mettre à jour un tag', async () => {
      const mockTag = {
        id: 'tag-1',
        name: 'Updated Tag',
        color: '#00ff00',
        isPinned: true,
      };

      vi.mocked(prisma.tag.findFirst).mockResolvedValue({
        id: 'tag-1',
        name: 'Old Tag',
        color: '#ff0000',
        isPinned: false,
      } as any);
      vi.mocked(prisma.tag.update).mockResolvedValue(mockTag as any);

      const tag = await tagsService.updateTag('tag-1', mockUserId, {
        name: 'Updated Tag',
        color: '#00ff00',
        isPinned: true,
      });

      expect(tag).toEqual(mockTag);
      expect(prisma.tag.update).toHaveBeenCalledWith({
        where: { id: 'tag-1' },
        data: {
          name: 'Updated Tag',
          color: '#00ff00',
          isPinned: true,
        },
      });
    });

    it("devrait lancer une erreur si le tag n'existe pas", async () => {
      vi.mocked(prisma.tag.findFirst).mockResolvedValue(null);

      await expect(
        tagsService.updateTag('non-existent', mockUserId, {
          name: 'Updated Tag',
        })
      ).rejects.toThrow('non trouvé');
    });
  });

  describe('deleteTag', () => {
    it('devrait supprimer un tag', async () => {
      vi.mocked(prisma.tag.findFirst).mockResolvedValue({
        id: 'tag-1',
        name: 'Tag to delete',
        color: '#ff0000',
        isPinned: false,
      } as any);
      vi.mocked(prisma.taskTag.deleteMany).mockResolvedValue({
        count: 0,
      } as any);
      vi.mocked(prisma.tag.delete).mockResolvedValue({} as any);

      await tagsService.deleteTag('tag-1', mockUserId);

      expect(prisma.taskTag.deleteMany).toHaveBeenCalledWith({
        where: { tagId: 'tag-1' },
      });
      expect(prisma.tag.delete).toHaveBeenCalledWith({
        where: { id: 'tag-1' },
      });
    });

    it("devrait lancer une erreur si le tag n'existe pas", async () => {
      vi.mocked(prisma.tag.findFirst).mockResolvedValue(null);

      await expect(
        tagsService.deleteTag('non-existent', mockUserId)
      ).rejects.toThrow('non trouvé');
    });
  });
});
