/**
 * Tests unitaires pour NotesService
 */

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

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

// Mock de tagsService
vi.mock('../task-management/tags', () => ({
  tagsService: {
    ensureTagsExist: vi.fn(),
  },
}));

describe('NotesService', () => {
  let service: NotesService;
  const mockUserId = 'user-123';

  beforeEach(() => {
    vi.clearAllMocks();
    service = new NotesService();
  });

  describe('getNotes', () => {
    it('devrait récupérer toutes les notes pour un utilisateur', async () => {
      const mockNotes = [
        {
          id: 'note-1',
          title: 'Note 1',
          content: 'Content 1',
          userId: mockUserId,
          taskId: null,
          createdAt: new Date(),
          updatedAt: new Date(),
          noteTags: [],
          task: null,
        },
      ];

      vi.mocked(prisma.note.findMany).mockResolvedValue(mockNotes as any);

      const notes = await service.getNotes(mockUserId);

      expect(notes).toHaveLength(1);
      expect(prisma.note.findMany).toHaveBeenCalledWith({
        where: { userId: mockUserId },
        include: {
          noteTags: {
            include: {
              tag: true,
            },
          },
          task: {
            include: {
              taskTags: {
                include: {
                  tag: true,
                },
              },
              primaryTag: true,
            },
          },
        },
        orderBy: { updatedAt: 'desc' },
      });
    });
  });

  describe('getNoteById', () => {
    it('devrait récupérer une note par son ID', async () => {
      const mockNote = {
        id: 'note-1',
        title: 'Note 1',
        content: 'Content 1',
        userId: mockUserId,
        taskId: null,
        createdAt: new Date(),
        updatedAt: new Date(),
        noteTags: [],
        task: null,
      };

      vi.mocked(prisma.note.findFirst).mockResolvedValue(mockNote as any);

      const note = await service.getNoteById('note-1', mockUserId);

      expect(note).toBeDefined();
      expect(note?.id).toBe('note-1');
      expect(prisma.note.findFirst).toHaveBeenCalledWith({
        where: {
          id: 'note-1',
          userId: mockUserId,
        },
        include: {
          noteTags: {
            include: {
              tag: true,
            },
          },
          task: {
            include: {
              taskTags: {
                include: {
                  tag: true,
                },
              },
              primaryTag: true,
            },
          },
        },
      });
    });

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

      const note = await service.getNoteById('non-existent', mockUserId);

      expect(note).toBeNull();
    });
  });

  describe('createNote', () => {
    it('devrait créer une nouvelle note', async () => {
      const mockNote = {
        id: 'note-1',
        title: 'New Note',
        content: 'Content',
        userId: mockUserId,
        taskId: null,
        createdAt: new Date(),
        updatedAt: new Date(),
        noteTags: [],
        task: null,
      };

      vi.mocked(prisma.note.create).mockResolvedValue(mockNote as any);
      vi.mocked(prisma.note.findUnique).mockResolvedValue(mockNote as any);
      vi.mocked(tagsService.ensureTagsExist).mockResolvedValue([]);

      const note = await service.createNote({
        title: 'New Note',
        content: 'Content',
        userId: mockUserId,
      });

      expect(note).toBeDefined();
      expect(prisma.note.create).toHaveBeenCalled();
    });

    it('devrait créer une note avec des tags', async () => {
      const mockNote = {
        id: 'note-1',
        title: 'New Note',
        content: 'Content',
        userId: mockUserId,
        taskId: null,
        createdAt: new Date(),
        updatedAt: new Date(),
        noteTags: [],
        task: null,
      };

      const mockTags = [
        { id: 'tag-1', name: 'Tag 1', color: '#ff0000', isPinned: false },
      ];

      vi.mocked(prisma.note.create).mockResolvedValue(mockNote as any);
      vi.mocked(prisma.note.findUnique).mockResolvedValue({
        ...mockNote,
        noteTags: [{ tag: mockTags[0] }],
      } as any);
      vi.mocked(tagsService.ensureTagsExist).mockResolvedValue(mockTags as any);
      vi.mocked(prisma.noteTag.createMany).mockResolvedValue({
        count: 1,
      } as any);

      await service.createNote({
        title: 'New Note',
        content: 'Content',
        userId: mockUserId,
        tags: ['Tag 1'],
      });

      expect(tagsService.ensureTagsExist).toHaveBeenCalledWith(
        ['Tag 1'],
        mockUserId
      );
    });
  });

  describe('updateNote', () => {
    it('devrait mettre à jour une note', async () => {
      const mockNote = {
        id: 'note-1',
        title: 'Updated Note',
        content: 'Updated Content',
        userId: mockUserId,
        taskId: null,
        createdAt: new Date(),
        updatedAt: new Date(),
        noteTags: [],
        task: null,
      };

      vi.mocked(prisma.note.findFirst).mockResolvedValue({
        id: 'note-1',
        userId: mockUserId,
      } as any);
      vi.mocked(prisma.note.update).mockResolvedValue(mockNote as any);
      vi.mocked(prisma.note.findUnique).mockResolvedValue({
        ...mockNote,
        noteTags: [],
      } as any);
      vi.mocked(prisma.noteTag.deleteMany).mockResolvedValue({
        count: 0,
      } as any);
      vi.mocked(tagsService.ensureTagsExist).mockResolvedValue([]);

      const note = await service.updateNote('note-1', mockUserId, {
        title: 'Updated Note',
        content: 'Updated Content',
      });

      expect(note).toBeDefined();
      expect(prisma.note.update).toHaveBeenCalled();
    });

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

      await expect(
        service.updateNote('non-existent', mockUserId, {
          title: 'Updated',
        })
      ).rejects.toThrow();
    });
  });

  describe('deleteNote', () => {
    it('devrait supprimer une note', async () => {
      vi.mocked(prisma.note.findFirst).mockResolvedValue({
        id: 'note-1',
        userId: mockUserId,
      } as any);
      vi.mocked(prisma.note.delete).mockResolvedValue({} as any);

      await service.deleteNote('note-1', mockUserId);

      expect(prisma.note.delete).toHaveBeenCalledWith({
        where: { id: 'note-1' },
      });
    });

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

      await expect(
        service.deleteNote('non-existent', mockUserId)
      ).rejects.toThrow();
    });
  });

  describe('searchNotes', () => {
    it('devrait rechercher dans les notes', async () => {
      const mockNotes = [
        {
          id: 'note-1',
          title: 'Test Note',
          content: 'Content',
          userId: mockUserId,
          taskId: null,
          createdAt: new Date(),
          updatedAt: new Date(),
          noteTags: [],
          task: null,
        },
      ];

      vi.mocked(prisma.note.findMany).mockResolvedValue(mockNotes as any);

      const notes = await service.searchNotes(mockUserId, 'Test');

      expect(notes).toHaveLength(1);
      expect(prisma.note.findMany).toHaveBeenCalledWith({
        where: {
          userId: mockUserId,
          OR: [
            { title: { contains: 'Test' } },
            { content: { contains: 'Test' } },
          ],
        },
        orderBy: { updatedAt: 'desc' },
      });
    });
  });

  describe('getNotesStats', () => {
    it('devrait retourner les statistiques des notes', async () => {
      const mockNotes = [
        {
          id: 'note-1',
          content: 'This is a test note with words',
          updatedAt: new Date(),
        },
        {
          id: 'note-2',
          content: 'Another note',
          updatedAt: new Date(),
        },
      ];

      vi.mocked(prisma.note.findMany).mockResolvedValue(mockNotes as any);

      const stats = await service.getNotesStats(mockUserId);

      expect(stats.totalNotes).toBeGreaterThanOrEqual(0);
      expect(stats.totalWords).toBeGreaterThanOrEqual(0);
      expect(stats.lastUpdated).toBeDefined();
    });
  });
});
